当把 * 放在 * 类型之前时,构造函数类型参数意味着什么?

我最近遇到了这种不寻常的 Java 语法... 下面是一个例子:

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

注意 <String, Long>类型参数的位置... 它不是在类型之后,而是在之前。我不介意承认我以前从未见过这种语法。还要注意,当 ArrayList只有1个类型参数时,有2个类型参数。

类型参数的定位是否与将它们放在类型之后具有相同的含义?如果不是,那么不同的定位意味着什么?

ArrayList只有1个类型参数时,为什么有2个类型参数是合法的?

我找遍了所有常去的地方。除了 ANTLR 项目的 Java 语法文件中的语法规则之外,Angelika Langer 等人在这里没有找到任何提到这种语法的地方。

7023 次浏览

调用泛型构造函数

这是不寻常的好,但完全有效的 Java。为了理解,我们需要知道一个类可能有一个泛型构造函数,例如:

public class TypeWithGenericConstructor {


public <T> TypeWithGenericConstructor(T arg) {
// TODO Auto-generated constructor stub
}
    

}

我认为在通过泛型构造函数实例化类时,通常不需要显式化类型参数。例如:

new TypeWithGenericConstructor(LocalDate.now(ZoneId.systemDefault()));

现在 T显然是 LocalDate。然而,可能存在 Java 无法推断(推断)类型参数的情况。然后我们使用你问题中的句法明确地提供它:

new <LocalDate>TypeWithGenericConstructor(null);

当然,如果我们认为它有助于提高可读性或出于某种原因,我们也可以提供它,即使它并不是必要的:

new <LocalDate>TypeWithGenericConstructor(LocalDate.now(ZoneId.systemDefault()));

在您的问题中,您似乎在调用 java.util.ArrayList构造函数。该构造函数不是泛型的(只有 ArrayList类作为一个整体是泛型的,这是另外一回事)。至于为什么 Java 允许在调用中提供类型参数,请参阅下面的编辑。我的 Eclipse 给了我一个 警告:

的非泛型构造函数 ArrayList ()的未使用类型参数 类型 ArrayList; 它不应该用参数 < String, 长 >

但是这不是一个错误,而且程序运行良好(我还收到了关于 ListArrayList缺少类型参数的警告,但这又是另一回事了)。

泛型类与泛型构造函数

类型参数的定位是否与 把他们放在类型之后? 如果不是,有什么不同 定位意味着什么?

不,这不一样。通常的类型参数/s 之后类型(ArrayList<Integer>())用于 泛型类。类型参数 之前用于 构造函数

这两种形式也可合并使用:

List<Integer> list = new <String, Long>ArrayList<Integer>();

我认为这更正确一些,因为我们现在可以看到列表存储 Integer对象(当然,我仍然希望省略无意义的 <String, Long>)。

当 ArrayList 只有1个类型参数时,为什么有2个类型参数是合法的?

首先,如果在类型之前提供类型参数,那么应该为构造函数提供正确的数字,而不是为类提供正确的数字,因此它与 ArrayList类拥有多少类型参数没有任何关系。这实际上意味着在这种情况下,您不应该提供任何类型的参数,因为构造函数不接受类型参数(它不是泛型)。当你无论如何提供了一些,他们被忽略,这就是为什么它不重要,多少或多少你供应。

为什么允许无意义的类型参数?

编辑链接时要感谢@Slaw: Java 允许在所有方法调用上使用类型参数。如果调用的方法是泛型的,则使用类型参数; 如果不是,则忽略它们。例如:

int length = "My string".<List>length();

是的,这是荒谬的。 Java 语言规范(JLS)在第15.12.2.1节中给出了这个理由:

这个规则源于兼容性问题和可替代性原则。因为接口或超类可以被泛化 独立于它们的子类型,我们可以用 但是,重写(非泛型)方法必须 适用于对泛型方法的调用,包括调用 显式地传递类型参数。否则子类型将不是 可替换为它的泛化超类型

该参数不适用于构造函数,因为它们不能被直接重写。但我认为他们希望有同样的规则,以便不使已经复杂的规则过于复杂。在任何情况下,关于实例化和 new的第15.9.3节多次引用15.12.2。

连结

显然,你可以在任何非泛型方法/构造函数前面加上任何你喜欢的泛型参数:

new <Long>String();
Thread.currentThread().<Long>getName();

编译器不在乎,因为它不必将这些类型参数与实际的泛型参数匹配。

一旦编译器必须检查参数,它就会抱怨不匹配:

Collections.<String, Long>singleton("A"); // does not compile

我觉得像是编译器的错误。