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

9
  • Possibly related: Reference to method is ambiguous when using lambdas and generics Commented Aug 6, 2018 at 17:49
  • Do not overload methods to take different functional interfaces in the same argument position, because it causes confusion. Commented Aug 6, 2018 at 18:02
  • 1
    Most recent discussion in this field: stackoverflow.com/a/51582052/4611488 (featuring a notable quote by Joshua Bloch). Commented Aug 6, 2018 at 20:44
  • @StephanHerrmann I don't think that this is related to that question IMO, see my answer Commented Aug 7, 2018 at 9: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. Commented Aug 8, 2018 at 11:04

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

5 Comments

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 very interesting point about Optional::empty, I did not think that it would fail... but I don't understand your reasoning about it actually
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 funner: doStuff(Callable<Object> callable... and static class Gen<T> {} some usage doStuff(Gen<String>::new); // works, doStuff(Gen::new);// does not. Now I'll try to find the JLS specifics for this
@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 :(
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<>(); } );
}

5 Comments

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
When you have one doStuff the call is not ambiguous. Try just the doStuff(Callable) alone and you won't get an error either.
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?
@PeterLawrey this is wrong, the reasons are different: stackoverflow.com/a/51715030/1059372
@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<Object> callable), this overload is unambiguosly more specific and will be chosen. The problem is the ArrayList::new method reference might refer to multiple constructors.
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.

11 Comments

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 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 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"?
@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 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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.