构造函数类型参数放在类型之前是什么意思?

我最近遇到了这种不寻常的(对我来说)Java语法...下面是一个示例:

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

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

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

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

我已经搜索了通常的地方,例如。Angelika Langer和这里,但除了ANTLR项目Java语法文件中的语法规则外,在任何地方都找不到任何关于这种语法的提及。


答案 1

调用泛型构造函数

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

public class TypeWithGenericConstructor {

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

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

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

现在显然是.但是,在某些情况下,Java 可能无法推断(推断)类型参数。然后,我们使用您的问题中的语法显式提供它:TLocalDate

new <LocalDate>TypeWithGenericConstructor(null);

当然,我们也可以提供它,即使我们认为它有助于可读性或出于任何原因,这是不必要的:

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

在你的问题中,你似乎在调用构造函数。该构造函数不是泛型的(只有整个类才是,这是别的什么)。有关为什么Java允许您在不使用时在调用中提供类型参数,请参阅下面的编辑。我的 Eclipse 给了我一个警告java.util.ArrayListArrayList

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

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

泛型类与泛型构造函数

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

不,这是不同的。类型 () 后面的常用类型参数用于泛型类前面的类型参数用于构造函数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 中给出了这个理由:

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

该参数不适用于构造函数,因为它们不能被直接覆盖。但我想他们希望有相同的规则,以免使已经复杂的规则过于复杂。无论如何,关于实例化的第 15.9.3 节以及不止一次地引用了 15.12.2。new

链接


答案 2

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

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

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

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

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

对我来说似乎是一个编译器错误。