我一直在阅读泛型方法,并且认为我理解泛型类型参数将如何约束方法参数类型,但是当我用实际代码测试了一些想法时,我得到了意想不到的结果。这是一个我不理解的简单通用方法:
private static <T> void foo(T[] t1, T[] t2){
t2[0] = t1[0];
}
...
String[] stringArray = new String[]{"1", "2", "3"};
Integer[] integerArray = new Integer[]{4,5,6};
foo(stringArray, integerArray);
我原本认为这个泛型方法受到约束,因此两个数组必须是相同类型的T,但实际上上面的代码编译得很好,即使一个数组是String类型而另一个数组是输入整数。程序运行时,会生成运行时异常(ArrayStoreException)。
答案 0 :(得分:1)
在此示例中,推断类型为? extends Object[]
,适合两种类型。
为了达到你想要的效果,你需要:
private static <T> void foo(Class<T> clazz, T[] t1, T[] t2);
然后
foo(String.class, stringArray, stringArray); // compiles
foo(String.class, stringArray, integerArray); // fails
答案 1 :(得分:1)
关于Java的一个不为人知的事实是String[]
是Object[]
的子类型(与List<String>
不是List<Object>
的子类型相反) 。因此,编译器可以推断出T
= Object
,从而制作方法签名
foo(Object[] t1, Object[] t2)
可以使用foo(stringArray, integerArray)
调用。
如果您对列表进行相同操作:
<T> void foo(List<T> t1, List<T> t2) { ... }
你会发现
foo(new ArrayList<String>(), new ArrayList<Integer>())
无法编译 - 因为没有T
,List<String>
和List<Integer>
都是List<T>
的子类型。但是,如果您使用通配符类型来声明方法:
void foo(List<?> t1, List<?> t2) { ... }
可以调用该方法(但是方法体不会编译,因为编译器知道?
可能引用了不兼容的类型。)
答案 2 :(得分:1)
正如@Bozho已经证明问题可以解决,但是为了证明正在发生的事情,请考虑以下代码:
public class Main {
// This is the original version that fails because of type erasure in arrays
private static <T> void foo(T[] t1, T[] t2) {
t2[0] = t1[0];
}
// The same method as foo() but with the type erasure demonstrated
private static void foo2(Object[] t1, Object[] t2) {
// Integer[] should not contain String
t2[0] = t1[0];
}
public static void main(String[] args) {
String[] stringArray = new String[]{"1", "2", "3"};
Integer[] integerArray = new Integer[]{4, 5, 6};
foo2(stringArray, integerArray);
}
}
这与Java中的数组是协变的事实有关,而泛型却不是。 There is an interesting article about it here。快速报价说明了一切:
Java语言中的数组是 协变 - 这意味着如果 整数扩展数字(它 确实如此),那么不仅是整数 也是一个数字,但是整数[]是 也是一个数字[],你可以自由 传递或分配一个Integer [],其中a 需要Number []。 (更多 正式地,如果Number是超类型 整数,然后Number []是一个超类型 整数[]。)你可能会认为 通用类型也是如此 - List是List的超类型,你可以传递一个 列出List的位置 预期。不幸的是,事实并非如此 这样工作。
事实证明这是有充分理由的 不这样做:它会破裂 假设类型安全仿制药 提供。想象一下,你可以分配一个 列表到列表。然后 以下代码将允许您 放一些不是整数的东西 进入清单:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
因为ln是List,所以添加Float似乎是完全合法的。但是如果ln与li混淆,那么它将破坏li定义中隐含的类型安全承诺 - 它是一个整数列表,这就是泛型类型不能协变的原因。
答案 3 :(得分:0)
编译器已经为您推断出类型T,并且可能选择了Object
。
您可以根据需要使用以下语法指定类型:
MyClass.<String>foo(stringArray, integerArray); // Compiler error.
组合数组和泛型是普遍可怕的。举例来说,我介绍了Angelika Langer的泛型常见问题:http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html