我试图理解两个带有一些初始化的构造函数,例如
CardboardContainer<String> n1 = new CardboardContainer<String>("Hello"); // In T constructor
CardboardContainer<String> n2 = new CardboardContainer<String>(3); // In T2 constructor
CardboardContainer<Integer> n22 = new CardboardContainer<Integer>(3); // In T constructor
CardboardContainer<String> n3 = new <Integer>CardboardContainer<String>(3); // In T2 constructor
我有一个像这样的通用类:
class CardboardContainer<T> {
T myField;
CardboardContainer(T myField) {
System.out.println("In T constructor");
this.myField = myField;
}
<T extends Number> CardboardContainer(T myField) {
System.out.println("In T2 constructor");
// this.myField = myField;
}
}
你已经通过遮盖
T
而把自己弄得七零八落了。这段代码是100%等效的,字节码(类文件)是逐位相同的,语义也完全相同:
class CardboardContainer<T> {
T myField;
CardboardContainer(T myField) {
System.out.println("In T constructor");
this.myField = myField;
}
<X extends Number> CardboardContainer(X myField) {
System.out.println("In X constructor");
// this.myField = myField;
}
}
我所做的就是在第二个构造函数中将
X
重命名为 T
。那是因为 它们是 - <T>
中的class CardboardContainer<T>
是声明类型变量(而T构造函数中的T
是使用这个声明的类型变量)。然而,这里的<X extends Number>
是also声明一个类型变量。您已声明 2 个变量 - 一个在类型上,另一个在构造函数 T2 上。为两者选择相同的名称是合法的,但这是一个极其愚蠢的想法,你绝对不应该这样做;除了混淆之外,它没有任何用处 - 在该方法的上下文中,您在 T2 构造函数上声明的 T
会“隐藏”该类型的 T - 每当您在该构造函数中的任何地方提到 T
时,都会假定它引用您在构造函数中声明的 T。但请不要误会,这是两个完全、完全独立的类型变量,没有任何关系。 因此,理解这些东西的第一步是停止跟踪 T 并使用我的片段,该片段已将其重命名为
X
。现在,我们可以理解事情了:
为什么 n1 使用第一个构造函数而不是第二个构造函数?因为第二个构造函数甚至无法使用。
new CardboardContainer<String>
部分将
T
设置为 String,这意味着第一个构造函数“有效”(它需要 T 参数;"Hello"
是一个 String,因此有效)。第二个构造函数无法工作:第一个参数必须是 X,调用者可以选择任何 X...只要调用者选择 extends Number
的类型。不存在 both[A]
extends Number
and[B] 允许
String
的类型。
为什么当我将 String 更改为 Integer 时,n22 使用第二个构造函数?因为在
n22
中,对
both构造函数的调用“有效” - 第一个构造函数有效,因为需要
Integer
,而 3
是一个(Integer
是必需的,因为您使用 new CardboardConstructor<Integer>
调用它,所以,T
是Inteer) - 第二个构造函数起作用,因为调用者必须选择某种类型,使得参数都是该类型,和该类型
extends Number
。整数作为一个选择。因此,可以使用任一构造函数,然后 lang 规范决定 java 选择一个构造函数的顺序。它选择第一个。为什么?没有任何理由——只是,必须要赢得一些东西。这不是随机的——规范说明在这种任意情况下第一个获胜。没有明确的理由说明为什么规范要这样写,我也想不出一个理由 - 可能没有一个。不得不在规范中写下编译器由于此处有不明确的调用而必须生成错误(尽管这通常是 java 解决此类困境的方式),这很烦人。这里根本不存在;这永远不会出现,因为这段代码太疯狂了,永远不应该被编写。即使改名了。这太混乱了,没有任何好处。
我认为n3会选择第一个构造函数。为什么选择第一个?您已将
<X>
强制变为
<Integer>
。这就是 new <A>Foo<B>();
的含义:通常对于方法上类型变量(或者在本例中,构造函数上 - X
中的 <X extends Number>
是构造函数上类型变量声明),编译器会在您随时选择正确的类型称呼它,只需看看你如何称呼它。但您可以自由地明确选择它们。例如:var x = List.<String>of();
// note, the signature of that method is:
public static <T> List<T> of(T... args) { ... }
如果您只写
var x = List.of()
,则
x
类型将为 List<Object>
,因为没有更多内容可继续。但是有了明确的 <String>
,x
的类型就是 List<String>
。您也可以在构造函数中显式选择泛型,这就是 <A>
部分的内容。您在这里明确选择了它,这会强制第二个构造函数,因为第一个构造函数没有需要明确的泛型。