泛型类型变量中的局部类型推断和逆变
Local type inference and contravariance in generic type variables
我遇到了以下代码:
public static <T> Set<T> distinct(
Collection<? extends T> list,
Comparator<? super T> comparator) {
Set<T> set = new TreeSet<>(comparator);
set.addAll(list);
return set;
}
此代码仅使用一个中间体 TreeSet
来删除重复项,其中元素之间的相等性是根据提供的比较器定义的。
让我们给本地类型推断一个机会,我(天真地)想...所以我将上面的代码改为:
public static <T> Set<T> distinct(
Collection<? extends T> list,
Comparator<? super T> comparator) {
var set = new TreeSet<>(comparator);
set.addAll(list);
return set;
}
这对我来说很有意义,因为 set
的类型可以从 comparator
的类型推断出来,我是这么认为的。但是,修改后的代码无法编译并生成以下错误:
java: incompatible types: java.util.TreeSet<capture#1 of ? super T> cannot be converted to java.util.Set<T>
现在明白为什么会报错了,我承认比较器的类型其实是Comparator<? super T>
,所以var
推断出的类型是TreeSet<? super T>
。
但是,我想知道 为什么 var
无法将 TreeSet
的通用类型推断为 T
而不是 ? super T
.毕竟,according to the docs,一个TreeSet<E>
有一个接受类型Comparator<? super E>
参数的构造函数。所以调用这个构造函数应该创建一个 TreeSet<E>
,而不是 TreeSet<? super E>
。 (这是第一个片段显示的内容)。我希望 var
遵循同样的逻辑。
注 1: 编译代码的一种方法是将 return 类型更改为 Set<? super T>
。但是,那将是一个几乎无法使用的集合...
注释 2: 另一种方法是不在比较器中使用逆变,但我不想这样,因为我无法使用 Comparator
比较 T
.
的祖先
注意 3: 我知道第一个片段有效,所以很明显我应该坚持不使用 var
并将集合明确声明为 Set<T>
。但是,我的问题不是我是否应该丢弃我的第二个片段或如何修复它。相反,我想知道为什么 var
没有将 TreeSet<T>
推断为我的第二个代码段中 set
局部变量的类型。
编辑 1: 在 this comment 中,用户 @nullpointer 正确指出我应该进行以下细微更改以使第二个代码段编译:
var set = new TreeSet<T>(comparator); // T brings in the magic!
现在泛型类型参数 T
对于 TreeSet
是显式的,因此 var
正确地将 set
局部变量的类型推断为 TreeSet<T>
。不过,我想知道为什么我必须明确指定 T
。
编辑 2: 在 this other comment 中,用户 @Holger 巧妙地提到以下语言是 forbidden:
var set = new TreeSet<? super T>(comparator);
以上代码编译失败,出现以下错误:
java: unexpected type
required: class or interface without bounds
found: ? super T
所以现在问题变得更加明显:如果我不能在实例化表达式 new TreeSet<? super T>(comparator)
中显式指定有界泛型类型 ? super T
,为什么编译器将 TreeSet<? super T>
推断为类型set
局部变量的?
根据 on ,他说:
Local variable type inference says: the types I need are probably already present on the right hand side, why repeat them on the left.
关于您问题中的代码,唯一可推断的类型(通过使用提供的 Comparator
)是 TreeSet<? super T>
。我们人类足够聪明,可以看到 distinct
returns set
并期待 Set<T>
。然而,编译器可能不够聪明,无法解决它(我相信它可以),但更有可能的是 var
使用 RHS 上提供的信息推断出最具体的类型,而架构师不想打破它。
现在,正如 nullpointer 在他们的评论中所述,您可以将 TreeSet
明确定义为 T
类型,而不是推断的捕获类型 ? super T
,如下所示:
var set = new TreeSet<T>(comparator);
我假设显式泛型类型会覆盖传递给构造函数的推断类型,这是有道理的。
JLS §14.4.1: Local Variable Declarators and Types 似乎支持我的说法,声明如下:
注意:"upward projection of T",它可能只是推断类型(TreeSet
而不是 Set
),但可能包括泛型也输入。
我认为这与 var list = List.<Number>of(1, 2, 3);
中的 list
是 List<Number>
而不是 List<Integer>
的原因相同,没有 var
也一样有效.
在第二个代码段中使用局部变量需要您明确指定 TreeSet
的范围,如 :
public static <T> Set<T> distinct(Collection<? extends T> list, Comparator<? super T> comparator) {
var set = new TreeSet<T>(comparator);
set.addAll(list);
return set;
}
原因是推断的 var 否则会使用与比较器一起使用的最明显的界限,并被推断为 TreeSet<? super T>
并且由于转换不兼容而导致声明的编译错误失败。
why I must specify T explicitly
正如雅各布所指出的那样反过来思考并将代码制定为
private static Set<Integer> distincts(Collection<? extends Integer> list, Comparator<Number> comparator) {
var set = new TreeSet<>(comparator);
// if you don't specify the bound, you get a compiler error on the return statement
// since the inferred type would be `Number`
set.addAll(list);
return set;
}
简单地反问一下,对于TreeSet
,您希望此处默认推断出什么类型? Integer
、Byte
哪一个来自 Number
的子类列表以及如何(基于 return 类型,这可能会在很晚的时候推断出来)?
Edit:- 我确实认为给定构造函数 TreeSet(Comparator<? super E> comparator)
constructs a TreeSet<E>
, hence invocation of such constructor should be inferred as TreeSet<E>
instead of TreeSet<? super E>
. Also, as , not everything could be inferred and for any such specific types, one could ask for it(assuming on the jdk mailing list).
我遇到了以下代码:
public static <T> Set<T> distinct(
Collection<? extends T> list,
Comparator<? super T> comparator) {
Set<T> set = new TreeSet<>(comparator);
set.addAll(list);
return set;
}
此代码仅使用一个中间体 TreeSet
来删除重复项,其中元素之间的相等性是根据提供的比较器定义的。
让我们给本地类型推断一个机会,我(天真地)想...所以我将上面的代码改为:
public static <T> Set<T> distinct(
Collection<? extends T> list,
Comparator<? super T> comparator) {
var set = new TreeSet<>(comparator);
set.addAll(list);
return set;
}
这对我来说很有意义,因为 set
的类型可以从 comparator
的类型推断出来,我是这么认为的。但是,修改后的代码无法编译并生成以下错误:
java: incompatible types: java.util.TreeSet<capture#1 of ? super T> cannot be converted to java.util.Set<T>
现在明白为什么会报错了,我承认比较器的类型其实是Comparator<? super T>
,所以var
推断出的类型是TreeSet<? super T>
。
但是,我想知道 为什么 var
无法将 TreeSet
的通用类型推断为 T
而不是 ? super T
.毕竟,according to the docs,一个TreeSet<E>
有一个接受类型Comparator<? super E>
参数的构造函数。所以调用这个构造函数应该创建一个 TreeSet<E>
,而不是 TreeSet<? super E>
。 (这是第一个片段显示的内容)。我希望 var
遵循同样的逻辑。
注 1: 编译代码的一种方法是将 return 类型更改为 Set<? super T>
。但是,那将是一个几乎无法使用的集合...
注释 2: 另一种方法是不在比较器中使用逆变,但我不想这样,因为我无法使用 Comparator
比较 T
.
注意 3: 我知道第一个片段有效,所以很明显我应该坚持不使用 var
并将集合明确声明为 Set<T>
。但是,我的问题不是我是否应该丢弃我的第二个片段或如何修复它。相反,我想知道为什么 var
没有将 TreeSet<T>
推断为我的第二个代码段中 set
局部变量的类型。
编辑 1: 在 this comment 中,用户 @nullpointer 正确指出我应该进行以下细微更改以使第二个代码段编译:
var set = new TreeSet<T>(comparator); // T brings in the magic!
现在泛型类型参数 T
对于 TreeSet
是显式的,因此 var
正确地将 set
局部变量的类型推断为 TreeSet<T>
。不过,我想知道为什么我必须明确指定 T
。
编辑 2: 在 this other comment 中,用户 @Holger 巧妙地提到以下语言是 forbidden:
var set = new TreeSet<? super T>(comparator);
以上代码编译失败,出现以下错误:
java: unexpected type
required: class or interface without bounds
found: ? super T
所以现在问题变得更加明显:如果我不能在实例化表达式 new TreeSet<? super T>(comparator)
中显式指定有界泛型类型 ? super T
,为什么编译器将 TreeSet<? super T>
推断为类型set
局部变量的?
根据
Local variable type inference says: the types I need are probably already present on the right hand side, why repeat them on the left.
关于您问题中的代码,唯一可推断的类型(通过使用提供的 Comparator
)是 TreeSet<? super T>
。我们人类足够聪明,可以看到 distinct
returns set
并期待 Set<T>
。然而,编译器可能不够聪明,无法解决它(我相信它可以),但更有可能的是 var
使用 RHS 上提供的信息推断出最具体的类型,而架构师不想打破它。
现在,正如 nullpointer 在他们的评论中所述,您可以将 TreeSet
明确定义为 T
类型,而不是推断的捕获类型 ? super T
,如下所示:
var set = new TreeSet<T>(comparator);
我假设显式泛型类型会覆盖传递给构造函数的推断类型,这是有道理的。
JLS §14.4.1: Local Variable Declarators and Types 似乎支持我的说法,声明如下:
注意:"upward projection of T",它可能只是推断类型(TreeSet
而不是 Set
),但可能包括泛型也输入。
我认为这与 var list = List.<Number>of(1, 2, 3);
中的 list
是 List<Number>
而不是 List<Integer>
的原因相同,没有 var
也一样有效.
在第二个代码段中使用局部变量需要您明确指定 TreeSet
的范围,如 :
public static <T> Set<T> distinct(Collection<? extends T> list, Comparator<? super T> comparator) {
var set = new TreeSet<T>(comparator);
set.addAll(list);
return set;
}
原因是推断的 var 否则会使用与比较器一起使用的最明显的界限,并被推断为 TreeSet<? super T>
并且由于转换不兼容而导致声明的编译错误失败。
why I must specify T explicitly
正如雅各布所指出的那样反过来思考并将代码制定为
private static Set<Integer> distincts(Collection<? extends Integer> list, Comparator<Number> comparator) {
var set = new TreeSet<>(comparator);
// if you don't specify the bound, you get a compiler error on the return statement
// since the inferred type would be `Number`
set.addAll(list);
return set;
}
简单地反问一下,对于TreeSet
,您希望此处默认推断出什么类型? Integer
、Byte
哪一个来自 Number
的子类列表以及如何(基于 return 类型,这可能会在很晚的时候推断出来)?
Edit:- 我确实认为给定构造函数 TreeSet(Comparator<? super E> comparator)
constructs a TreeSet<E>
, hence invocation of such constructor should be inferred as TreeSet<E>
instead of TreeSet<? super E>
. Also, as