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

时间:2015-01-18 21:12:12

标签: java generics

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

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

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

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

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

4 个答案:

答案 0 :(得分:20)

来自JLS §15.2.12.1

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

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

接下来是理由

  

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

沿着这条推理,让我们构建一个例子。假设在Java 1.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){ ... }
}

当Java 1.5引入泛型时,Foo.check被归为

    public <T> T check(T obj)

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

现在,根据上述理由,由于这个编译:

    MyFoo myFoo = new MyFoo();

    ((Foo)myFoo).<String>check("");

这也必须编译:

    myFoo.<String>check("");

即使MyFoo.check不是通用的。


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

答案 1 :(得分:4)

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

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

processStringList(Collections.emptyList());

其中processStringList定义为:

void processStringList(List<String> stringList) 
{
    // process stringList
}

这会导致错误,因为它无法将List<Object>转换为List<String>。因此,证人是必需的。虽然,你可以分多步完成,但这可以更方便。

答案 2 :(得分:2)

想知道为什么&#34; Type Witness&#34;在Java中被扔了? :d

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

  

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

如果上述算法仍然无法确定我们的类型&#34; Type Witness&#34;明确说明我们需要什么类型。例如:

public class TypeWitnessTest {

    public static void main(String[] args) {
        print(Collections.emptyList());
    }

    static void print(List<String> 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<String>
  found: List<Object>
  reason: actual argument List<Object> cannot be converted to List<String> by method invocation conversion
1 error

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

public class TypeWitnessTest {

    public static void main(String[] args) {
        print(Collections.<String>emptyList());
    }

    static void print(List<String> list) {
        System.out.println(list);
    }
}

这是可编辑的并且工作正常,但Java 8中的情况有所改善 JEP 101: Generalized Target-Type Inference

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

修改

关于非一般见证的

类型见证

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

class Internet {
    public static <T> 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 <java.lang.Integer>()
        s = Internet.<Integer>stringReturn(); //Witness ignored, returns a string
                    ^
1 error

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

答案 3 :(得分:2)

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

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

虽然灵活性有限。如果您使用n类型引入了类型参数,则以后{。}}或m无法更改为m != 0类型(以源兼容的方式)。

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