Ric*_*ick 4 java generics type-inference language-lawyer java-stream
我有以下简化的示例,以TreeMap从Integer到List的形式将字符串列表分组为类别
public static void main(String[] args)
{
List<String> list = Arrays.asList("A", "B", "C", "D", "E");
TreeMap<Integer, List<String>> res = list.stream()
.collect(Collectors.groupingBy(
s -> s.charAt(0) % 3,
() -> new TreeMap<>(Comparator.<Integer>reverseOrder()), // Type required
Collectors.toList()
));
System.out.println(res);
}
Run Code Online (Sandbox Code Playgroud)
如果我未指定Comparator.reverseOrder()的类型,则代码将无法编译(有关错误,请参见文章底部)。
如果我明确指定TreeMap的类型而不是Comparator.reverseOrder()的类型,则代码可以正常工作。
() -> new TreeMap<Integer, List<String>>(Comparator.reverseOrder()), // Type required
Run Code Online (Sandbox Code Playgroud)
所以:
我不明白为什么编译器不能同时推断两种类型。我已经使用Oracle的JDK 1.8.0_191和AdoptOpenJDK的JDK 11.0.1_13进行了测试,结果相同。
这是我不知道的一些限制吗?
Error:(22, 32) java: no suitable method found for groupingBy((s)->s.cha[...]) % 3,()->new Tr[...]er()),java.util.stream.Collector<java.lang.Object,capture#1 of ?,java.util.List<java.lang.Object>>)
method java.util.stream.Collectors.<T,K>groupingBy(java.util.function.Function<? super T,? extends K>) is not applicable
(cannot infer type-variable(s) T,K
(actual and formal argument lists differ in length))
method java.util.stream.Collectors.<T,K,A,D>groupingBy(java.util.function.Function<? super T,? extends K>,java.util.stream.Collector<? super T,A,D>) is not applicable
(cannot infer type-variable(s) T,K,A,D
(actual and formal argument lists differ in length))
method java.util.stream.Collectors.<T,K,D,A,M>groupingBy(java.util.function.Function<? super T,? extends K>,java.util.function.Supplier<M>,java.util.stream.Collector<? super T,A,D>) is not applicable
(inferred type does not conform to upper bound(s)
inferred: java.lang.Object
upper bound(s): java.lang.Comparable<? super T>,T,java.lang.Object)
Run Code Online (Sandbox Code Playgroud)
不幸的是,类型推断具有非常复杂的规范,这使得很难确定特定的奇怪行为是符合规范还是仅是编译器错误。
类型推断有两个众所周知的故意限制。
首先,表达式的目标类型不用于接收方表达式,即方法调用链中。所以当你有一个形式的声明
TargetType x = first.second(…).third(…);
Run Code Online (Sandbox Code Playgroud)
在TargetType将使用推断通用类型的third()调用和它的参数表现,而不是second(…)调用。因此,类型推断second(…)只能使用和的独立类型first。
这不是问题。由于独立型list的好定义为List<String>,存在推断的结果类型没问题Stream<String>的stream()电话,有问题的collect调用链,可以使用目标类型的最后一个方法调用TreeMap<Integer, List<String>>推断类型参数。
第二个限制是关于过载解析。语言设计人员在涉及参数表达的不完整类型之间的循环依赖时,故意进行了削减,这些不完整类型的参数表达式需要先了解实际的目标方法及其类型,然后才能帮助确定正确的调用方法。
这在这里也不适用。当groupingBy重载时,这些方法的参数数量有所不同,这允许在不知道参数类型的情况下选择唯一合适的方法。还可以证明,当我们替换groupingBy为具有预期签名但没有重载的其他方法时,编译器的行为不会改变。
您的问题可以通过使用解决,例如
TreeMap<Integer, List<String>> res = list.stream()
.collect(Collectors.groupingBy(
(String s) -> s.charAt(0) % 3,
() -> new TreeMap<>(Comparator.reverseOrder()),
Collectors.toList()
));
Run Code Online (Sandbox Code Playgroud)
这为分组函数使用了显式类型化的lambda表达式,尽管该表达式实际上并未对映射键的类型有所贡献,但会使编译器找到实际的类型。
如上所述,尽管使用显式类型的lambda表达式而不是隐式类型的表达式可以在方法重载解析上有所不同,但在此不应该将其应用于此处,因为这种特定情况不是重载方法的问题。
足够奇怪的是,甚至以下更改也使编译器错误消失了:
static <X> X dummy(X x) { return x; }
…
TreeMap<Integer, List<String>> res = list.stream()
.collect(Collectors.groupingBy(
s -> s.charAt(0) % 3,
dummy(() -> new TreeMap<>(Comparator.reverseOrder())),
Collectors.toList()
));
Run Code Online (Sandbox Code Playgroud)
在这里,我们没有帮助任何其他显式类型,也没有改变lambda表达式的形式性质,但是,编译器仍然突然正确地推断了所有类型。
该行为似乎与以下事实有关:零参数lambda表达式始终被显式键入。由于我们无法更改零参数lambda表达式的性质,因此创建了以下替代收集器方法进行验证:
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Function<Void,M> mapFactory,
Collector<? super T, A, D> downstream) {
return Collectors.groupingBy(classifier, () -> mapFactory.apply(null), downstream);
}
Run Code Online (Sandbox Code Playgroud)
然后,使用隐式类型的lambda表达式作为map factory进行编译时不会出现问题:
TreeMap<Integer, List<String>> res = list.stream()
.collect(groupingBy(
s -> s.charAt(0) % 3,
x -> new TreeMap<>(Comparator.reverseOrder()),
Collectors.toList()
));
Run Code Online (Sandbox Code Playgroud)
而使用显式类型的lambda表达式会导致编译器错误:
TreeMap<Integer, List<String>> res = list.stream()
.collect(groupingBy( // compiler error
s -> s.charAt(0) % 3,
(Void x) -> new TreeMap<>(Comparator.reverseOrder()),
Collectors.toList()
));
Run Code Online (Sandbox Code Playgroud)
我认为,即使规范支持此行为,也应予以纠正,因为提供显式类型的含义绝不应该是类型推断变得比没有条件恶化。对于零参数lambda表达式尤其如此,我们不能将其转换为隐式类型的表达式。
它还没有解释为什么将所有参数都转换为显式类型的lambda表达式也可以消除编译器错误。
| 归档时间: |
|
| 查看次数: |
249 次 |
| 最近记录: |