可能不应该在这种问题上死磕,但是还是想不明白:
代码来自《Effective Java》 第三版,32章
贴代码:
static <T> T[] toArray(T... args) {
return args;
}
static <T> T[] pickTwo(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArray(a, b);
case 1: return toArray(a, c);
case 2: return toArray(b, c);
}
throw new AssertionError(); // Can't get here
}
public static void main(String[] args) {
String[] attributes = pickTwo("Good", "Fast", "Cheap");
}
程序能够顺利通过编译,但是运行会报错。
报错原因为:pickTwo返回的类型为Objcet[]
类型,因此无法强制转换为String[]
类型。
但是:
Objcet[] obj=new String[1]();
String[] str=(String[])obj;
是能正确运行的。
按照擦除和编译的语法糖,代码应该被编译成这样:
public static void main(String[] args) {
String[] attributes = pickTwo("Good", "Fast", "Cheap");
}
static String[] pickTwo(Object a, Object b, Object c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return (String[])toArray(a, b);
case 1: return (String[])toArray(a, c);
case 2: return (String[])toArray(b, c);
}
throw new AssertionError(); // Can't get here
}
static String[] toArray(Object... args) {
return (String[])args;
}
这也是泛型出现的意义。
可是static <T> T[] pickTwo(T a, T b, T c) {
pickTwo明明根据传入类型告诉了编译器,需要返回T[]类型。
因此传入String
类型的代码不应该就返回String[]
么
可是书上和运行结果确实证明了pickTwo
方法确实始终返回了Objcet
类型。
这是因为泛型数组的特殊性么?是在想不通,望指点
因为java是不允许创建泛型数组的啊。。。先假设Java可以创建泛型数组,由于java泛型的类型擦除和数组的协变。下面的代码将会编译通过。
List[] stringLists=new List[1];
List intList = Arrays.asList(40);
Object[] objects = stringLists;
objects[0]=intList;
String s=stringLists[0].get(0);
由于泛型的类型擦除,List<Integer>,List<String>与List在运行期并没有区别,所以List<String>放入List<Integer>并不会产生ArrayStoreException异常。但是String s=stringLists[0].get(0);将会抛出ClassCastException异常。如果允许创建泛型数组,就绕过了泛型的编译时的类型检查,将List<Integer>放入List<String>[],并在实际存的是Integer的对象转为String时抛出异常。
如果泛型没有限制类型。比如List s=new ArrayList[5];或者List<?>[] s=new ArrayList[5];还是可以使用的,应为没有了编译时的类型检查,需要开发者自己保证类型转换安全。
你可以把String[]想象成Array<String>,而Object[]想象成Array<Object>,虽然String是Object的派生类,但是Array<String>不是Array<Object>的派生类。
然后你可能要问,为什么Java不设计成Array<String>是Array<Object>的派生类(所谓的协变),这是出于性能的考虑。如果要这么设计,那么Java不得不放弃为了性能而使用的类型擦除机制了,因为程序需要在运行时允许做类型转换,这样整个jvm就推翻了。不过不要以为这么做不合理,C#就可以使现有条件的协变(接口),而动态语言更不在话下了。