流中收集器中的比较器会导致类型推断出现问题? [英] Comparator in collector in stream causes issues with type inference?

查看:97
本文介绍了流中收集器中的比较器会导致类型推断出现问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下简化示例,它将字符串列表分类为类别,以TreeMap的形式从整数到列表

I have the following simplified example that groups a List of Strings into categories, in the form of a TreeMap from Integer to 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);
}

如果我没有指定Comparator.reverseOrder()的类型代码将无法编译(请参阅帖子底部的错误)。

If I don't specify the type of the Comparator.reverseOrder() the code will fail to compile (see bottom of the post for the error).

如果我明确指定TreeMap的类型而不是Comparator.reverseOrder()的类型代码工作正常。

If I explicitly specify the type of the TreeMap instead of the type of Comparator.reverseOrder() the code works fine.

() -> new TreeMap<Integer, List<String>>(Comparator.reverseOrder()), // Type required

所以:


  • 编译器能够推断出TreeMap的类型

  • 编译器如果它知道TreeMap的类型,则能够推断比较器的类型

  • 但是如果编译器必须推断出类型,那么编译器无法确定比较器的类型。 TreeMap。

我不明白为什么编译器无法推断这两种类型。我用Oracle的JDK 1.8.0_191和AdoptOpenJDK的JDK 11.0.1_13进行了测试,结果相同。

I don't understand why the compiler can't infer both types. I've tested this with both JDK 1.8.0_191 from Oracle and JDK 11.0.1_13 from AdoptOpenJDK, with the same results.

这是一些限制我不知道?

Is this some limitation I'm not aware of?

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)


推荐答案

不幸的是,类型推断具有非常复杂的规范,这使得很难确定特定的奇怪行为是否符合规范或仅仅是编译器错误。

Unfortunately, the type inference has a really complex specification, which makes it very hard to decide whether a particular odd behavior is conforming to the specification or just a compiler bug.

对类型推断有两个众所周知的有意限制。

There are two well-known deliberate limitations to the type inference.

首先,表达式的目标类型不用于接收器表达式,即一系列方法调用。所以当你有一份表格的陈述时

First, the target type of an expression is not used for receiver expressions, i.e. in a chain of method invocations. So when you have a statement of the form

TargetType x = first.second(…).third(…);

TargetType 将用于推断 third()调用的泛型类型及其参数表达式,但不适用于 second(...)调用。因此 second(...)的类型推断只能使用独立类型的 first 和参数表达式。

the TargetType will be use to infer the generic type of the third() invocation and its argument expressions, but not for second(…) invocation. So the type inference for second(…) can only use the stand-alone type of first and the argument expressions.

这不是问题。由于独立类型的列表被明确定义为 List< String> ,因此推断出没有问题结果类型流< String> 用于 stream()调用以及有问题的 collect call是链的最后一个方法调用,它可以使用目标类型 TreeMap< Integer,List< String>> 来推断类型参数。

This is not an issue here. Since the stand-alone type of list is well defined as List<String>, there is no problem in inferring the result type Stream<String> for the stream() call and the problematic collect call is the last method invocation of the chain, which can use the target type TreeMap<Integer, List<String>> to infer the type arguments.

第二个限制是关于重载解析。语言设计者在涉及到需要知道实际目标方法及其类型的不完整类型的参数表达式之间的循环依赖时,会有意识地削减它们,然后才能帮助确定正确的调用方法。

The second limitation is about overload resolution. The language designers made a deliberate cut when it comes to a circular dependency between incomplete types of argument expressions which need to know the actual target method and its type, before they could help determine the right method to invoke.

这也不适用于此。虽然 groupingBy 被重载,但这些方法的参数数量不同,这允许在不知道参数类型的情况下选择唯一合适的方法。还可以证明,当我们用一个具有预期签名但没有重载的不同方法替换 groupingBy 时,编译器的行为不会改变。

This also doesn’t apply here. While groupingBy is overloaded, these methods differ in the number of parameters, which allows to select the only appropriate method without knowing the argument types. It can also be shown that the compiler’s behavior doesn’t change when we replace groupingBy with a different method which has the intended signature but no overloads.

您的问题可以通过使用来解决,例如

Your issue can be solved by using, e.g.

TreeMap<Integer, List<String>> res = list.stream()
    .collect(Collectors.groupingBy(
        (String s) -> s.charAt(0) % 3,
        () -> new TreeMap<>(Comparator.reverseOrder()),
        Collectors.toList()
    ));

这为分组函数使用显式类型的lambda表达式,虽然实际上并没有对类型有贡献映射的键,导致编译器找到实际的类型。

This uses an explicitly typed lambda expression for the grouping function, which, while not actually contributes to the types of the map’s key, causes the compiler to find the actual types.

虽然使用显式类型的lambda表达式而不是隐式类型的表达式可以对方法重载决策产生影响,如上所述,它不应​​该适用于此,因为这个特定的场景不是重载方法的问题。

While the use of explicitly typed lambda expressions instead of implicitly typed ones can make a difference on method overload resolution, as said above, it shouldn’t apply here, as this specific scenario is not an issue of overloaded methods.

奇怪的是,即使是以下更改也会导致编译错误离开:

Weirdly enough, even the following change makes the compiler error go away:

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()
    ));

在这里,我们没有帮助任何其他显式类型,也没有改变其正式性质lambda表达式,但仍然,编译器突然正确地推断所有类型。

Here, we’re not helping with any additional explicit type and also not changing the formal nature of the lambda expressions, but still, the compiler suddenly infers all types correctly.

这种行为似乎与零参数lambda表达式总是显式类型的事实有关。由于我们无法更改零参数lambda表达式的性质,因此我创建了以下替代收集器方法进行验证:

The behavior seems to be connected to the fact that zero parameter lambda expressions are always explicitly typed. Since we can’t change the nature of a zero parameter lambda expression, I created the followed alternative collector method for verification:

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);
}

然后,使用隐式类型的lambda表达式作为map工厂编译没有问题:

Then, using an implicitly typed lambda expression as map factory compiles without problems:

TreeMap<Integer, List<String>> res = list.stream()
    .collect(groupingBy(
        s -> s.charAt(0) % 3,
        x -> new TreeMap<>(Comparator.reverseOrder()),
        Collectors.toList()
    ));

而使用显式类型的lambda表达式会导致编译器错误:

whereas using an explicitly typed lambda expression causes a compiler error:

TreeMap<Integer, List<String>> res = list.stream()
    .collect(groupingBy(                           // compiler error
        s -> s.charAt(0) % 3,
        (Void x) -> new TreeMap<>(Comparator.reverseOrder()),
        Collectors.toList()
    ));

在我看来,即使规范备份了这种行为,它也应该得到纠正,因为它意味着提供显式类型永远不应该是类型推断变得比没有更糟糕。对于零参数lambda表达式尤其如此,我们不能将其转换为隐式类型的表达式。

In my opinion, even if the specification backs up this behavior, it should get corrected, as the implication of providing explicit types should never be that the type inference gets worse than without. That’s especially true for zero argument lambda expressions which we can’t turn into implicitly typed ones.

它也没有解释为什么将所有参数转换为显式类型的lambda表达式将也消除了编译错误。

It also doesn’t explain why turning all arguments into explicitly typed lambda expressions will also eliminate the compiler error.

这篇关于流中收集器中的比较器会导致类型推断出现问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆