【问题标题】:Java Compile Error: Method reference in combination with overloadingJava 编译错误:方法引用与重载相结合
【发布时间】:2018-08-06 14:44:08
【问题描述】:

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

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);
}

方法test1 导致编译错误并显示错误消息 The method doStuff(Runnable) is ambiguous for the type Test.

我添加了第三种方法test3,如下所示:

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

这里执行了doStuff(Runnable)方法,这是显而易见的。

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

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

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

在我看来,这就像一个 java 错误。

编辑: 它与ArrayList 和/或它的泛型类型无关。当您有 Callable&lt;String&gt; 或任何其他对象时,同样的错误。

提前致谢

迪米特里

【问题讨论】:

  • 不要重载方法以在相同的参数位置采用不同的功能接口,因为这会导致混淆。
  • 该领域的最新讨论:stackoverflow.com/a/51582052/4611488(引用 Joshua Bloch 的著名引言)。
  • @StephanHerrmann 我认为这与 IMO 的那个问题无关,请参阅我的回答
  • @Eugene 似乎这个当前的问题比我链接的问题更严重,但共同的主题是:如果你遵循约书亚布洛赫的建议。如果您阅读 Gilad 的博文,在使用任何形式的重载之前,您会(至少)三思而后行。

标签: java java-8 compiler-errors overloading method-reference


【解决方案1】:

好吧,我们可以简化一下:

// 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);

是的...这将无法匹配。您可能会认为这很愚蠢,因为只有 go 是不带参数的那个才有意义,在这种简单的情况下你很容易做出这个判断,而不是编译器。本质上,这就像一个死锁:

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

我们需要解析方法才能找到目标类型,但我们需要知道目标类型才能解析方法。

ArrayList 拥有多个构造函数时也会发生同样的情况......

【讨论】:

  • 我同意这与必须解析方法和目标类型有关 - 这可以通过 Object::new 的分辨率明确这一事实来证明(调整 Callable 类型参数)。但是多个构造函数并不是这里模棱两可的唯一原因:Optional::empty 也是模棱两可的,我猜是由于类型参数的推断。 String::new 含糊不清,可能是您的观点的更好示例 - 多个符合条件的参考目标。
  • @Hulk 关于Optional::empty 非常有趣的一点,我不认为它会失败......但我实际上不明白你的推理
  • 我也不确定原因——我的猜测是它与泛型类型的推断有关。我只是想建议,在String::new-case 中选择非泛型方法可能更容易推理。
  • @Hulk funner: doStuff(Callable&lt;Object&gt; callable...static class Gen&lt;T&gt; {} 一些用法 doStuff(Gen&lt;String&gt;::new); // works, doStuff(Gen::new);// does not。现在我将尝试为此找到 JLS 细节
  • @Holger 哦该死的:|我仍然不明白为什么我之前评论的泛型部分不起作用。我的意思是我确实明白它没有,但我真的不知道为什么,似乎我已经阅读了很多关于这个的答案:(
【解决方案2】:

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

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

是隐含的

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

并且只有 Callable 返回一个对象。

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

可能是

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

【讨论】:

  • 不,这是错误的,就像我在另一个答案的评论中所说的那样,当我只有 doStuff(Runnable) 时,test2 方法编译并使用 test2。所以在这种情况下它不能返回任何东西,否则会导致编译错误
  • 当您有一个doStuff 时,呼叫就不会模棱两可了。单独尝试doStuff(Callable) 也不会出错。
  • 是的,我知道,我明白你的意思。但我想知道为什么编译器可以决定与 lambda 表达式一起使用哪种方法,但无法通过方法引用来决定它?因此,似乎 lambdas 的规则是将单行表达式编译为带有return 的语句,但是当需要可运行时,表达式将编译为没有return 的方法。为什么方法引用没有这样的顺序?
  • @PeterLawrey 这是错误的,原因不同:stackoverflow.com/a/51715030/1059372
  • @PeterLawrey 我同意 Eugene - 如果您将 ArrayList::new 替换为对可以明确解决的方法的引用(例如 doStuff(Object::new);)并将重载调整为 doStuff(Callable&lt;Object&gt; callable),则此重载为明确更具体,将被选中。问题是ArrayList::new 方法引用可能引用了多个构造函数。
【解决方案3】:

已编辑

看看这些例子:

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

所以ArrayList::new 被解释为RunnableCallable。请注意,不涉及 lambda。

下一个例子:

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

() -&gt; new ArrayList&lt;&gt;() 传递给方法时

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

优于

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

所以,Callable 被调用,没有任何歧义。

【讨论】:

  • 是的,你是对的 ArrayList::new 可以是两回事。但是为什么() -&gt; new ArrayList&lt;&gt;() 工作呢?这也可能意味着两件事。如果我删除doStuff(Callable) 的声明,那么test2 使用另一种方法。那么为什么这不是编译错误。恕我直言,这两种情况的行为不一致。
  • @Dimitrios Begnis 不,() -&gt; new ArrayList&lt;&gt;()() -&gt; { return new ArrayList&lt;&gt;(); } 相同,它不是 () -&gt;{ new ArrayList&lt;&gt;(); }。只有单行 return 语句的 lambda 可以省略括号和 return 关键字。
  • @zhh 那么为什么Runnable r = () -&gt; new ArrayList&lt;&gt; (); 编译没有任何问题,但Runnable r2 = () -&gt; { return new ArrayList&lt;&gt;(); } 没有,如果你声称两者“相同”?
  • @Dimitrios Begnis 你不能说RunnableCallable 更受欢迎,反之亦然。但是你可以说() -&gt; { return new ArrayList&lt;&gt;(); }() -&gt; { new ArrayList&lt;&gt;(); } 更受欢迎,因为它们都是lambda。 “方法引用是 [...] 易于阅读的 lambda 表达式 [...]”,以便我们更容易理解方法引用。但我认为编译器将方法引用和 lambda 视为两件事。
  • @zhh,在这种简单的情况下对你来说很容易,而不是对编译器来说,因为它本来可以是 100*100 选项。他们不得不在某个地方进行删减,因此他们没有为最多 5 种方法提供诸如“分辨率可行”之类的内容,而是将其完全删减。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-15
  • 2018-08-27
  • 1970-01-01
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多