A new JEP has been filed with proposed changes to enhance lambda functionality, including better disambiguation, use of the underscore for unused parameters, and shadowing of outer variables. Although these changes would bring lambdas in Java closer to those of other languages', initial discussions are showing a mixed level of support. This JEP complements a series of other proposals to improve the Java language and which include local variable type inference and enhanced enums, all of them potentially included in Java 10.
Even though all three changes are related to lambdas, they are rather independent, and some of them could be dropped while others moved forward depending on feedback. For this reason, we will proceed to explain them separately in this article.
Better disambiguation
When lambdas were added to Java in version 8, type inference had to be modified to support them. However, the changes performed in the past didn’t go as far as they could, in part due to concerns that some of those changes may seem confusing for developers new to lambdas. By this point, however, it seems that the situation is changing, and some developers get frustrated when the compiler fails to infer the type of a lambda where the context provides sufficient information for this. The following examples indicate how type inference of lambdas works at the moment:
// Case 1: type of lambda inferred as Predicate<String>
// first overloaded version of method will be called.
private void m(Predicate<String> ps) { /* ... */ }
private void m(Function<String, String> fss) { /* ... */ }
private void callingM() {
m((String s) -> s.isEmpty());
}
// Case 2: not enough information to infer type of lambda independently,
// but m2 isn't overloaded.
// Signature of method argument is applied to lambda to infer
// that s is String and that s.length() returns Integer
private void m2(Function<String, Integer> fsi) { /* ... */ }
private void callingM2() {
m2(s -> s.length());
}
// Case 3: not enough information to infer type of lambda independently.
// m3 is overloaded, but different versions have different arity,
// only first version has matching number of parameters (1).
// Signature of method argument is applied to lambda to infer
// that s is String and that s.length() returns Integer
private void m3(Function<String, Integer> fsi) { /* ... */ }
private void m3(Function<String, Integer> fsi, String s) { /* ... */ }
private void callingM3() {
m3(s -> s.length());
}
// Case 4: not enough information to infer type of lambda independently.
// Multiple overloaded versions of m4 with same arity available.
// Ambiguous call, ERROR
private void m4(Predicate<String> ps) { /* ... */ }
private void m4(Function<String, String> fss) { /* ... */ }
private void callingM4() {
m4(s -> s.isEmpty());
}
In this last case however, there is enough information to conclude that the first overloaded version of m4
is the one to use, although the compiler doesn’t currently use that information. Under the new proposal, the compiler could follow these steps to resolve ambiguity:
- Both possibilities assume that the argument of the lambda is a
String
, sos
can be considered of typeString
- Now that we know that
s
is aString
, we know that the methodString.isEmpty()
returnsboolean
- Since the lambda returns
boolean
, the second variation ofm4
doesn’t match, so it is discarded - The only remaining option is the first variation of
m4
, which matches the inferred type of the lambda, so this one is used
A similar argumentation can be applied to method references.
Use of underscore for unused parameters
There are scenarios where a lambda with a number of parameters is expected, although the body won’t use all of them, forcing the developer to use indicative names for the unused parameters; this change would allow the use of the underscore for such unused parameters.
Function<String, Integer> noneByDefault = notUsed -> 0; // currently
Function<String, Integer> noneByDefault = _ -> 0; // proposed
This is a feature that is available in several other languages like Scala, Ruby or Prolog, however, this couldn’t be easily implemented in Java since, up to Java 7, the underscore was a valid identifier that could be in use somewhere else in the code. In order to introduce this change without forcing the rewrite of large amounts of code, a slow succession of steps was needed:
- Java 8: a warning is issued when the underscore is used as an identifier, discouraging developers from using it; underscore isn’t allowed as an identifier in a lambda (this doesn’t cause any backwards incompatibility since lambdas weren’t available prior to Java 8).
- Java 9: the previous warning is turned into an error, ensuring that the use of underscore as an identifier is removed from the Java code corpus.
- Java 10 (or later): underscore is re-introduced as an identifier, but only for unused parameters in lambda expressions.
Support for this change doesn’t seem unanimous though, judging by initial conversations; while some users like the brevity of the new proposals, some others prefer the use of explicit names. Further discussions are likely to happen to reach a consensus.
Shadowing of parameters
Perhaps the most contested of the newly proposed features. At the moment lambda parameters are not allowed to shadow outer variables, meaning names have to be chosen different from any variables accessible in the current scope; this is aligned to how other enclosing scopes work, like while
loops or if
statements:
String s = "hello";
if(finished) {
String s = "bye"; // error, s already defined
}
Predicate<String> ps = s -> s.isEmpty(); // error, s already defined
If the proposed changes go ahead, lambda parameters will be able to reuse and shadow existing identifiers. This could benefit users in some cases that won’t need to use other less direct names for their lambda parameters (the above example would typically be rewritten as s2 -> s2.isEmpty()
), however, it also has the potential to introduce subtle bugs like Roy Van Rijn, popular international speaker, suggested:
Map<String, Integer> map = /* ... */
String key = "theInitialKey";
map.computeIfAbsent(key, _ -> {
String key = "theShadowKey"; // shadow variable
return key.length();
});
Currently, the above code isn’t allowed, but under the new proposal it would. If the line marked as “shadow variable” was to be deleted, the code would still compile and run, although it would do something entirely different.
Many conversations are still necessary to assess whether the above will be introduced to Java, and in what form. However, what seems to be clear is that the introduction of lambdas in Java 8 was just the first step on a number of future improvements to the Java language.