本文来自知乎问答。
从悲观角度回答:不能。至少一点:因为还允许raw type的存在,而且允许从generic type到raw type,再从raw type到generic type的强制类型转换——当你这么用的时候编译器会给出警告,但是它还是会允许这种代码编译。
所以,为了保证运行时类型系统是安全的,编译器会在泛型信息的使用点上插入必要的运行时类型检查(checkcast指令),而这些运行时检查自然只能在运行时报错。
当然像是运行时用反射注入不兼容的泛型类型的值这些也是编译时顾不上的,所以也靠编译器插入的运行时类型检查来保证安全。
正确的使用可以保证,但是故意错用就没办法了。
怎么知道有没有故意错用呢?看javac编译会不会报警告就知道了。例如说:
import java.util.*; public class zz { public static ArrayList<String> foo(ArrayList<?> list) { return (ArrayList<String>)list; } }
用javac编译的话:
$ javac -Xlint zz.java zz.java:5: warning: [unchecked] unchecked cast return (ArrayList<String>)list; ^ required: ArrayList<String> found: ArrayList<CAP#1> where CAP#1 is a fresh type-variable: CAP#1 extends Object from capture of ? 1 warning
那么我们如果手上拿着一个声称自己是ArrayList<String> list的变量并且它指向的是一个非空的ArrayList,是否可以相信list.get(0)得到的一定是一个String呢?答案是:大多数情况下,只要这个list.get(0)能成功执行,就可以相信拿到的是一个String(orz因为如果不满足类型安全的要求的话,这个操作就会在运行时抛出异常。
看下面的反例:
import java.util.*; public class zz { public static ArrayList<String> foo(ArrayList<?> list) { return (ArrayList<String>)list; } public static Class<?> getElementClass(ArrayList<String> list) { Object elem = list.get(0); return elem.getClass(); } public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(123); Class<?> clz = getElementClass(foo((ArrayList) list)); System.out.println(clz); } }
这个例子用了多个不安全的转换,javac会指出最明显的一个:
$ javac -Xlint zz.java zz.java:5: warning: [unchecked] unchecked cast return (ArrayList<String>)list; ^ required: ArrayList<String> found: ArrayList<CAP#1> where CAP#1 is a fresh type-variable: CAP#1 extends Object from capture of ? 1 warning
然后运行:
$ java zz class java.lang.Integer
没报错!没抛异常!直接运行完了,我们看到一个ArrayList<String>的第0个元素的类型却是Integer。这是靠Java类型系统里任何引用类型都可以隐式转换为java.lang.Object类型引致的——由于这个转换总是必须成功,javac就不会在上面例子的第9行getElementClass里的list.get(0)处插入checkcast String指令,于是就丢失了一个运行时类型检查点。