java泛型方法如何约束方法类型参数?

时间:2010-11-09 21:56:38

标签: java generics

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

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)。

4 个答案:

答案 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>())

无法编译 - 因为没有TList<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