背景:我最近写了一个答案,我建议写下面的代码:
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>
。
1.然后它确定这个接口的函数类型,在我的例子中是() -> Iterator<? extends CharSequence>
。
1.然后,它查看lambda并检查它是否兼容,在我的例子中,lambda的类型为() -> Iterator<String>
,它与步骤2中确定的函数类型兼容。
有趣的是,如果我将lambda的目标改为Supplier
:
Supplier<Iterator<? extends CharSequence>> supplier = () -> Arrays.stream(arr)
.map(Object::toString)
.iterator();
它将编译良好。
为什么javac不能为这个lambda推断出正确的类型?
2条答案
按热度按时间cdmah0mi1#
你可以找到一些解释here:
通配符参数化函数接口类型必须在检查兼容性之前转换为函数类型(方法签名)...其工作原理如下:
因此,如果lambda表达式是隐式类型的,则LHS变为
Iterator<CharSequence>
,而RHS为Iterator<String>
。JLS §18.5.3中也解释了这种行为。
hc2pp10m2#
在阅读了另一个答案(这是绝对正确的)和一些咖啡之后,看起来bug中的解释相当合乎逻辑。
这里有两种情况:一个 * 显式 * lambda类型和一个 * 隐式 * lambda类型。显式类型为:
或者像OP的例子一样:
我们直接告诉编译器lambda表达式是哪种类型:
Iterable<String>
.在这种情况下,编译器只需要做一件事:查看目标是否可分配给该类型;很容易找到,而且与lambdas本身没有太大关系。
另一种类型是 * implicit * 类型,当编译器必须 * 推断 * 类型时,事情变得有点棘手。"棘手"的部分来自于目标使用通配符的事实,因此可能匹配不止一个选项。可能有无数种方法(当然是有限的,但只是为了证明一点)可以将lambda推断为。
它可以这样开始,例如:
无论进一步做什么,这都将失败:
CharSequence
不能扩展Serializable
,但String
可以;我们将不能将Iterable<? extends CharSequence> iterable
分配给"具有可串行化的任何推断类型"。或者可以这样开头:
因此,理论上,编译器可以开始推断该类型可能是什么,并逐个检查"某个"推断的类型是否可以匹配目标;但显然需要大量的工作因此不进行。
另一种方法要简单得多,"切割"目标,从而将推理的可能性降到只有一个,一旦目标被转换为:
编译器必须做的工作要简单得多。
顺便说一句,这不是我第一次在lambdas中看到这种隐式与显式类型的逻辑。