I have an overloaded method that takes a Consumer and a Function object respectively and returns a generic type that matches the corresponding Consumer/Function. I thought this would be fine, but when I try to call either method with a lambda expression I get an error indicating the reference to the method is ambiguous.
Based on my reading of JLS §15.12.2.1. Identify Potentially Applicable Methods: it seems like the compiler should know that my lambda with a void block matches the Consumer method and my lambda with a return type matches the Function method.
I put together the following sample code that fails to compile:
import java.util.function.Consumer;
import java.util.function.Function;
public class AmbiguityBug {
public static void main(String[] args) {
doStuff(getPattern(x -> System.out.println(x)));
doStuff(getPattern(x -> String.valueOf(x)));
}
static Pattern<String, String> getPattern(Function<String, String> function) {
return new Pattern<>(function);
}
static ConsumablePattern<String> getPattern(Consumer<String> consumer) {
return new ConsumablePattern<>(consumer);
}
static void doStuff(Pattern<String, String> pattern) {
String result = pattern.apply("Hello World");
System.out.println(result);
}
static void doStuff(ConsumablePattern<String> consumablePattern) {
consumablePattern.consume("Hello World");
}
public static class Pattern<T, R> {
private final Function<T, R> function;
public Pattern(Function<T, R> function) {
this.function = function;
}
public R apply(T value) {
return function.apply(value);
}
}
public static class ConsumablePattern<T> {
private final Consumer<T> consumer;
public ConsumablePattern(Consumer<T> consumer) {
this.consumer = consumer;
}
public void consume(T value) {
consumer.accept(value);
}
}
}
I also found a similar stackoverflow post that turned out to be a compiler bug. My case is very similar, though a bit more complicated. To me this still looks like a bug, but I wanted to make sure I am not misunderstanding the language spec for lambdas. I'm using Java 8u45 which should have all of the latest fixes.
If I change my method calls to be wrapped in a block everything seems to compile, but this adds additional verbosity and many auto-formatters will reformat it into multiple lines.
doStuff(getPattern(x -> { System.out.println(x); }));
doStuff(getPattern(x -> { return String.valueOf(x); }));
This line is definitely ambiguous:
doStuff(getPattern(x -> String.valueOf(x)));
Reread this from the linked JLS chapter:
A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:
The arity of the target type's function type is the same as the arity of the lambda expression.
If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).
If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).
In your case for Consumer you have a statement expression as any method invocation can be used as statement expression even if the method is non-void. For example, you can simply write this:
public void test(Object x) {
String.valueOf(x);
}
It makes no sense, but compiles perfectly. Your method may have a side-effect, compiler doesn't know about it. For example, were it List.add which always returns true and nobody cares about its return value.
Of course this lambda also qualifies for Function as it's an expression. Thus it's ambigue. If you have something which is an expression, but not a statement expression, then the call will be mapped to Function without any problem:
doStuff(getPattern(x -> x == null ? "" : String.valueOf(x)));
When you change it to { return String.valueOf(x); }, you create a value-compatible block, so it matches the Function, but it does not qualify as a void-compatible block. However you may have problems with blocks as well:
doStuff(getPattern(x -> {throw new UnsupportedOperationException();}));
This block qualifies both as a value-compatible and a void-compatible, thus you have an ambiguity again. Another ambigue block example is an endless loop:
doStuff(getPattern(x -> {while(true) System.out.println(x);}));
As for System.out.println(x) case it's a little bit tricky. It surely qualifies as statement expression, so can be matched to Consumer, but seems that it matches to expression as well as spec says that method invocation is an expression. However it's an expression of limited use like 15.12.3 says:
If the compile-time declaration is void, then the method invocation must be a top level expression (that is, the Expression in an expression statement or in the ForInit or ForUpdate part of a for statement), or a compile-time error occurs. Such a method invocation produces no value and so must be used only in a situation where a value is not needed.
So compiler perfectly follows the specification. First it determines that your lambda body is qualified both as an expression (even though its return type is void: 15.12.2.1 makes no exception for this case) and a statement expression, so it's considered an ambiguity as well.
Thus for me both statements compile according to the specification. ECJ compiler produces the same error messages on this code.
In general I'd suggest you to avoid overloading your methods when your overloads has the same number of parameters and has the difference only in accepted functional interface. Even if these functional interfaces have different arity (for example, Consumer and BiConsumer): you will have no problems with lambda, but may have problems with method references. Just select different names for your methods in this case (for example, processStuff and consumeStuff).
Related
This question already has answers here:
Why do Consumers accept lambdas with statement bodies but not expression bodies?
(3 answers)
Why does a Java method reference with return type match the Consumer interface?
(2 answers)
Closed 4 years ago.
It's better to express this behavior in the code:
List<Integer> list= new ArrayList<>();
Stream.of(1,2,3).forEach(i -> list.add(1)); // COMPILES
Stream.of(1,2,3).forEach(i -> true); // DOES NOT COMPILE!
forEach(...) accepts Consumer, but why does the first example compile if List interface has following signature boolean add(E e)? whereas the second yields:
bad return type in lambda expression: boolean cannot be converted to
void
Though you might be looking just for
Stream.of(1,2,3).forEach(list::add); // adding all to `list`
why does the first example compile if List interface has following
signature boolean add(E e)
Primarily because the return type of the method is ignored in the first call. This is what it expands to:
Stream.of(1,2,3).forEach(new Consumer<Integer>() {
#Override
public void accept(Integer i) {
list.add(1); // ignored return type
}
}); // COMPILES
On the other hand, the other lambda representation is more like a Predicate(which is also a FunctionalInterface) represented as returning true always from its test method. If you even try to represent it as a Consumer, it might just look like
Stream.of(1,2,3).forEach(new Consumer<Integer>() {
#Override
public void accept(Integer i) {
return true; // you can correlate easily now why this wouldn't compile
}
}); // DOES NOT COMPILE!
To add to the design basis via a comment from Brian
Java allows you to call a method and ignore the return value (a method invocation expression as a statement). Since we allow this at
the invocation, we also allow this when adapting a method to a
functional interface whose arguments are compatible but the functional
interface is void-returning.
Edit: To put it in his own words as close to the language spec:
More precisely, list.add(x) is a statement expression, and
therefore is void-compatible. true is not a statement expression,
and therefore not void-compatible. forEach(Consumer) requires a
void-compatible lambda.
forEach() as a Consumer as the one and only parameter therefore you need to give an implemention of the method accept() which returns void so on the second example you're implementing a method with the given assinature static boolean method(int).
The Special Void-Compatibility Rule (JLS) says that a lambda that returns void may accept a statement whose return is non-void. In this case, the return is discarded. hence this compiles.
Stream.of(1,2,3).forEach(i -> list.add(1)); // return value of add is ignored
To word it a bit differently by quoting Java-8 in Action book:
If a lambda has a statement expression as its body, it’s compatible
with a function descriptor that returns void (provided the parameter
list is compatible too). For example, both of the following lines are
legal even though the method add of a List returns a boolean and not
void as expected in the Consumer context (T -> void):
// Predicate has a boolean return
Predicate<String> p = s -> list.add(s);
// Consumer has a void return
Consumer<String> b = s -> list.add(s);
As for the second example, this is explicitly trying to return a boolean where it's not expected hence not possible.
Stream.of(1,2,3).forEach(i -> true);
in other words:
Stream.of(1,2,3).forEach(i -> {return true;});
As you already know forEach takes a Consumer so attempting to "explicitly" return a boolean should & will yield a compilation error.
This question already has answers here:
Lambda 'special void-compatibility rule' - statement expression
(3 answers)
Why do Consumers accept lambdas with statement bodies but not expression bodies?
(3 answers)
Why does a Java method reference with return type match the Consumer interface?
(2 answers)
Consumer lambda in Java 8 [duplicate]
(2 answers)
Closed 4 years ago.
Given this method:
private static Integer return0() {
return 0;
}
I discovered a weird property of the following lambda expression:
() -> return0();
Does it actually return the value from the function it calls (which would make it a Supplier-Interface) or does it not return the value but only calls the function and returns void (which would make it a Runnable-Interface). Intuitively, I would expect the first case to be correct but could live with the second.
When trying to assign the statement:
Supplier<Integer> supplier2 = () -> return0();
Runnable runnable2 = () -> return0();
It turns out both lines do compile! Why would they allow that? It is completely ambiguous and really confusing!
EDIT:
Here is more code to demonstrate what I mean by confusing/ambigous:
public static void main(String[] args) {
callMe(() -> return0());
}
private static Integer return0() {
return 0;
}
private static void callMe(Supplier<Integer> supplier) {
System.out.println("supplier!");
}
private static void callMe(Runnable runnable) {
System.out.println("runnable!");
}
This all compiles well and upon execution prints "supplier!". I do not find it particularly intuitive that the first method is chosen but rather arbitrary.
The relevant part of the spec is Sec 15.27.3 (emphasis mine):
A lambda expression is congruent with a function type if all of the following are true:
The function type has no type parameters.
The number of lambda parameters is the same as the number of parameter types of the function type.
If the lambda expression is explicitly typed, its formal parameter types are the same as the parameter types of the function type.
If the lambda parameters are assumed to have the same types as the function type's parameter types, then:
If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.
If the function type's result is a (non-void) type R, then either i) the lambda body is an expression that is compatible with R in an assignment context, or ii) the lambda body is a value-compatible block, and each result expression (§15.27.2) is compatible with R in an assignment context.
Your lambda body is a statement expression, and the function type's result is void.
In other words, it would be fine for you to write:
return0();
and ignore the return value in "regular" code, so it's fine to ignore the result value in a lambda too.
In terms of the question over ambiguity of overloads, there is no ambiguity in this case (it's easy to construct a case where there is ambiguity, e.g. another overload with a parameter that looks like Supplier but is a different interface, i.e. takes no parameters, returns a value).
You would have to read the spec in detail for the precise reasoning, but I think the most relevant section is Sec 15.12, which describes method invocation expressions, and the most useful quote from that is in Sec 15.12.2.5, which deals with selecting the most-specific overload:
The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error.
You can use a Supplier<Integer> in place of a Runnable (with a bit of a hand-wavy fudge) because you can simply ignore the return value; you can't use a Runnable in place of a Supplier<Integer> because it doesn't have a return value.
So a method taking the Supplier<Integer> is more specific than the method taking the Runnable, hence that is the one which is invoked.
If you get confused with lambda expressions, replace them with anonymous classes for a better understanding (IntelliJ IDEA can easily help you with that). The following code snippets are completely valid:
Supplier<Integer> supplier2 = () -> return0() is equivalent to:
Supplier<Integer> supplier2 = new Supplier<Integer>() {
#Override
public Integer get() {
return return0();
}
};
Runnable runnable2 = () -> return0() is equivalent to:
Runnable runnable2 = new Runnable() {
#Override
public void run() {
return0();
}
};
public static void main(String[] args) throws Exception
{
Supplier<Integer> consumer2 = Trial::return0;
Runnable runnable2 = Trial::return0;
run(Trial::return0);
}
private static Integer return0() {
return 0;
}
private static int run(Supplier<Integer> a)
{
System.out.println("supplier");
return a.get();
}
private static void run(Runnable r)
{
System.out.println("runnable");
r.run();
}
As far as method overloading is concerned, this code in class Trial prints "supplier".
I've noticed a weird behavior for overloading methods with generics and lambdas. This class works fine:
public <T> void test(T t) { }
public <T> void test(Supplier<T> t) { }
public void test() {
test("test");
test(() -> "test");
}
No ambiguous method call. However, changing it to this makes the second call ambiguous:
public <T> void test(Class<T> c, T t) { }
public <T> void test(Class<T> c, Supplier<T> t) { }
public void test() {
test(String.class, "test");
test(String.class, () -> "test"); // this line does not compile
}
How can this be? Why would adding another argument cause the method resolution to be ambiguous? Why can it tell the difference between a Supplier and an Object in the first example, but not the second?
Edit: This is using 1.8.0_121. This is the full error message:
error: reference to test is ambiguous
test(String.class, () -> "test");
^
both method <T#1>test(Class<T#1>,T#1) in TestFoo and method <T#2>test(Class<T#2>,Supplier<T#2>) in TestFoo match
where T#1,T#2 are type-variables:
T#1 extends Object declared in method <T#1>test(Class<T#1>,T#1)
T#2 extends Object declared in method <T#2>test(Class<T#2>,Supplier<T#2>)
/workspace/com/test/TestFoo.java:14: error: incompatible types: cannot infer type-variable(s) T
test(String.class, () -> "test");
^
(argument mismatch; String is not a functional interface)
where T is a type-variable:
T extends Object declared in method <T>test(Class<T>,T)
If my understanding of chapters 15 and 18 of the JLS for Java SE 8 is correct, the key to your question lies in the following quote from paragraph 15.12.2:
Certain argument expressions that contain implicitly typed lambda expressions (§15.27.1) or inexact method references (§15.13.1) are ignored by the applicability tests, because their meaning cannot be determined until a target type is selected.
When a Java compiler encounters a method call expression such as test(() -> "test"), it has to search for accessible (visible) and applicable (i.e. with matching signature) methods to which this method call can be dispatched. In your first example, both <T> void test(T) and <T> void test(Supplier<T>) are accessible and applicable w.r.t. the test(() -> "test") method call. In such cases, when there are multiple matching methods, the compiler attempts to determine the most specific one. Now, while this determination for generic methods (as covered in
JLS 15.12.2.5 and JLS 18.5.4) is quite complicated, we can use the intuition from the opening of 15.12.2.5:
The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error.
Since for any valid call to <T> void test(Supplier<T>) we can find a corresponding instantiation of the type parameter T in <T> void test(T), the former is more specific than the latter.
Now, the surprising part is that in your second example, both <T> void test(Class<T>, Supplier<T>) and <T> void test(Class<T>, T) are considered applicable for method call test(String.class, () -> "test"), even though it's clear to us, that the latter shouldn't be. The problem is, that the compiler acts very conservatively in the presence of implicitly typed lambdas, as quoted above. See in particular JLS 18.5.1:
A set of constraint formulas, C, is constructed as follows.
...
To test for applicability by strict invocation:
If k ≠ n, or if there exists an i (1 ≤ i ≤ n) such that e_i is pertinent to applicability (§15.12.2.2) (...) Otherwise, C includes, for all i (1 ≤ i ≤ k) where e_i is pertinent to applicability, ‹e_i → F_i θ›.
To test for applicability by loose invocation:
If k ≠ n, the method is not applicable and there is no need to proceed with inference.
Otherwise, C includes, for all i (1 ≤ i ≤ k) where e_i is pertinent to applicability, ‹e_i → F_i θ›.
and JLS 15.12.2.2:
An argument expression is considered pertinent to applicability for a potentially applicable method m unless it has one of the following forms:
An implicitly typed lambda expression (§15.27.1).
...
So, the constraints from implicitly typed lambdas passed as arguments take no part in resolving type inference in the context of method applicability checks.
Now, if we assume that both methods are applicable, the problem - and the difference between this and the previous example - is that none of this methods is more specific. There exist calls which are valid for <T> void test(Class<T>, Supplier<T>) but not for <T> void test(Class<T>, T), and vice versa.
This also explains why test(String.class, (Supplier<String>) () -> "test"); compiles, as mentioned by #Aominè in the comment above. (Supplier<String>) () -> "test") is an explicitly typed lambda, and as such is considered pertinent to applicability, the compiler is able to correctly deduce, that only one of these methods is applicable, and no conflict occurs.
This problem has kept buzzing me since some days now.
I won't go into JLS details but more on a logical explanation.
What if i'm calling:
test(Callable.class, () -> new Callable() {
#Override
public Object call() throws Exception {
return null;
}
});
Which method the compiler can choose
public <T> void test(Class<T> c, T t)
same as below >>
public <Callable> void test(Class<Callable> c, Callable<Callable> t)
Callable is a #FunctionalInterface so it seems perfectly valid
public <T> void test(Class<T> c, Supplier<T> t)
same as below >>
public <Callable> void test(Class<Callable> c, Supplier<Callable> t)
Supplier is a #FunctionalInterface so it seems also perfectly valid
When providing a class which is a FunctionalInterface then the two calls are valid and neither of the calls are more specific than the other which lead to an ambiguous method message.
Then what about the functions with only one parameter:
What if i'm calling:
test(() -> new Callable() {
#Override
public Object call() throws Exception {
return null;
}
})
Which method the compiler can choose
public <T> void test(T t)
A lambda expression has to be mapped to a #FunctionalInterface but in this case we aren't providing any explicit type to map so this call can't be valid
public <T> void test(Supplier<T> t)
Supplier is a #FunctionalInterface so the lambda expression can be mapped to a #FunctionalInterface (Supplier), the call is then valid
Only one of the two methods is applicable
During compilation all generic types are erased, so Object is used and an Object can be a FunctionalInterface
I hope someone more JLS compliant than me can validate or correct my explanation it would be great.
I just started to learn about Lambda Expression and I did something like this:
public class LambdaTest {
public static void main(String[] args) {
int num = returnNumber((num) -> { return 4 });
}
public static int returnNumber(int num) {
return num;
}
}
But it gives me an error: "invalid tokens". Here is an image:
Can someone please explain me what's wrong? It's just a test.
I have Java 1.8 supported in my Eclipse installation (Luna 4.4).
There are a few restrictions on what can be done in the body of the lambda, most of which are pretty intuitive—a lambda body can’t “break” or “continue” out of the lambda, and if the lambda returns a value, every code path must return a value or throw an exception, and so on. These are much the same rules as for a standard Java method, so they shouldn’t be too surprising.
Reference : http://www.oracle.com/technetwork/articles/java/architect-lambdas-part1-2080972.html
The method's body has the effect of evaluating the lambda body, if it
is an expression, or of executing the lambda body, if it is a block;
if a result is expected, it is returned from the method.
If the function type's result is void, the lambda body is either a
statement expression or a void-compatible block.
Reference : http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27.4
The syntax error is pretty straight-forward. It says that there is a ; missing after a statement and that’s in no ways other than with statements outside lambda expressions. If you write (num) -> { return 4 }, there must be a semicolon after return 4 as it would have to be at every place where you can write return 4 (I’m astounded why nobody else was capable of telling you that).
You can write a lambda expression returning an int in two ways, either like (num) -> { return 4; } or, much simpler, as num -> 4 (here, without semicolon).
But, of course, you can’t call returnNumber(int num) with a lambda expression as parameter as it expects an int and not a functional interface. Your compiler will tell you that once you fixed the structural syntax error of the missing semicolon.
Lambdas are just implementations for method of functional interface (interfaces with one abstract methods), but in case of
returnNumber(int num)
lambdas can't be used because:
int is not an functional interface
so lambdas can't be used to supply implementation of its only abstract method (since primitive types are primitive - they have no methods).
Before lambdas to execute method like
method(SomeInterface si){...}
you would need to either create separate class which would implement this interface
class MyClass implements SomeInterface{
void method(Type1 arg1, Type2 arg2){
//body
}
}
...
MyClass mc = new MyClass();
method(md);
or add its implementation on fly by creating anonymous class
method(new SomeInterface{
void method(Type1 arg1, Type2 arg2){
//body
}
});
Lambdas can shorten this process by skipping this idiom and letting you focus only on arguments types, and implementation.
So instead of
new SomeInterface{
void method(Type1 arg1, Type2 arg2){
//body
}
}
you can simply write
(Type1 arg1, Type2 arg2) -> { body } // you can actually shorten it even farther
// but that is not important now
I am trying to use java 8 features. While reading official tutorial I came across this code
static void invoke(Runnable r) {
r.run();
}
static <T> T invoke(Callable<T> c) throws Exception {
return c.call();
}
and there was a question:
Which method will be invoked in the following statement?"
String s = invoke(() -> "done");
and answer to it was
The method invoke(Callable<T>) will be invoked because that method returns a value; the method invoke(Runnable) does not. In this case, the type of the lambda expression () -> "done" is Callable<T>.
As I understand since invoke is expected to return a String, it calls Callable's invoke. But, not sure how exactly it works.
Let's take a look at the lambda
invoke(() -> "done");
The fact that you only have
"done"
makes the lambda value compatible. The body of the lambda, which doesn't appear to be an executable statement, implicitly becomes
{ return "done";}
Now, since Runnable#run() doesn't have a return value and Callable#call() does, the latter will be chosen.
Say you had written
invoke(() -> System.out.println());
instead, the lambda would be resolved to an instance of type Runnable, since there is no expression that could be used a return value.