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

时间:2018-03-30 17:14:23

标签: java lambda java-10

允许在Java 10中为var分配一个字符串,如:

var foo = "boo";

虽然不允许为其分配lambda表达式,例如:

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

为什么它不能推断lambda或方法引用类型,因为它可以推断出其余的StringArrayList,用户类等?

7 个答案:

答案 0 :(得分:55)

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

自Java8中引入以来,lambda表达式和方法引用没有独立类型 - 它们需要目标类型,它必须是一个功能接口。

如果您尝试:

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

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

要求用var进行推理只会让它变得更难,但由于更容易回答的问题无法解决,更难的问题也无法解决。

请注意,您可以通过其他方式(例如演员表)提供目标类型,然后它可以工作:

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

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

答案 1 :(得分:54)

来自Local-Variable Type Inference JEP

  

推理过程基本上只给变量赋予其初始化表达式的类型。一些微妙之处:

     
      
  • 初始化程序没有目标类型(因为我们还没有推断它)。需要这种类型的多表达式(如 lambdas ,方法引用和数组初始值设定项)将触发错误。
  •   

因为lambda表达式本身没有类型,所以不能推断出var

  

...同样,可以设置默认规则。

当然,您可以想出一种解决此限制的方法。为什么开发人员做出不这样做的决定真的取决于猜测,除非参与决策的人能在这里回答。如果您有兴趣,可以在openjdk邮件列表中询问:http://mail.openjdk.java.net/mailman/listinfo

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

答案 2 :(得分:26)

对于那些说这是不可能的,不受欢迎的或不需要的人,我只想指出Scala可以通过仅指定参数类型来推断lambda的类型:

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

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

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

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

换句话说,它不是Java 10中的一个功能。这是对它们的实现和以前的设计选择的限制。

答案 3 :(得分:4)

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

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

lambda表达式总是实现一个功能接口,因此当你必须提供像Runnable这样的功能接口时,你不必创建一个实现接口的全新类,你可以只使用lambda语法创建功能接口所需的方法。请记住,lambda仍然具有它正在实现的功能接口的类型。

考虑到这一点,让我们更进一步:

这在Runnable的情况下效果很好,我可以创建一个像new Thread(()->{//put code to run here});这样的新线程,而不是创建一个全新的对象来实现功能接口。这是有效的,因为编译器知道Thread()采用Runnable类型的对象,因此它知道lambda表达式必须是什么类型。

但是,在将lambda分配给局部变量的情况下,编译器不知道这个lambda正在实现什么功能接口,因此它无法推断出var应该是什么类型。既然它可能实现了用户创建的功能界面,或者它可能是runnable界面,那么就没有办法知道了。

这就是为什么lambdas不能使用var关键字。

答案 4 :(得分:4)

正如有几个人已经提到的那样,var应该推断出什么类型,为什么要这样做?

声明:

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

是模糊的,没有正当理由为什么编译器应该Function<Apple, Boolean>选择Predicate<Apple>,反之亦然,假设lambda中的apple标识符代表Apple isntance。

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

此外,&#34;如果这是可能的&#34; 想象开销,因为编译器必须通过所有功能接口并确定每次你最合适的功能接口将lambda分配给var变量。

答案 5 :(得分:2)

因为这是一个非特征:

  

此处理将局限于具有初始化器的局部变量,增强型for循环中的索引以及传统for循环中声明的局部变量;它不适用于方法形式,构造函数形式,方法返回类型,字段,捕获形式或任何其他类型的变量声明。

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

答案 6 :(得分:0)

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