72

允许var在 Java 10 中使用如下字符串进行分配:

var foo = "boo";

虽然不允许使用 lambda 表达式分配它,例如:

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

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

4

7 回答 7

68

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

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

如果你试试:

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

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

要求推理var只会让事情变得更难,但由于无法回答更容易的问题,因此更难的问题也无法回答。

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

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

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

于 2018-03-30T21:34:11.613 回答
62

局部变量类型推断 JEP

推理过程实质上只是为变量提供了其初始化表达式的类型。一些微妙之处:

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

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


...类似地,可以设置默认规则。

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

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

于 2018-03-30T17:18:57.823 回答
32

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

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

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

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

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

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

于 2018-03-30T19:54:13.920 回答
5

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

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

lambda 表达式总是实现一个函数式接口,因此当您必须提供一个函数式接口Runnable时,不必创建一个实现该接口的全新类,您只需使用 lambda 语法创建一个函数式接口的方法需要。请记住,尽管 lambda 仍然具有它正在实现的功能接口的类型。

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

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

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

这就是为什么 lambda 不能与 var 关键字一起使用的原因。

于 2018-03-30T17:29:46.217 回答
5

正如一些人已经提到的,应该var推断什么类型,为什么要推断?

该声明:

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

是模棱两可的,假设lambda 中的标识符代表一个实例,编译器应该选择Function<Apple, Boolean>Predicate<Apple>反之亦然,没有正当理由。appleApple

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

此外,“如果这是可能的”想象一下开销,因为每次将 lambda 分配给var变量时,编译器必须遍历所有功能接口并确定哪个功能接口最合适。

于 2018-03-30T18:06:48.357 回答
2

因为这是一个非功能:

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

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

于 2018-03-30T17:18:49.990 回答
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>
于 2020-01-04T02:50:59.507 回答