Pair<Long,String>接收mybatis查询的结果储存的却是BigInteger(JAVA类型擦除机制)

最近做项目遇到一个匪夷所思的问题

代码如下:

我的map是由List<Pair<Long,String>>转换来的:

执行到for循环时,抛出异常:

告诉我BigInteger不能转为Long?一脸懵逼。

应该就是这个map的key为BigInteger,我用Long去接收它需要类型转换,所以报错了,于是我换了个写法确认一下,看看这个key到底是啥类型:

还真是BigInteger!

这里想做的事情是去获取表中的两个字段,由于数据一一对应,想直接用Map去接收,但是Mybatis并不支持直接转成我想要的Map,得自己处理,网上大概有3种方法:

1.自己写个注解去实现,

2.写个Handler,从里面去获取

3.用Pair去接收,这个看起来最简单,于是我选择了这个方案。

看了下数据库表结构,ORG_ID是BigInt没错,之前也是用Long去接收的Bigint没有任何问题,这次为什么转成了BigInteger,而且接收的时候还没有报错,一直运行到遍历的时候才报错?

Mapper中: 

表结构:

 Mapper.xml

和同事讨论了一下,得知:

但是我的表中这个字段并没有定义成unsigned无符号呀,为什么还是给我转成了BigInteger?

到这里,我有两个非常不解的疑惑:

1.为什么 mysql 中 bigint类型字段 在没有定义unsigned的情况下,被mybatis自动转成了Biginteger?根据官方文档中写的,bigint应该对应Long,只有在定义成无符号的时候,超出Long范围,才会变成BigInteger

2. 为什么Pair中定义Long去接收BigInteger却没有报错,一直运行到遍历它的时候才报错?

经过一番艰苦的排查,得知了结果:

1.sql语句中,select的ORG_ID来自于O表,其他字段来自T表,而我检查的时候的检查的却是T表中的ORG_ID字段。再重新看了一下O表,确实O表中的 ORG_ID 字段被定义成了unsigned无符号,所以接收它的时候,mybatis把它转成了Biginteger。

2.但是还有一个问题,为什么用Long去接收BigInteger的时候没有报错,一直到运行到for循环遍历的时候才报错呢?

这是因为JAVA的类型擦除机制,这里转载一下看到的资料:

什么是类型擦除?

这是个Java才有的“特性”。在运行时,Java不会保留范型。

举个例子,你可以往一群猫里放一条狗:

LinkedList<Cat> cats = new LinkedList<Cat>();
LinkedList list = cats;  // 注意我在这里把范型去掉了,但是list和cats是同一个链表!
list.add(new Dog());  // 完全没问题!

为什么可以这么玩?因为Java的范型只存在于源码里,编译的时候给你静态地检查一下范型类型是否正确,而到了运行时就不检查了。上面这段代码在JRE(Java运行环境)看来和下面这段没区别:

LinkedList cats = new LinkedList();  // 注意:没有范型!
LinkedList list = cats;
list.add(new Dog());

为什么要类型擦除?

为了向下兼容。

Java 5才引入了范型,在此之前都是没有范型的。当时引入范型只是为了解决遍历集合的时候总要手动强制转型的问题,比如像这样的代码:

List cats = loadCats();  // 5之前没有范型哦

for (Object obj : cats) {
  Cat cat = (Cat) obj;  // 所以这里总是需要强转
  ...
}

为了让JVM保持向下兼容,就出了类型擦除这个下下策。

类型擦除的后果

类型擦除带来了很多不良后果,下面列举三个我知道的后果(名称是我自己起的,不是什么官方名称):

反射困境

由于运行时没有范型,导致很多基于反射的实现需要额外提供范型类型的信息,比如Gson在反序列化JSON字符串到下面这种类型的对象时就会犯懵:

public class Foo {
  private LinkedList<Bar> bars;
  // 省略 get 和 set 方法
}

为什么?因为Gson在运行时通过反射拿不到bars里的元素应该是什么类型!所以对于这种情况,你就需要更绕也更反直觉的代码来解决问题。

类型数量爆炸

类型擦除带来的第二个问题是类型数量爆炸。比如 Java 8 内置的函数式接口为什么会有 Supplier、Function 和 BiFunction 之分?就是因为 Java 没法用范型个数来区分类型,因为范型被擦除了。C#没有类型擦除,所以可以用Func<R>表示一个类型,Func<T, R>表示另一个类型,Func<T1, T2, R> 表示再另一个类型。

基本数据类型歧视

还有一个机制不是那么明显的后果,就是基本数据类型受到歧视。

由于运行时范型会被擦除,导致运行时无法计算范型类型所需的内存大小。怎么办呢?Java设计组又想了个歪招:范型只能是引用类型!反正不管什么类型的引用占的内存大小都一样。基本数据类型因为各自有各自的大小所以不让用。

解决一个问题,又引入一个新问题:我需要往集合里扔基本数据类型怎么办?打包!于是就有了各种基本数据类型的包装类(顺道吐槽,为毛long的包装类是Long,double的包装类是Double,而int的包装类不是Int?),还有了什么自动装箱拆箱的“特性”。

顺带一提,C#的范型会保留到运行时,所以C#的范型可以是值类型(类似于Java的基本数据类型但更强大),也不需要装箱拆箱的操作。

继续吐槽,基本数据类型受到的歧视也会加剧类型数量爆炸,因为它和范型数量不在一个维度,所以各自导致的爆炸系数要相乘!比如上面说的 Supplier,如果你要返回int,则你不能用Supplier<int> (因为范型不能是基本数据类型)而必须用IntSupplier ,要返回double就要用DoubleSupplier。同理还有IntFunctionDoubleFunctionLongFunctionToLongFunctionIntToDoubleFunction 等等等等,每一种参数和返回类型的组合都必须单独定义一个接口。Java你真是没救了。(进一步吐槽,返回boolean的函数不叫 BooleanFunction,而是叫 Predicate!根据Java函数式接口设计的尿性还派生出了 BiPredicate、IntPredicate、LongPredicate、DoublePredicate,真是醉了)

我数了一下,java.util.function包里有43个函数式接口!而且这还不算完,因为不是所有的基本数据类型都覆盖到了,比如 byte、char 和 short 就没被覆盖到。 如果你需要对这些类型的支持,不好意思,你得自己写函数式接口!而且就算写出来,Java的stream接口还不认!

结束语

Java的范型就像是一个不受待见的私生子,它的出生只是图一时之快的后果,而且之后一直没有受到生父的关爱,从而引发了后续的各种社会问题。可以说,C++的模板都比它好,更不用说C#的真范型了。我觉得Java要有语言特性上的长足发展,必须在某个时候彻底清算这些历史遗留问题,而不是一味地无原则地坚持向下兼容,否则只会越来越丑。

作者:Aetherus
链接:https://www.zhihu.com/question/452958728/answer/1817841881

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风间琉璃c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值