什么是类型擦除?
泛型在很多编程语言中都存在,而Java中的实现方式最为独特,Java的泛型只存在于源码阶段,在编译期会被擦除。具体来说,Java编译器会将类型参数替换为其上界(upper bound,在Java中即类型参数中extends子句的类型),如果上界没有定义,则默认为Object。为了实现泛型做了什么?隐式转换
为了保证类型安全,会插入显式类型转换,示例如下:// 编译前 List books = getBooks(); for (Book obj : books) { book.type(); } // 编译后 List books = getBooks(); for (Object obj : books) { // 类型转换,编译期自动生成的 Book book = (Book) obj; book.type(); }桥接方法
为了保证 多态性,会生成桥接方法,示例如下: // 源代码 public class People { void set(T t) { } } public class Man extends People { @Override void set(String str) { // do something } } // 编译后 public class Man extends People{ void set(String str) { // do something } // 桥接方法,编译期自动生成的 void set(Object str) { set((String)str); } }为什么要引入泛型?
Java在jdk5引入泛型其中一方面是为了解决集合遍历时候总是需要手动强制类型转换的问题,如下面代码一样:List books = getBooks(); for (Object obj : books) { // 所以这里总是需要强转 Book book = (Book) obj; book.type(); }
但泛型的引入又导致jvm不能向下兼容,由此而搞了个类型擦除,但是类型擦除又引入了一系列的问题,我们接着看。类型擦除带来的恶果
类型擦除带来了一系列的不良后果,这也间接导致了后续Java版本用更多的实现代码来弥补这些不良后果。反射困境
由于泛型信息在编译时被擦除,因此在运行时,无法获取泛型信息。这使得开发者无法在运行时获得泛型类型,这对于一些需要动态获取泛型信息的框架和工具来说是一个很大的问题。相关框架为了能够获取到泛型类型信息,这些框架和工具都是通过反射和其他机制来实现动态获取泛型信息的。例如:Fastjson:阿里巴巴开发的 JSON 库,提供了 TypeReference 类,可以动态获取泛型信息,Jackson与Gson也有类似实现。Java 8 TypeTools:提供了 GenericTypeReflector 类,可以动态获取泛型信息。Spring Framework:Spring 提供了 ParameterizedTypeReference 类,可以动态获取泛型信息。类型数量爆炸
熟悉Java的同学有没有很奇怪一件事,Java 8 内置的 函数式接口为什么会有 Supplier、Function 和 BiFunction 之分?为什么不是Function、Function、Function呢?因为 Java没法用泛型的个数来区分类型,从而导致需要定义更多的类型来区分,还美其名曰为了增加可读性。 对基本类型不友好
泛型不支持基本类型,由于运行时泛型会被擦除,导致运行时无法计算泛型类型所需要的内存大小,为了解决这个问题,Java设计者们又想了个歪主意,泛型只能使用引用类型,因为不管什么类型的引用占的内存大小都是一样的,但是基本类型大小不一。
但是通过规范解决了这个问题,又引入了新的问题,我们要向一个集合中添加基本类型怎么办?谜之操作来了, 定义所有基础类型的包装类型 (int与Integer、long与Long等等),这又引入了一大堆新的实现和概念,例如自动装箱拆箱。忍不住多吐槽一下,自动拆箱引起的npe让代码写起来非常丑陋和冗余,如下: Integer type = remoteService.getBookType(); // 这里不判空,自动拆箱可能就npe了 if(type != null && type == 1){ // do something }
为了让函数式接口能够支持基本类型,例如Function,如果你要返回int,则你需要IntFunction,那是不是还有DoubleFunction、LongFunction、DoubleToLongFunction、LongToIntFunction等等,如你多料,java.util.function下还有几十个类似的函数式接口,然而这还不是全部基本类型,很多基本类型还需要自己实现。
各种各样的函数式接口
总之,Java设计师们一味的向下兼容,导致了Java的泛型就像是一个怪胎,完全是为了拥有而拥有,C++与C#的实现方案完全优于它,恨铁不成钢啊,当然作为一个javaer,我仍然希望Java能够长足发展,期待未来的改变。
我是搬长,如果文章给你带来了收获,请关注 + 点赞 ,你的支持是我最大的动力!