问题提出
List<String>
可以赋给 List<Integer>
吗,如果可以,如何实现?
泛型擦除
Java泛型是使用擦除来实现的,当使用泛型时,任何具体的类型信息都会被擦除。在泛型代码内部,无法获得任何有关泛型参数类型的信息。你唯一知道的就是你在使用一个对象。因此List<String>
和 List<Integer>
在运行时实际上是相同的类型。这两种形式都被擦除成它们的“原生”类型 List。
此时我们来看下面代码中标注的几个地方,能正确编译运行吗?
public static void main(String[] args) {
List<Integer> integerList = getStringList(); // 1
System.out.println(integerList.get(0)); // 2
System.out.println(integerList.get(0).toString()); // 3
System.out.println(String.valueOf(integerList.get(0))); // 4
}
public static <T> List<T> getStringList(){
List<String> result = new ArrayList<String>(){{
add("1");
}};
return (List<T>)result;
}
1、正确编译且能正常运行
由于泛型擦除,两个类型都被擦除为原生类型 List ,故而无异常。
2、正确编译且能正常运行
3、运行时异常,触发 java.lang.ClassCastException 异常
调用 integerList.get(0).toString() 方法时,实际上是泛型内部使用了隐式类型转换,调用了Integer.toString() 方法,但此时integerList.get(0) 实际存储的类型是String ,所以会触发类型转换异常。
4、正确编译且能正常运行
因为String.valueOf()方法使用了多态,实现了各种类型的支持,无论是Integer还是String 都有与之匹配的方法。
泛型边界
那么类似于 3 这种问题,有没有办法避免,或者说在编译器就使之暴露出来呢?当然有,那就是泛型边界。
边界可以在用于泛型的参数类型上设置限制条件。为此 java 泛型重用了 extends 关键字。
1、通配符
<?> : 无界通配符,可以认为是一种装饰,因为使用无界通配符好像等价于使用原生类型 。
<? super T> :超类型通配符,限定下边界为 T (逆变)。
<? extends T> :限定上边界为 T (协变)。
2、举例
例如,我们可以将getStringList方法中的 T 限定为 T extends Integer
那么在编译时就会提升 cannot cast java.util.List<java.lang.String> to java.util.List<T>
,因为上界为Integer,而显然String并不是 integer 的子类,所以无法转换。
public static <T extends Integer> List<T> getStringList(){
List<String> result = new ArrayList<String>(){{
add("1");
}};
return (List<T>)result; //无法转换,编译时报错
}