2010-11-09 75 views
3

我一直在阅读有关泛型方法,并认为我理解泛型类型参数如何约束方法参数类型,但是当我用实际代码测试了一些想法时,我得到了意想不到的结果。下面是我不明白一个简单的通用方法:java通用方法如何约束方法类型参数?

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而另一个数组的类型是Integer,编译也很好。程序运行时,会生成运行时异常(ArrayStoreException)。

+0

我不是java专家,但我猜分析器不能告诉foo()接受2个类型T [] s必须是相同的。它看到String []是一个T []类型,Integer []也是一个T []类型。 T应该使用哪一个? – Falmarri 2010-11-09 22:02:09

+0

很好的回答。做得好的SO'ers。 – CurtainDog 2010-11-10 01:07:50

+0

@CurtainDog非常感谢我们尽力而为;-) – 2010-11-10 09:13:05

回答

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

一个不那么众所周知的事实是,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) { ... } 

的方法可以调用(但方法体不会编译,因为编译器知道?可参考不兼容的类型)。

+0

字符串扩展对象是一个“不太知名的事实”? – EJP 2010-11-10 01:32:34

+0

数组的协变是令人惊讶的 - 毕竟它违反了Liskov的替换原则(当失败时,存在运行时检查会抛出ArrayStoreException),与泛型类型的不变性形成鲜明对比。 – meriton 2010-11-10 18:01:56

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扩展号(它 如此),那么不仅是一个整数 也有不少,但Integer []是 也是Number [],并且您可以通过 pass或指定一个Integer []来调用 Number []。 (更多 正式,如果Number是超类型 整型,则Number []是Integer []的超类型 )。您可能认为 同样适用于泛型类型以及 - 该List是超类型列表,并且您可以通过 列表,其中列表为 。不幸的是,它不是 这样工作。

事实证明,它有一个很好的理由它 不会这样工作:它会打破 类型安全仿制药应该提供 提供。想象一下,您可以将一个 列表分配给列表。然后 下面的代码将让你 放东西,是不是一个整数 到列表:

List<Integer> li = new ArrayList<Integer>(); 
List<Number> ln = li; // illegal 
ln.add(new Float(3.1415)); 

因为LN是一个列表,添加一个Float似乎完全合法的。但是,如果ln与li混叠在一起,那么它将打破li的定义中隐含的类型安全承诺 - 它是一个整数列表,这就是为什么泛型不能协变的原因。