为什么 Java 中的 var 关键字不能赋 lambda 表达式?

Why can't the var keyword in Java be assigned a lambda expression?

允许在 Java 10 中分配 var,字符串如下:

var foo = "boo";

虽然不允许用lambda表达式赋值,例如:

var predicateVar = apple -> apple.getColor().equals("red");

为什么它不能推断出 lambda 或方法引用类型,而它可以推断出 StringArrayList、用户 class 等其他类型?

因为那是一个non-feature:

This treatment would be restricted to local variables with initializers, indexes in the enhanced for-loop, and locals declared in a traditional for-loop; it would not be available for method formals, constructor formals, method return types, fields, catch formals, or any other kind of variable declaration.

http://openjdk.java.net/jeps/286

来自Local-Variable Type Inference JEP

The inference process, substantially, just gives the variable the type of its initializer expression. Some subtleties:

  • The initializer has no target type (because we haven't inferred it yet). Poly expressions that require such a type, like lambdas, method references, and array initializers, will trigger an error.

由于 lambda 表达式本身没有类型,因此无法推断 var


... Similarly, a default rule could be set.

当然,您可以想出一种方法来解决此限制。为什么开发人员决定不这样做真的取决于猜测,除非参与决策的人可以在这里回答。 (更新:回答 .) If you're interested anyway, you could ask about it on one of the openjdk mailing lists: http://mail.openjdk.java.net/mailman/listinfo

如果我猜的话,他们可能不想将 var 上下文中的 lambda 推理与一组特定的功能接口类型联系起来,这将排除任何第三方功能接口类型。更好的解决方案是推断可以转换为兼容的功能接口类型的通用函数类型(即 (Apple) -> boolean)。但是 JVM 没有这样的函数类型,并且在创建 lambda 表达式的项目期间已经做出了不实现它们的决定。同样,如果您对具体原因感兴趣,请询问开发人员。

要回答这个问题,我们必须深入了解 lambda 是什么以及它是如何工作的。

首先我们应该了解什么是lambda:

lambda 表达式始终实现函数式接口,因此当您必须提供像 Runnable 这样的函数式接口时,不必创建一个全新的 class 来实现该接口,您可以只使用 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");

是模棱两可的,并且没有合理的理由说明为什么编译器应该选择 Function<Apple, Boolean> 而不是 Predicate<Apple> 或者相反,假设 lambda 中的 apple 标识符代表 Apple实例。

另一个原因是 lambda 本身没有 speakable 类型,因此编译器无法推断它。

此外,"if this was possible" 想象一下开销,因为编译器必须遍历所有功能接口并确定每次分配时哪个功能接口最合适var 变量的 lambda。

对于所有说这是不可能的、不希望的或不需要的人,我只想指出 Scala 可以通过仅指定参数类型来推断 lambda 的类型:

val predicateVar = (apple: Apple) => apple.getColor().equals("red")

而在 Haskell 中,因为 getColor 将是一个独立的函数,不附加到对象,并且因为它进行完整的 Hindley-Milner 推理,所以您甚至不需要指定参数类型:

predicateVar = \apple -> getColor apple == "red"

这非常方便,因为让程序员显式指定的不是简单的类型,而是更复杂的类型。

换句话说,它不是 Java10 中的功能。这是他们的实现和以前的设计选择的限制。

这与var无关。它与 lambda 是否具有 独立类型 有关。 var 的工作方式是计算 RHS 上初始值设定项的独立类型,并推断出它。

自从在 Java 8 中引入以来,lambda 表达式和方法引用没有独立的类型——它们需要一个 目标类型,它必须是一个函数式接口。

如果你尝试:

Object o = (String s) -> s.length();

您还会遇到类型错误,因为编译器不知道您打算将 lambda 转换为什么功能接口。

var 进行推理只会让事情变得更难,但由于无法回答更简单的问题,因此也无法回答更难的问题。

请注意,您可以通过其他方式(例如强制转换)提供目标类型,然后它将起作用:

var x = (Predicate<String>) s -> s.isEmpty();

因为现在 RHS 有一个独立的类型。但是你最好通过给 x 一个清单类型来提供目标类型。

简而言之,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>