背景:我最近写了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编译器执行以下操作:
Iterable<? extends CharSequence>
。() -> Iterator<? extends CharSequence>
。() -> Iterator<String>
。有趣的是,如果我将lambda的目标更改为Supplier
:
Supplier<Iterator<? extends CharSequence>> supplier = () -> Arrays.stream(arr)
.map(Object::toString)
.iterator();
它将编译正常。
现在的问题是:为什么javac不能为此lambda推断正确的类型?
答案 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中的隐式和显式类型逻辑。