【问题标题】:Java Ambiguous method when a method has a generic parameter and other method has a generic lambda当一个方法具有泛型参数而其他方法具有泛型 lambda 时的 Java 不明确方法
【发布时间】:2020-07-21 17:32:33
【问题描述】:

考虑以下几点:

    public void run() {
        run(o -> {});
    }

    //replace CharSequence with whatever except Object
    public <E extends CharSequence> void run(E e) {
    }
    public <E extends CharSequence> void run(Consumer<E> e){
    }

编译器会抱怨java reference to run is ambiguous,即使传递的参数显然是一个 lambda,而这两个方法中只有一个是 lambda

问题是为什么会这样?它是一个错误吗?应该用什么替换签名以实现相同的行为?

额外: IntelliJ 说(Main 类无关紧要):

Ambiguous method call. Both
run(CharSequence) in Main and
run(Consumer<CharSequence>) in Main match

注意: 用除java.lang.Object 之外的任何东西替换CharSequence 都会产生同样的问题!

另请注意: 即使两个泛型不同并且彼此不相关也没关系!只要他们不是java.lang.Object

所以,这不会编译 ether:

    public void run() {
        run(o -> {});
    }
    public <E extends ArrayList> void run(E e) {
    }
    public <E extends String> void run(Consumer<E> e){
    }

【问题讨论】:

  • The same behaviour 语句包括:参数赋值中没有强制转换,仍然使用泛型类型,保持同名同类
  • 我得到一个不同的编译错误,它是The target type of this expression must be a functional interface。 (Java 11.0.6)
  • “非正式的直觉是,如果第一个方法处理的任何调用可以传递给另一个方法而不会出现编译时错误,那么一个方法比另一个方法更具体。”根据这个定义,这两种方法都没有更具体,因此它们是模棱两可的。
  • 是的,我也明白了。
  • “即使传递的参数显然是一个 lambda,而这两种方法中只有一个是 lambda” - 这是不正确的。它们都不是 lambda。你对 lambdas 有一个基本的误解。 lambda 创建一个接口的实例,因此它可以在任何需要普通(功能)接口的地方使用。 Consumer 是一个接口,E 也可以是一个。

标签: java generics lambda


【解决方案1】:

也不能将 CharSequence 声明为 lambda

看看吧。

interface WeirdCharSequence extends CharSequence {
  default int length() { return 0; }
  default char charAt(int index) { return 0; }
  default CharSequence subSequence(int start, int end) { return null; }

  void accept(CharSequence i); 
}

证明可以

WeirdCharSequence charSequence = o -> {};
run(charSequence);

o -&gt; {} 可能意味着很多东西,并且没有足够的上下文来弄清楚 lambda 究竟代表什么。您始终可以通过显式声明所需的类型来帮助编译器。

this.<CharSequence>run(o -> {});

Consumer<CharSequence> consumer = o -> {};
run(consumer);

或(个人不太喜欢)

run((Consumer<CharSequence>)o -> {});

可能是编译器将E 视为既是Consumer&lt;CharSequence&gt; 又是CharSequence 的类型。

class ReallyWeirdCharSequence implements Consumer<CharSequence>, CharSequence {
  public void accept(CharSequence i) { }
  public int length() { return 0; }
  public char charAt(int index) { return 0; }
  public CharSequence subSequence(int start, int end) { return null; }
} 

【讨论】:

  • 你从字面上理解了我的陈述,哈哈。我的意思是你不能创建一个 lambda 转换为 CharSequence。无论如何,CharSequence 只是一个例子
  • 进一步的问题,this.&lt;CharSequence&gt;run(o -&gt; {}); 有什么帮助,为什么不this.run((Consumer&lt;CharSequence&gt;) a -&gt; {});
  • 一个 lambda 可以用在任何需要普通接口的地方(只要它的功能性)。
  • 问题是即使您将CharSequence 替换为class 之类的String,也无法解决问题
  • @Naman 我认为两者都应该有所帮助,因为他们都清楚地说明了即将发生的事情(例如Consumer&lt;CharSequence&gt; consumer = o -&gt; {}; run(consumer);),我只是不喜欢演员表
【解决方案2】:

经过一番调查,我意识到这是java语言的一个bug。

试试你自己:

    public void run() {
        run(o -> {});
    }
    public <E extends Class> void run(E e) {
    }
    public void run(Consumer e) {
    }

或者:

    public void run() {
        run(o -> {});
    }
    public <E extends Class> void run(E e) {
    }
    public <E extends Integer> void run(Consumer<E> e) {
    }

即使两个函数的参数彼此不相关。不知何故,java编译器认为它们是。

总之,使用泛型参数创建方法将使该方法作为接受 lambda 表达式的方法有效。

【讨论】:

    【解决方案3】:

    现在猜一猜,但一切都有一个 toString 方法,通过让编译器为您隐式调用它来隐式地将事物转换为字符串是很常见的。 String 当然是 CharSequence 的继承者。所以编译器可能会抱怨它无法在以下两者之间做出决定:

    run((CharSequence)(o -> {}).toString());
    

    run((Consumer<CharSequence>)(o -> {}));
    

    【讨论】:

    • 如果参数是异常/数字或其他任何东西怎么办?我写了 CharSequence 作为例子,实际上我遇到了 Throwable 的问题
    • "编译器为你隐式调用它" - 编译器不会为你隐式调用toString。如果你正在考虑System.out.println(myObject)toString 调用在println 方法中显式实现。
    • 这个答案是完全错误的。编译器只在字符串连接中隐式调用toString()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-08
    • 1970-01-01
    相关资源
    最近更新 更多