在所有方法调用中允许类型见证的重点是什么?

假设我们有两种方法,如下所示:

public static  T genericReturn() { /*...*/ } public static String stringReturn() { /*...*/ } 

在调用任何方法时,无论是否有任何要求,您都可以提供类型见证:

 String s; s = Internet.genericReturn(); //Type witness used in return type, returns a String s = Internet.stringReturn(); //Type witness ignored, returns a String 

但是我根本没有在Java中看到任何实际用途,除非无法推断出这种类型(这通常表明存在更大的问题)。 另外,当它没有被适当地使用时它被简单地忽略这一事实似乎违反直觉。 那么在Java中有什么意义呢?

从JLS§15.2.12.1开始 :

  • 如果方法调用包含显式类型参数,并且成员是generics方法,则类型参数的数量等于方法的类型参数的数量。

此子句意味着非generics方法可能适用于提供显式类型参数的调用。 实际上,它可能适用。 在这种情况下,类型参数将被忽略。

接着是理由

该规则源于兼容性和可替代性原则的问题。 由于接口或超类可以独立于其子类型进行生成,因此我们可以使用非generics方法覆盖generics方法。 但是,重写(非generics)方法必须适用于对generics方法的调用,包括显式传递类型参数的调用。 否则,子类型不能替代其生成的超类型。

沿着这条推理,让我们构建一个例子。 假设在Java1.4中,JDK有一个类

 public class Foo { /** check obj, and return it */ public Object check(Object obj){ ... } } 

一些用户编写了一个扩展Foo的专有类,并覆盖了check方法

 public class MyFoo extends Foo { public Object check(Object obj){ ... } } 

当Java1.5引入generics时, Foo.check被广泛称为

  public  T check(T obj) 

雄心勃勃的后向可比性目标要求MyFoo仍然在Java1.5中编译而不进行修改; 和MyFoo.check[Object->Object]仍然是Foo.check[T->T]的重写方法。

现在,根据上述理由,自编译以来

  MyFoo myFoo = new MyFoo(); ((Foo)myFoo).check(""); 

这也必须编译

  myFoo.check(""); 

即使MyFoo.check不是通用的。


这听起来像一个延伸。 但即使我们购买这个论点,解决方案仍然过于宽泛和过度。 JLS可能会收紧它,因此myFoo.checkobj.toString()是非法的,因为类型参数arity不匹配。 他们可能没有时间去解决它,所以他们只是走了一条简单的路线。

当类型推断不起作用时,您需要类型见证(菱形中的类型)(请参阅http://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html )

给出的示例是菊花链式调用,如:

 processStringList(Collections.emptyList()); 

其中processStringList定义为:

 void processStringList(List stringList) { // process stringList } 

这将导致错误,因为它无法将List转换为List 。 因此,证人是必需的。 尽管如此,您可以通过多个步骤完成此操作,但这可以更加方便。

这是因为向后兼容和/或向前兼容性(在源级别)。

想象一下类似于在JDK 7中为Swing中的某些类引入generics参数。它也可能与方法一起发生(即使有限制)。 如果事情结果是个坏主意,你可以删除它们,使用它的源代码仍会编译。 在我看来,这就是为什么允许这样做的原因,即使它没有被使用。

虽然灵活性有限。 如果引入了n种类型的类型参数,如果m != 0m != n ,则以后不能更改为m种类型(以源兼容的方式)。

(我知道这可能不会回答你的问题,因为我不是Java的设计者,这只是我的想法/意见。)

想知道为什么“Type Witness”在Java中被抛出? :d

为了理解这一点,我们应该从Type Inference开始讲故事。

类型推断是Java编译器查看每个方法调用和相应声明的能力,以确定使调用适用的类型参数(或参数)。 推理算法确定参数的类型,如果可用,还确定分配或返回结果的类型 。 最后,推理算法尝试查找适用于所有参数的最具体类型。

如果上述算法仍然无法确定类型,我们有“Type Witness”来明确说明我们需要什么类型。 例如:

 public class TypeWitnessTest { public static void main(String[] args) { print(Collections.emptyList()); } static void print(List list) { System.out.println(list); } } 

上面的代码没有编译:

 TypeWitnessTest.java:11: error: method print in class TypeWitnessTest cannot be applied to given types; print(Collections.emptyList()); ^ required: List found: List reason: actual argument List cannot be converted to List by method invocation conversion 1 error 

所以,你有Type Witness来拯救这个:

 public class TypeWitnessTest { public static void main(String[] args) { print(Collections.emptyList()); } static void print(List list) { System.out.println(list); } } 

这是可编译的并且工作正常,但是在Java 8已经得到了更多改进:
JEP 101:广义目标类型推断

PS:我从基础开始,以便其他StackOverflow读者也可以受益。

编辑

在非一般见证人上输入见证人!

 public class InternetTest { public static void main(String[] args) { String s; s = Internet.genericReturn(); //Witness used in return type, returns a string s = Internet.stringReturn(); //Witness ignored, returns a string } } class Internet { public static  T genericReturn() { return null; } public static String stringReturn() { return null; } } 

我尝试使用javac 1.6.0_65来模拟@Rogue示例,但是编译失败并出现以下错误:

 javac InternetTest.java InternetTest.java:5: stringReturn() in Internet cannot be applied to () s = Internet.stringReturn(); //Witness ignored, returns a string ^ 1 error 

@Rogue:如果您使用的是先前版本,请告诉我您的javac版本。 如果你当时就不允许了。 :P