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

允许在 Java10中使用如下字符串分配 var:

var foo = "boo";

虽然不允许将其赋给 lambda 表达式,如:

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

为什么它不能推断一个 lambda 或者方法引用类型,而它却能推断其他类型,比如 StringArrayList、用户类等等。?

6985 次浏览

Because that is a 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

From the 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.

Because a lambda expression by itself does not have a type, it can not be inferred for var.


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

Sure, you can come up with a way to work around this limitation. Why the developers made the decision not to do that is really up to speculation, unless someone who was part of the decision making can answer here. (Update: answered here.) If you're interested anyway, you could ask about it on one of the openjdk mailing lists: http://mail.openjdk.java.net/mailman/listinfo

If I were to guess, they probably didn't want to tie lambda inference in the context of var to a specific set of functional interface types, which would exclude any third party functional interface types. A better solution would be to infer a generic function type (i.e. (Apple) -> boolean) that can than be converted to a compatible functional interface type. But the JVM does not have such function types, and the decision to not implement them was already made during the project that created lambda expressions. Again if you're interested in concrete reasons, ask the devs.

To answer this we have to go into details and understand what a lambda is and how it works.

First we should understand what a lambda is:

A lambda expression always implements a functional interface, so that when you have to supply a functional interface like Runnable, instead of having to create a whole new class that implements the interface, you can just use the lambda syntax to create a method that the functional interface requires. Keep in mind though that the lambda still has the type of the functional interface that it is implementing.

With that in mind, lets take this a step further:

This works great as in the case of Runnable, I can just create a new thread like this new Thread(()->{//put code to run here}); instead of creating a whole new object to implement the functional interface. This works since the compiler knows that Thread() takes an object of type Runnable, so it knows what type the lambda expression has to be.

However, in a case of assigning a lambda to a local variable, the compiler has no clue what functional interface this lambda is implementing so it can't infer what type var should be. Since maybe it's implementing a functional interface the user created or maybe it's the runnable interface, there is just no way to know.

This is why lambdas do not work with the var keyword.

As several people have already mentioned, what type should var infer and why should it?

The statement:

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

is ambiguous and there is no valid reason why the compiler should pick Function<Apple, Boolean> over Predicate<Apple> or vice versa assuming the apple identifier in the lambda represents an Apple isntance.

Another reason is that a lambda in its own doesn't have a speakable type hence there is no way for the compiler to infer it.

Also, "if this was possible" imagine the overhead as the compiler would have to go through all the functional interfaces and determine which functional interface is the most appropriate each time you assign a lambda to a var variable.

To everyone who is saying this is impossible, undesired, or unwanted, I just want to point out that Scala can infer the lambda's type by specifying only the argument type:

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

And in Haskell, because getColor would be a standalone function not attached to an object, and because it does full Hindley-Milner inference, you don't need to specify even the argument type:

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

This is extraordinarily handy, because it's not the simple types that are annoying for programmers to explicitly specify, it's the more complex ones.

In other words, it's not a feature in Java 10. It's a limitation of their implementation and previous design choices.

This has nothing to do with var. It has to do with whether a lambda has a standalone type. The way var works is that it computes the standalone type of the initializer on the RHS, and infers that.

自从在Java8中引入以来,lambda表达式和方法引用就没有独立的类型--它们需要一个target type,它必须是一个函数接口。

If you try:

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

you also get a type error, because the compiler has no idea what functional interface you intend to convert the lambda to.

Asking for inference with var just makes it harder, but since the easier question can't be answered, the harder one cannot either.

Note that you could provide a target type by other means (such as a cast) and then it would work:

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

because now the RHS has a standalone type. But you are better off providing the target type by giving x a manifest type.

In a nutshell, the types of a var and lambda expression both need inference, but in opposite way. The type of a var is inferred by the initializer:

var a = new Apple();

The type of a lambda expression is set by the context. The type expected by the context is called the target type, and is usually inferred by the declaration e.g.

// 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;
}

So when a var and lambda expression are used together, the type of the former needs to be inferred by the latter while the type of the latter needs to be inferred by the former.

var a = (n) -> 2 * n;

The root of this dilemma is Java cannot decide the type of a lambda expression uniquely, which is further caused by Java's nominal instead of structural type system. That is, two types with identical structures but different names are not deemed as the same, e.g.

class A{
public int count;
int value(){
return count;
}
}


class B{
public int count;
int value(){
return count;
}
}


Function<Integer, Boolean>
Predicate<Integer>