在Java 10中,允许像这样使用字符串赋值var
:
var foo = "boo";
虽然不允许将其分配给lambda表达式,例如:
var predicateVar = apple -> apple.getColor().equals("red");
为什么它可以推断出String
、ArrayList
、用户类等,却无法推断出lambda或方法引用类型呢?这与var
无关,而是与lambda是否具有独立类型有关。 var
的工作方式是计算右侧初始化程序的独立类型,并推断出该类型。
自从Java 8引入lambda表达式和方法引用以来,它们就没有独立类型 - 它们需要一个目标类型,该目标类型必须是函数式接口。
如果您尝试:
Object o = (String s) -> s.length();
你还会得到类型错误,因为编译器不知道你想将lambda转换成什么函数接口。
使用var
进行推断只会让问题更难,但是由于更容易的问题无法回答,更难的问题也无法得到解决。
请注意,你可以通过其他方式(比如强制类型转换)提供目标类型,那样它就会起作用:
var x = (Predicate<String>) s -> s.isEmpty();
因为现在RHS有一个独立的类型。但最好通过为x
提供显式类型来指定目标类型。
来自局部变量类型推断JEP:
推断过程实质上只是给变量赋予其初始化表达式的类型。一些微妙之处:
- 初始化器没有目标类型(因为我们尚未推断出它)。需要此类类型的多态表达式,如lambda、方法引用和数组初始化器,将触发错误。
因为lambda表达式本身没有类型,所以无法为var
推断类型。
... 同样,可以设置一个默认规则。
当然,你可以想出一种解决这个限制的方法。开发人员为什么决定不这样做,真的取决于猜测,除非有参与决策的人能在这里回答。 (更新:在这里回答。) 如果您有兴趣,可以在一个openjdk邮件列表上询问: http://mail.openjdk.java.net/mailman/listinfo
如果我要猜测,他们可能不想将lambda推断与var
上下文中的特定一组功能接口类型联系起来,这将排除任何第三方功能接口类型。更好的解决方案是推断出一种通用函数类型(i.e. (Apple) -> boolean
),然后将其转换为兼容的功能接口类型。但JVM没有这样的函数类型,并且在创建lambda表达式的项目期间已经做出了不实现它们的决定。如果你有兴趣了解具体原因,请询问开发人员。
javac
做了很少的优化,在编译时对多态表达式进行潜在搜索实现将是疯狂的,我想。 - Eugene(String s) -> s.isEmpty()
已经兼容 Function<String,Boolean>
和 Predicate<String>
等多种类型。要求选择一种类型等于要求心灵感应,如果我们添加新类型,则可能会导致人们程序中的类型发生变化,或现有程序无法编译。这样做不太好。其次,在java.util.function
中的函数式接口不是魔法或语言的一部分,它们只是普通的接口。更好的方式是让用户直接声明他们所需的类型。 - Brian Goetz对于所有认为这是不可能、不需要或不想要的人,我想指出的是,Scala可以通过仅指定参数类型来推断lambda的类型:
val predicateVar = (apple: Apple) => apple.getColor().equals("red")
在 Haskell 中,由于 getColor
是一个独立的函数而不是附加到对象上的,而且它执行完整的 Hindley-Milner 推理,因此你甚至不需要指定参数类型:
predicateVar = \apple -> getColor apple == "red"
这非常方便,因为对于程序员来说,繁琐的不是指定简单类型,而是更复杂的类型。
换句话说,这不是 Java 10 的一个特性。这是它们实现和之前的设计选择的限制。
正如其他人已经提到的那样,var
应该推断哪种类型,为什么呢?
这个语句:
var predicateVar = apple -> apple.getColor().equals("red");
这段代码含义不够明确,编译器在lambda表达式中的apple
标识符代表一个Apple
实例时,没有理由为什么要选择Function<Apple, Boolean>
而不是Predicate<Apple>
或者反过来。
另一个原因是,lambda表达式本身没有一个可说的类型,因此编译器无法推断它。
此外,如果这是可能的话,想象一下编译器会有多大的开销,每次将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 a = (n) -> 2 * n;
class A{
public int count;
int value(){
return count;
}
}
class B{
public int count;
int value(){
return count;
}
}
Function<Integer, Boolean>
Predicate<Integer>
var
应该推断哪种特定类型?标准函数接口中的一种还是您自己创建的一种?这个决定基于什么? - PshemoPredicate<Apple>
(假设apple
是Apple
类型)。这就是为什么这是一个错误而不是一个特性。 - erickson