首页 > 解决方案 > 为什么 Java 编译器不能推断 Iterable从约束 Iterable和 () -> (Iterator)

问题描述

背景:我最近写了一个答案,我建议编写以下代码:

Files.write(Paths.get("PostgradStudent.csv"),
        Arrays.stream(PGstudentArray).map(Object::toString).collect(Collectors.toList()),
        StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

想了想,我说:“我这里其实不需要列表,我只需要一个Iterable<? extends CharSequence>”。
正如Stream<T>有一个方法Iterator<T> iterator(),然后我想,嗯,这很容易:

Iterable<? extends CharSequence> iterable = () -> Arrays.stream(arr).map(Object::toString).iterator();

(我将它提取到这个问题的局部变量中,我想在最后内联。)
不幸的是,如果没有额外的类型提示,这将无法编译:

error: incompatible types: bad return type in lambda expression
Iterable<? extends CharSequence> iterable = () -> Arrays.stream(arr).map(Object::toString).iterator();
                                                                                                   ^
    Iterator<String> cannot be converted to Iterator<CharSequence>

当然,添加一些类型提示将使这项工作:

Iterable<? extends CharSequence> iterable2 = (Iterable<String>) () -> Arrays.stream(arr).map(Object::toString).iterator();
Iterable<? extends CharSequence> iterable3 = () -> Arrays.stream(arr).<CharSequence>map(Object::toString).iterator();

在我的理解中,Java 编译器做了以下事情:

  1. 它查看表达式的目标类型,即Iterable<? extends CharSequence>.
  2. 然后它确定这个接口的函数类型,() -> Iterator<? extends CharSequence>在我的例子中。
  3. 然后它查看 lambda 并检查它是否兼容。
    就我而言,lambda 的类型为() -> Iterator<String>.
    与步骤 2 中确定的函数类型兼容。

有趣的是,如果我将 lambda 的目标更改为Supplier

Supplier<Iterator<? extends CharSequence>> supplier = () -> Arrays.stream(arr)
    .map(Object::toString)
    .iterator();

它会编译得很好。

现在的问题是:为什么 javac 不能推断出这个 lambda 的正确类型?

标签: javalambdatype-inferencejls

解决方案


你可以在这里找到一些解释:

在检查兼容性之前,必须将通配符参数化的功能接口类型转换为函数类型(方法签名)......其工作原理如下:

Iterable<? extends CharSequence> becomes () -> Iterator<CharSequence>

因此,如果 lambda 表达式是隐式类型的,则 LHS 变为Iterator<CharSequence>而 RHS 为Iterator<String>. 因此,出现错误:

Iterator<String> cannot be converted to Iterator<CharSequence>

JLS §18.5.3中也解释了这种行为。


推荐阅读