为什么Java编译器不能从约束Iterable <推断出Iterable <String>?扩展CharSequence>和()->(Iterator <String>)

时间:2019-10-24 09:16:04

标签: java lambda type-inference jls

背景:我最近写了an answer,建议写以下代码:

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推断正确的类型?

2 个答案:

答案 0 :(得分:3)

您可以找到一些解释here

  

通配符参数化的功能接口类型必须先转换为功能类型(方法签名),然后再检查兼容性...这如下工作:

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中也对此行为进行了解释。

答案 1 :(得分:0)

在阅读了另一个答案(绝对正确)和一些咖啡后,该错误中的解释似乎是合乎逻辑的。

这里有两种情况:显式 lambda类型和隐式 lambda类型。显式类型是:

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

或在OP的示例中:

Iterable<String>

我们直接告诉编译器lambda表达式的类型是:Iterator<? extends Serializable> iter = Arrays.stream(arr).map(Object::toString).iterator();

在这种情况下,编译器只需要做一件事:查看目标是否可分配给该类型;很容易发现,与本身的lambda无关。

另一种类型是隐式类型,当编译器必须推断时,这里的内容会有些棘手。 “棘手”部分来自以下事实:目标使用通配符,因此可以匹配多个选项。可以推断出lambda的方法有无数种(当然是有限的,只是为了证明一个观点)。

它可以从这样的东西开始,例如:

CharSequence

无论进一步做什么,都会失败:Serializable不会扩展String,但是Iterable<? extends CharSequence> iterable会扩展;我们将无法将Iterator<? extends Comparable<? extends CharSequence>> iter = Arrays.stream(arr).map(Object::toString).iterator(); 分配给“具有可序列化的任何推断类型”。

或者它可以开始于:

Iterable<CharSequence> iterable...

因此,从理论上讲,编译器可以开始推断该类型是什么,并一个接一个地检查“确定的”推断类型是否可以匹配目标。但显然需要很多工作;因此没有完成。

另一种方法要容易得多,可以“削减”目标,从而将推理的可能性降低到一个。将目标转换为:

Exception in thread "main" javax.xml.ws.WebServiceException: Provider com.sun.xml.internal.ws.spi.ProviderImpl not found
        at javax.xml.ws.spi.FactoryFinder$1.createException(FactoryFinder.java:31)
        at javax.xml.ws.spi.FactoryFinder$1.createException(FactoryFinder.java:28)
        at javax.xml.ws.spi.ServiceLoaderUtil.newInstance(ServiceLoaderUtil.java:73)
        at javax.xml.ws.spi.FactoryFinder.find(FactoryFinder.java:82)
        at javax.xml.ws.spi.Provider.provider(Provider.java:66)
        at javax.xml.ws.Service.<init>(Service.java:82)
        at com.sunchain.sge.RecherchePointV20.<init>(RecherchePointV20.java:42)
        at com.sunchain.sge.DemoApplication.main(DemoApplication.java:22)
Caused by: java.lang.ClassNotFoundException: com.sun.xml.internal.ws.spi.ProviderImpl
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        at javax.xml.ws.spi.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:60)
        at javax.xml.ws.spi.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:93)
        at javax.xml.ws.spi.ServiceLoaderUtil.newInstance(ServiceLoaderUtil.java:71)
        ... 5 more

编译器要做的工作很简单。

顺便说一句,这并不是我第一次看到lambdas中的隐式和显式类型逻辑。