为什么这个通用代码在java 8中编译?

我偶然发现了一段代码,让我想知道为什么编译成功:

public class Main { public static void main(String[] args) { String s = newList(); // why does this line compile? System.out.println(s); } private static <T extends List<Integer>> T newList() { return (T) new ArrayList<Integer>(); } } 

有趣的是,如果我用<T extends ArrayList<Integer>>修改newList方法的签名,它就不再工作了。

注释和响应之后更新:如果将genericstypes从方法移动到类中,代码将不再编译:

 public class SomeClass<T extends List<Integer>> { public void main(String[] args) { String s = newList(); // this doesn't compile anymore System.out.println(s); } private T newList() { return (T) new ArrayList<Integer>(); } } 

如果你在一个方法中声明了一个types参数,你可以让调用者为它select一个实际的types,只要这个实际的types符合约束。 这个types不一定是一个实际的具体types,它可能是一个抽象types,一个typesvariables或一个交集types,在其他更通俗的话中是一个假设types。 所以, 正如Mureinik所说 ,可能有一个扩展String和实现List的types。 我们不能为调用手动指定交集types,但我们可以使用typesvariables来演示逻辑:

 public class Main { public static <X extends String&List<Integer>> void main(String[] args) { String s = Main.<X>newList(); System.out.println(s); } private static <T extends List<Integer>> T newList() { return (T) new ArrayList<Integer>(); } } 

当然, newList()不能满足返回这种types的期望,但是这是这个方法的定义(或实现)的问题。 在将ArrayList投射到T时,您应该得到一个“未经检查”的警告。 唯一可能的正确的实现将在这里返回null ,这使得方法相当无用。

重复初始语句的要点是,generics方法的调用者为types参数select实际types。 相比之下,当你声明一个generics

 public class SomeClass<T extends List<Integer>> { public void main(String[] args) { String s = newList(); // this doesn't compile anymore System.out.println(s); } private T newList() { return (T) new ArrayList<Integer>(); } } 

types参数是类的合同的一部分,所以无论谁创build一个实例将select实例的实际types。 实例方法main是该类的一部分,必须遵守该合同。 你不能select你想要的T ; T的实际types已经设置好了,在Java中,你通常甚至无法知道T是什么。

generics编程的关键是编写独立于为types参数select实际types的代码。

但是请注意,你可以用你喜欢的任何types创build另一个独立的实例,并调用这个方法,例如

 public class SomeClass<T extends List<Integer>> { public <X extends String&List<Integer>> void main(String[] args) { String s = new SomeClass<X>().newList(); System.out.println(s); } private T newList() { return (T) new ArrayList<Integer>(); } } 

在这里,新实例的创build者将select该实例的实际types。 如上所述,实际types不需要是具体的types。

我猜这是因为List是一个接口。 如果我们忽略了Stringfinal这个事实,理论上你可以有一个extends String的类(意味着你可以将它赋值给s ),但是implements List<Integer> (意味着它可以从newList() )。 一旦你将一个接口( T extends List )的返回types改为一个具体类( T extends ArrayList ),编译器就可以推断出它们不能相互赋值,并产生一个错误。

当然,这是因为String实际上是final ,所以我们可以期望编译器考虑到这一点。 恕我直言,这是一个错误,但我必须承认,我不是编译器专家,可能有一个很好的理由忽略final修改器在这一点上。

我不知道为什么这个编译。 另一方面,我可以解释如何充分利用编译时检查。

所以, newList()是一个通用的方法,它有一个types参数。 如果你指定这个参数,那么编译器会为你检查:

无法编译:

 String s = Main.<String>newList(); // this doesn't compile anymore System.out.println(s); 

通过编译步骤:

 List<Integer> l = Main.<ArrayList<Integer>>newList(); // this compiles and works well System.out.println(l); 

指定types参数

types参数只提供编译时检查。 这是devise,Java使用types擦除的genericstypes。 为了使编译器为你工作,你必须在代码中指定这些types。

在实例创build时input参数

最常见的情况是指定对象实例的模式。 即列表:

 List<String> list = new ArrayList<>(); 

在这里我们可以看到List<String>指定了列表项的types。 另一方面,新的ArrayList<>()没有。 它使用钻石运算符来代替。 也就是说,java编译器根据声明推断出types。

方法调用的隐式types参数

当你调用一个静态方法时,你必须以另一种方式指定types。 有时你可以指定它作为参数:

 public static <T extends Number> T max(T n1, T n2) { if (n1.doubleValue() < n2.doubleValue()) { return n2; } return n1; } 

你可以像这样使用它:

 int max = max(3, 4); // implicit param type: Integer 

或者像这样:

 double max2 = max(3.0, 4.0); // implicit param type: Double 

方法调用的显式types参数:

举个例子,这是你如何创build一个types安全的空列表:

 List<Integer> noIntegers = Collections.<Integer>emptyList(); 

types参数<Integer>传递给方法emptyList() 。 唯一的限制是你也必须指定类。 即你不能这样做:

 import static java.util.Collections.emptyList; ... List<Integer> noIntegers = <Integer>emptyList(); // this won't compile 

运行时types标记

如果这些技巧都不能帮助你,那么你可以指定一个运行时types标记 。 即你提供一个类作为参数。 一个常见的例子是EnumMap :

 private static enum Letters {A, B, C}; // dummy enum ... public static void main(String[] args) { Map<Letters, Integer> map = new EnumMap<>(Letters.class); }