Java编译错误:方法引用与重载相结合

Java编译错误:方法引用与重载相结合,java,java-8,compiler-errors,overloading,method-reference,Java,Java 8,Compiler Errors,Overloading,Method Reference,我有以下带有重载方法的类: 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 ArrayL

我有以下带有重载方法的类:

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);
}
这里执行方法
doStuff(Runnable)
,这是显而易见的

但是编译器如何决定在
test2
中执行这两个方法中的哪一个呢

为什么我可以使用lambda表达式而不是方法引用

test2
中的lambda表达式使用返回可调用的方法,为什么方法引用尝试使用其他方法

在我看来,这就像一个java bug

编辑: 它与
ArrayList
和/或它的泛型类型无关。当您有
可调用的
或任何其他对象时,也会出现相同的错误

提前谢谢

迪米特里

但是编译器如何决定在test2中执行这两个方法中的哪一个呢

public void test2(){
doStuff(()->newarraylist());
}
含蓄地

public void test2 () {
    doStuff ( () -> { return new ArrayList<>(); } );
}
public void test2(){
doStuff(()->{returnnewarraylist();});
}
并且只有Callable返回一个对象

为什么我可以使用lambda表达式而不是方法引用

可能是

public void test2 () {
    doStuff ( () -> { new ArrayList<>(); } );
}
public void test2(){
doStuff(()->{newarraylist();});
}
已编辑 看看这些例子:

Runnable r = ArrayList::new; // compiled
Callable c = ArrayList::new; // compiled
doStuff(ArrayList::new); // compile error, ambiguous
因此
ArrayList::new
被解释为
Runnable
Callable
。注意,不涉及lambda

下一个例子:

Runnable r = () -> new ArrayList<>(); // compiled
Callable c = () ->  new ArrayList<>(); // compiled
doStuff(() -> new ArrayList<>()); // compiled, the Callable one
Runnable r=()->new ArrayList();//汇编
可调用的c=()->new ArrayList();//汇编
doStuff(()->new ArrayList());//编译的,可调用的
()->new ArrayList()
传递给方法时

() -> { return new ArrayList<>(); }
()->{返回新的ArrayList();}
优先于

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

因此,调用了
可调用的
一个,没有任何歧义。

好吧,我们可以简化这一点:

// 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");
}
嗯。。。这将无法匹配。你可能会认为这是愚蠢的,因为只有当
go
是一个不带参数的函数时才有意义,在这种简单的情况下,你很容易做出这样的判断,这不适合编译器。本质上,这就像一把死锁:

为了知道调用哪个
doStuff
方法,我们需要知道调用哪个
go
;同时,为了了解要调用哪个
go
,我们需要知道要调用哪个
doStuff
,或者:

为了找到目标类型,我们需要解析方法,但是为了解析方法,我们需要知道目标类型


对于
ArrayList
具有多个构造函数的情况,也会发生同样的情况

是的,你是对的。
ArrayList::new
可能意味着这两件事。但是为什么新的ArrayList()可以工作呢?这也可能意味着两件事。如果我删除了
doStuff(Callable)
的声明,那么
test2
将使用另一种方法。那么为什么这不是一个编译错误呢。如果这两种情况下的行为不一致,@Dimitrios Begnis否,
()->new ArrayList()
()->{return new ArrayList();}
相同,它不是
()->{new ArrayList();}
。只有那些带有一行return语句的lambda才能省略方括号和
return
关键字。@zhh那么为什么
Runnable r=()->new ArrayList()
编译没有任何问题,但是
Runnable r2=()->{return new ArrayList();}
不会,如果您声称两者“相同”?@Dimitrios Begnis您不能说
Runnable
Callable
更可取,反之亦然。但是您可以说
()->{return new ArrayList();}
()->{new ArrayList();}
更可取,因为它们都是lambda。“方法引用是[…]易于阅读的lambda表达式[…]”,因此我们更容易理解方法引用。但我认为编译器将方法引用和lambda视为两件事。@zhh再次强调,在这个简单的例子中,这对您来说很容易,而不是编译器,因为它可能是100*100选项。他们必须在某个地方进行剪切,因此,他们没有为最多5种方法提供“resolution will work”之类的内容,而是将其完全剪切。不,这是错误的,就像我在对另一个答案的评论中所说的那样,当时我只有
doStuff(Runnable)
test2方法编译并使用
test2
。因此,在这种情况下,它不能返回任何内容,否则当您有一个
doStuff
调用时,它将导致编译错误。单独尝试一下
doStuff(Callable)
,你也不会出错。是的,我知道,我明白你的意思。但我想知道为什么编译器可以决定使用lambda表达式的方法,但不能使用方法引用来决定?因此,lambdas的规则似乎是将单行表达式编译为带有
return
的语句,但当需要runnable时,表达式将遵从不带
return
的方法。为什么方法引用没有这样的顺序?@PeterLawrey这是错误的,原因是不同的:@PeterLawrey我同意Eugene-如果您将
ArrayList::new
替换为可以明确解析的方法引用(例如
doStuff(Object::new);
),并将重载调整为
doStuff(可调用可调用)
,此重载明确更具体,将被选择。问题是
ArrayList::new
方法引用可能引用多个构造函数。可能相关:不要重载方法以在同一参数位置使用不同的函数接口,因为这会导致混淆。本领域的最新讨论ld:(引用约书亚·布洛赫(Joshua Bloch)的一句著名的话)。@StephanHerrmann我认为这与这个问题无关,请看我的答案@
() -> { new ArrayList<>(); }
// 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");
}
private static List<String> go() {
    return null;
}

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