允许var
在 Java 10 中使用如下字符串进行分配:
var foo = "boo";
虽然不允许使用 lambda 表达式分配它,例如:
var predicateVar = apple -> apple.getColor().equals("red");
为什么它不能推断出 lambda 或方法引用类型,而它可以推断出其余的,如String
、ArrayList
、 用户类等?
这与var
. 它与 lambda 是否具有独立类型有关。工作方式var
是它计算 RHS 上初始化程序的独立类型,并推断出来。
由于它们在 Java 8 中的引入,lambda 表达式和方法引用没有独立的类型——它们需要一个目标类型,它必须是一个函数式接口。
如果你试试:
Object o = (String s) -> s.length();
您还会收到类型错误,因为编译器不知道您打算将 lambda 转换为什么功能接口。
要求推理var
只会让事情变得更难,但由于无法回答更容易的问题,因此更难的问题也无法回答。
请注意,您可以通过其他方式(例如强制转换)提供目标类型,然后它将起作用:
var x = (Predicate<String>) s -> s.isEmpty();
因为现在 RHS 有一个独立的类型。但是您最好通过提供x
清单类型来提供目标类型。
推理过程实质上只是为变量提供了其初始化表达式的类型。一些微妙之处:
- 初始化器没有目标类型(因为我们还没有推断出它)。需要这种类型的 Poly 表达式,如lambdas、方法引用和数组初始值设定项,将触发错误。
因为 lambda 表达式本身没有类型,所以不能推断为var
.
...类似地,可以设置默认规则。
当然,您可以想出一种方法来解决这个限制。为什么开发人员决定不这样做实际上取决于猜测,除非参与决策的人可以在这里回答。(更新:在这里回答。)如果你有兴趣,你可以在 openjdk 邮件列表之一上询问它:http: //mail.openjdk.java.net/mailman/listinfo
如果我猜的话,他们可能不想将上下文中的 lambda 推理var
与一组特定的功能接口类型联系起来,这将排除任何第三方功能接口类型。更好的解决方案是推断(Apple) -> boolean
可以转换为兼容的功能接口类型的通用函数类型(即 )。但是 JVM 没有这样的函数类型,并且在创建 lambda 表达式的项目期间已经做出了不实现它们的决定。同样,如果您对具体原因感兴趣,请询问开发人员。
对于每个说这是不可能、不希望或不需要的人,我只想指出 Scala 可以通过仅指定参数类型来推断 lambda 的类型:
val predicateVar = (apple: Apple) => apple.getColor().equals("red")
在 Haskell 中,因为getColor
它是一个不附加到对象的独立函数,并且因为它执行完整的 Hindley-Milner 推理,所以您甚至不需要指定参数类型:
predicateVar = \apple -> getColor apple == "red"
这非常方便,因为让程序员显式指定烦人的不是简单类型,而是更复杂的类型。
换句话说,它不是 Java 10 中的一个特性。这是它们的实现和以前的设计选择的限制。
要回答这个问题,我们必须深入了解 lambda 是什么以及它是如何工作的。
首先我们应该了解什么是 lambda:
lambda 表达式总是实现一个函数式接口,因此当您必须提供一个函数式接口Runnable
时,不必创建一个实现该接口的全新类,您只需使用 lambda 语法创建一个函数式接口的方法需要。请记住,尽管 lambda 仍然具有它正在实现的功能接口的类型。
考虑到这一点,让我们更进一步:
这在 Runnable 的情况下非常有效,我可以像这样创建一个新线程,new Thread(()->{//put code to run here});
而不是创建一个全新的对象来实现功能接口。这是可行的,因为编译器知道它Thread()
需要一个 Runnable 类型的对象,所以它知道 lambda 表达式必须是什么类型。
但是,在将 lambda 分配给局部变量的情况下,编译器不知道该 lambda 正在实现什么功能接口,因此它无法推断var
应该是什么类型。由于它可能正在实现用户创建的功能界面,或者它可能是runnable
界面,所以没有办法知道。
这就是为什么 lambda 不能与 var 关键字一起使用的原因。
正如一些人已经提到的,应该var
推断什么类型,为什么要推断?
该声明:
var predicateVar = apple -> apple.getColor().equals("red");
是模棱两可的,假设lambda 中的标识符代表一个实例,编译器应该选择Function<Apple, Boolean>
,Predicate<Apple>
反之亦然,没有正当理由。apple
Apple
另一个原因是 lambda 本身没有可说话的类型,因此编译器无法推断它。
此外,“如果这是可能的”想象一下开销,因为每次将 lambda 分配给var
变量时,编译器必须遍历所有功能接口并确定哪个功能接口最合适。
因为这是一个非功能:
这种处理将仅限于具有初始化器的局部变量、增强型 for 循环中的索引以及在传统 for 循环中声明的局部变量;它不适用于方法形式、构造函数形式、方法返回类型、字段、捕获形式或任何其他类型的变量声明。
简而言之,var 和 lambda 表达式的类型都需要推理,但方式相反。var 的类型由初始化器推断:
var a = new Apple();
lambda 表达式的类型由上下文设置。上下文期望的类型称为目标类型,通常由声明推断,例如
// Variable assignment
Function<Integer, Integer> l = (n) -> 2 * n;
// Method argument
List<Integer> map(List<Integer> list, Function<Integer, Integer> fn){
//...
}
map(List.of(1, 2, 3), (n) -> 2 * n);
// Method return
Function<Integer, Integer> foo(boolean flag){
//...
return (n) -> 2 * n;
}
所以当 var 和 lambda 表达式一起使用时,前者的类型需要由后者推断,而后者的类型需要由前者推断。
var a = (n) -> 2 * n;
这种困境的根源是Java不能唯一决定一个lambda表达式的类型,这进一步是由于Java的名义类型而不是结构类型系统造成的。也就是说,结构相同但名称不同的两种类型不被认为是相同的,例如
class A{
public int count;
int value(){
return count;
}
}
class B{
public int count;
int value(){
return count;
}
}
Function<Integer, Boolean>
Predicate<Integer>