允许所有方法调用类型见证的意义何在?

2022-09-01 17:37:23

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

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

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

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

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


答案 1

根据 JLS §15.2.12.1

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

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

其次是正当理由

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

沿着这条推理路线,让我们构建一个示例。假设在Java 1.4中,JDK有一个类

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

某些用户编写了一个专有类,用于扩展和重写该方法Foocheck

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

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

    public <T> T check(T obj)

雄心勃勃的向后可比性目标要求仍然在Java 1.5中编译而无需修改;并且仍然是 的重写方法。MyFooMyFoo.check[Object->Object]Foo.check[T->T]

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

    MyFoo myFoo = new MyFoo();

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

这也必须编译:

    myFoo.<String>check("");

即使不是通用的。MyFoo.check


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


答案 2

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

为此给出的示例是当菊花链调用时,例如:

processStringList(Collections.emptyList());

其中 processStringList 定义为:

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

这将导致错误,因为它无法将 a 强制转换为 .因此,证人是必需的。虽然,您可以通过多个步骤执行此操作,但这可以方便得多。List<Object>List<String>


推荐