11

I have the following class with an overloaded method:

import java.util.ArrayList;
import java.util.concurrent.Callable;

public abstract class Test {

  public void test1 () {
    doStuff (ArrayList::new); // compilation error
  }

  public void test2 () {
    doStuff ( () -> new ArrayList<> ());
  }

  public abstract void doStuff (Runnable runable);

  public abstract void doStuff (Callable<ArrayList<String>> callable);
}

The method test1 results in a compilation error with the error message The method doStuff(Runnable) is ambiguous for the type Test.

I've added a third method test3 which looks like this:

public void test3 () {
    doStuff ( () -> {
      new ArrayList<> ();
    });
  }

Here the method doStuff(Runnable) is executed which is obvious.

But how does the compiler decide which of the two methods is executed in test2?

Why can I use the lambda expression but not the method reference?

The lambda expression in test2 useses the method which returns the callable, why does the method reference try to use the other method?

This seems to me like a java bug.

Edit: It has nothing to do with the ArrayList and/or the generic type of it. Same error when you have Callable<String> or any other object.

Thanks in advance

Dimitri

Dimitrios Begnis
  • 823
  • 6
  • 16
  • Possibly related: [Reference to method is ambiguous when using lambdas and generics](https://stackoverflow.com/q/29323520) – Pshemo Aug 06 '18 at 17:49
  • Do not overload methods to take different functional interfaces in the same argument position, because it causes confusion. – Oleksandr Pyrohov Aug 06 '18 at 18:02
  • 1
    Most recent discussion in this field: https://stackoverflow.com/a/51582052/4611488 (featuring a notable quote by Joshua Bloch). – Stephan Herrmann Aug 06 '18 at 20:44
  • @StephanHerrmann I don't think that this is related to that question IMO, see my answer – Eugene Aug 07 '18 at 09:04
  • 1
    @Eugene it seems this current question is even more grave than the one I linked, but the common theme is: you should never get into this trouble (or should I say mayhem?) if you follow the advice by Joshua Bloch. And if you read Gilad's blog post, you will think twice (at least) before using any form of overloading. – Stephan Herrmann Aug 08 '18 at 11:04
  • @StephanHerrmann can not always agree - a method reference is more readable, creates one fewer method than a lambda internally, and is always suggested but my IDE of choice. I'd like to understand things, before resorting to a lambda, at least – Eugene Aug 08 '18 at 11:24
  • @Eugene I never suggested method references are bad, I just warned against overloading - overloading in general and most severely overloading based on different functional interfaces. Method references may add to the complexity, so if you have overloading on both sides that's Trouble with a capital T. But this is not the fault of method references, IMHO. BTW, method references may need to be translated to lambdas internally, so the simpler internal code is not guaranteed. – Stephan Herrmann Aug 08 '18 at 20:31
  • @StephanHerrmann that is one interesting point about method references translated to lambdas internally, I always thought that the `MethodHandle` approach was a good one... – Eugene Aug 08 '18 at 20:40
  • btw you [could accept one answer here](https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work) – Eugene Sep 03 '18 at 19:59

3 Answers3

4

Well, we can simplify this:

// takes a Runnable
public static void doStuff(Runnable runable) {
    System.out.println("Runnable");
}

// takes a Callable
public static void doStuff(Callable<List<String>> callable) {
    System.out.println("Callable");
}

And two extra methods that are overloads.

private static List<String> go() {
    return null;
}

private static List<String> go(int i) {
    return null;
}

What do you think will happen if you call this:

doStuff(YourClass::go);

Yeah... this will fail to match. And you might think that this is stupid as it only makes sense that go is the one that takes no arguments, it is easy for you in this simple situation to make this judgment, it's not for the compiler. In essence this is like a dead-lock:

In order to know which doStuff method to call, we need to know which go to call; and at the same time to understand which go to call we need to know which doStuff to call, or:

we need to resolve the method in order to find the target type, but we need to know the target type in order to resolve the method.

Same thing happens in your case with ArrayList having more than one constructors...

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • I agree that it has to do with having to resolve both the method and the target type- this can be shown by the fact that the resolution is unambiguous for `Object::new` (with adjusted `Callable` type parameters). But the multiple constructors are not the only reason for the ambiguity here: `Optional::empty` is also ambiguous, I guess due to the inference of the type parameter. `String::new` is ambiguous and might be a better exemple for your point - multiple eligible reference targets. – Hulk Aug 07 '18 at 09:43
  • @Hulk very interesting point about `Optional::empty`, I did not think that it would fail... but I don't understand your reasoning about it actually – Eugene Aug 07 '18 at 11:56
  • I'm not sure about the reasons either - my guess here was that it has to do with the inference of its generic type. I just wanted to suggest that it might be easier to reason about choosing a non-generic method like in the `String::new`-case. – Hulk Aug 07 '18 at 12:19
  • 1
    @Hulk funner: `doStuff(Callable callable...` and `static class Gen {}` some usage `doStuff(Gen::new); // works`, `doStuff(Gen::new);// does not`. Now I'll try to find the JLS specifics for this – Eugene Aug 07 '18 at 13:14
  • @Holger oh damn :| I still don't understand why the generics part of my previous comment does not work. I mean I do understand that it does not, but I *really* can't tell why and it seems I've read lots of your answers about this :( – Eugene Aug 21 '18 at 18:41
2

But how does the compiler decide which of the two methods is executed in test2?

public void test2 () {
    doStuff ( () -> new ArrayList<> ());
}

is implicitly

public void test2 () {
    doStuff ( () -> { return new ArrayList<>(); } );
}

and only Callable returns an object.

Why can I use the lambda expression but not the method reference?

It could be

public void test2 () {
    doStuff ( () -> { new ArrayList<>(); } );
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • No this is wrong, like I said in the comment on the other answer when I just have `doStuff(Runnable)` the `test2` method compiles and uses `test2`. Soin thiscase it can't be returning anything otherwise it should result in a compilation error – Dimitrios Begnis Aug 06 '18 at 15:10
  • 1
    When you have one `doStuff` the call is not ambiguous. Try just the `doStuff(Callable)` alone and you won't get an error either. – Peter Lawrey Aug 06 '18 at 15:12
  • 1
    Yeah sure I know and I got your point. But I wonder why the complier can decide which method to use with the lambda expression but isn't able to decide it with the method reference? So it seems that the rule for lambdas is that the one line expression is compiled to a statement with `return` but when a runnable is required the expression is complied to a method without a `return`. Why does the method reference doesn't have such a order? – Dimitrios Begnis Aug 06 '18 at 15:17
  • 1
    @PeterLawrey this is wrong, the reasons are different: https://stackoverflow.com/a/51715030/1059372 – Eugene Aug 06 '18 at 20:17
  • 1
    @PeterLawrey I agree with Eugene - if you replace `ArrayList::new` with a reference to a method that can be unambiguosly resolved (e.g. `doStuff(Object::new);`) and adjust the overload to `doStuff(Callable callable)`, this overload is unambiguosly more specific and will be chosen. The problem is the `ArrayList::new` method reference might refer to multiple constructors. – Hulk Aug 07 '18 at 10:39
2

EDITED

Look at those examples:

Runnable r = ArrayList::new; // compiled
Callable c = ArrayList::new; // compiled
doStuff(ArrayList::new); // compile error, ambiguous

So ArrayList::new is interpreted as both Runnable and Callable. Note there is no lambda involved.

Next example:

Runnable r = () -> new ArrayList<>(); // compiled
Callable c = () ->  new ArrayList<>(); // compiled
doStuff(() -> new ArrayList<>()); // compiled, the Callable one

When passing () -> new ArrayList<>() to a method

() -> { return new ArrayList<>(); }

is preferred than

() -> { new ArrayList<>(); }

So, the Callable one is invoked and there is nothing ambiguous.

zhh
  • 2,346
  • 1
  • 11
  • 22
  • 1
    Yes you are right `ArrayList::new` can be mean both things. But why does `() -> new ArrayList<>()` work then? This can also mean both things. If I remove the declaration of `doStuff(Callable)` then `test2` uses the other method. So why is this then not a compile error. IMHO the behavior is inconsistent between the two cases. – Dimitrios Begnis Aug 06 '18 at 15:07
  • 1
    @Dimitrios Begnis No, ```() -> new ArrayList<>()``` is the same as ```() -> { return new ArrayList<>(); }```, it is **not** ```() ->{ new ArrayList<>(); }```. Only those lambda with one line **return** statement can leave out brackets and the ```return``` keyword. – zhh Aug 06 '18 at 15:09
  • 1
    @zhh Then why `Runnable r = () -> new ArrayList<> ();` compiles without any problem but `Runnable r2 = () -> { return new ArrayList<>(); }` does not if as you claim both are "the same"? – Pshemo Aug 06 '18 at 15:12
  • But why the method `test2` works then with the `doStuff(Runnable)` declaration? If it returns always the `ArrayList` it should result in a compilation error because a runnable can not return an `ArrayList`. – Dimitrios Begnis Aug 06 '18 at 15:13
  • @zhh So you agree with me? – Dimitrios Begnis Aug 06 '18 at 15:24
  • Great! This is exactly what I thought. But like I said in my first comment I think this inconsistent / a bug. Why does the method reference does not has a prefered order? Is there a logical reason or any explanation other then "That's just how it is"? On https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html it says: "method references are [...] easy-to-read lambda expressions [...]". When this statement is true then this is clearly a bug because the behaviour should be exactly the same. – Dimitrios Begnis Aug 06 '18 at 16:00
  • 1
    @Dimitrios Begnis You can't say ```Runnable``` is preferred than ```Callable``` or vice versa. But you can say ```() -> { return new ArrayList<>(); }``` is preferred than ```() -> { new ArrayList<>(); }```, since they are both lambdas. "method references are [...] easy-to-read lambda expressions [...]", so that it is easier for us to understand method reference. But I think the compiler treats method reference and lambda as two things. – zhh Aug 06 '18 at 16:05
  • 1
    I just found this: "method references are a feature that is just supported for unambiguous method references only". Here is a good example why this is so. Thanks! https://blog.jooq.org/2015/07/28/java-8s-method-references-put-further-restrictions-on-overloading/ – Dimitrios Begnis Aug 06 '18 at 16:08
  • @zhh this is somehow wrong... https://stackoverflow.com/a/51715030/1059372 – Eugene Aug 06 '18 at 20:18
  • @Eugene You're right. But I still don't understand that why ```ArrayList(int)``` is considered since it matches none of these ```doStuff```s. – zhh Aug 07 '18 at 01:39
  • 2
    @zhh again, it is easy for you in this simple case, not for the compiler, when it could have been 100*100 options. They had to cut it somewhere, so instead of providing something like "resolution would work" for up to 5 methods for example, they cut it entirely. – Eugene Aug 07 '18 at 07:12