关于 Java 的泛型
根据《Java 核心技术卷 1》的第 8 章稍微了解一下泛型。
Java 的泛型归根结底是一种语法糖,当我们定义一个泛型类,比如 Pair<T,U>,又去声明两个实现,比如 Pair<String, Integer>,Pair<Integer, LocalDate>,这并不会真的在编译期多整出两个类出来(C++的模版是这样干的),在运行时,泛型类其实归根结底只有一个raw type
。
考虑如下代码——
1 |
|
可以看到,a.getClass()
并不能返回什么 Class Pair<Integer,String>,编译期会将泛型的类型擦除(擦除后获得的 raw type 为它继承的第一个类/接口,如果没有,则是 Object,所以本书建议将带方法的接口放在前,而标签接口,如RandomAccess
等接口放到最后,以防止添加更多类型转换),所有的泛型类的实例其实都是raw Type
类型。只不过编译器会自动处理类型转换罢了。
为什么要泛型
泛型机制相较于杂乱地使用 Object 变量进行强制类型转换的代码来说更有安全性和可读性,它对使用容器类型尤其有用。
我认为 Java 中的泛型,实际上就是编译期的类型检查机制,对变量类型进行各种各样的约束,从而保证不出错误,同时也方便编写。比如,要求泛型类必须实现 Comparable 接口(这个接口也是泛型的,它标识能够与什么对象进行比较),这样就能够方便大胆地对泛型变量调用compareTo
方法而不用担心抛出异常,因为这是编译器保证了的。
比如,我之前按照《算法》中的排序算法,编写如下代码——
1 |
|
这里,将 Comparable 数组作为参数,它抛出一个警告,要求不要使用 raw type,这里可以改成Comparable<T>
,但也可以这样改——
1 |
|
好家伙,这样不是看起来更舒服一些?而且能让编译器替我们做检查,妙哇!
extends 和 super
泛型中可以有两个关键字——extends
和super
。
泛型之前
Java 在提供泛型之前,泛型是通过继承实现的(啥?)。考虑没有泛型的 ArrayList——
1 |
|
当我们试图获取一个值的时候,必须要对它进行强制类型转换——
1 |
|
显然,这里如果加入泛型,就能在编译期进行语法检查,防止这种操作了。
为什么不能声明泛型数组?
原因就是,会绕过类型检查。编译器无法发现类型错误,
考虑如下代码——
1 |
|
假设泛型数组是合法的,则用户可以向上转型,将其转化为Object
数组,然后向其插入其它类型的该泛型,这在语法上是没有任何问题的——它们的 raw type 都是一样的嘛,所以不会抛java.lang.ArrayStoreException
(如果试图向该Object
数组里插入别的类型就会抛这个异常),只有到使用中才会发现问题,抛出java.lang.ClassCastException
。
总之,泛型数组会导致编译期的泛型机制失去约束的意义,需要泛型数组应当使用ArrayList
。
但是,泛型数组可以作为方法参数传入,还蛮奇妙的。或许是因为,作为函数参数使用时,其使用是方法设计者规定,而非是用户规定的,所以它是被“封装”了的,能够避过用户的骚操作。这是 Java 对方法设计者的信任吗?
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!