【问题标题】:Warning: [overloads] method m1 is potentially ambiguous with method m2警告:[重载] 方法 m1 可能与方法 m2 不明确
【发布时间】:2015-03-19 02:09:54
【问题描述】:
import java.util.function.*;

class Test { 
    void test(int    foo, Consumer<Integer> bar) { }
    void test(long   foo, Consumer<Long>    bar) { }
    void test(float  foo, Consumer<Float>   bar) { }
    void test(double foo, Consumer<Double>  bar) { }
}

当我用javac -Xlint Test.java 编译它时,我收到了一些警告:

Test.java:4: warning: [overloads] test(int,Consumer<Integer>) in Test is potentially ambiguous with test(long,Consumer<Long>) in Test
    void test(int    foo, Consumer<Integer> bar) { }
         ^
Test.java:6: warning: [overloads] test(float,Consumer<Float>) in Test is potentially ambiguous with test(double,Consumer<Double>) in Test
    void test(float  foo, Consumer<Float>   bar) { }
         ^
2 warnings

如果我将Consumer 更改为Supplier,警告就会消失。这个程序没有警告:

import java.util.function.*;

class Test { 
    void test(int    foo, Supplier<Integer> bar) { }
    void test(long   foo, Supplier<Long>    bar) { }
    void test(float  foo, Supplier<Float>   bar) { }
    void test(double foo, Supplier<Double>  bar) { }
}

这是为什么呢?这个警告是什么意思?这些方法如何模棱两可?抑制警告是否安全?

【问题讨论】:

  • 当您尝试调用警告函数时会发生什么?
  • 看来只有在以下情况下才会发出警告:它是一个功能接口(即消费者、供应商),并且 b。任何接口的方法都包含任何参数...作为测试,我制作了自己的 Consumer/Supplier 接口副本并与它们一起玩弄。遗憾的是,我对 Java 函数了解得不够多,无法知道为什么会为此产生警告。
  • 更正:A 点不正确,它不需要是功能接口,只定义了一个(非默认)方法的任何接口(类不产生警告)。此外,我已将 test(int, Consumer) 的参数更改为几乎不可能模棱两可的参数,例如 test(Object, Consumer) vs. test(Map, Consumer) 但仍然抛出警告。因此,除非您使用一些非常古怪的编程,否则我真的认为您无需担心这一点。
  • @Kai: interface 有一个确切的抽象方法一个函数式接口。没有其他标准。
  • @Holger 是的,直到今天才研究 lambda,我发现 Oracle 的文章写得很好:Java 8: Lambdas, Part 1, Java 8: Lambdas, Part 2

标签: java java-8 compiler-warnings overloading functional-interface


【解决方案1】:

这些警告的出现是因为重载解析、目标类型和类型推断之间有趣的交集。编译器会提前为您考虑并警告您,因为大多数 lambda 表达式都是在没有显式声明的类型的情况下编写的。例如,考虑这个调用:

    test(1, i -> { });

i 的类型是什么?编译器在完成重载解析之前无法推断它......但值 1 匹配所有四个重载。无论选择哪个重载都会影响第二个参数的目标类型,进而影响为i 推断的类型。这里确实没有足够的信息让编译器决定调用哪个方法,所以这行实际上会导致编译时错误:

    error: reference to test is ambiguous
           both method test(float,Consumer<Float>) in Test and
           method test(double,Consumer<Double>) in Test match

(有趣的是,它提到了 floatdouble 重载,但是如果您将其中一个注释掉,您会得到与 long 重载相同的错误。)

可以想象一种策略,其中编译器使用最具体的规则完成重载解析,从而选择带有 int 参数的重载。然后它将有一个明确的目标类型应用于 lambda。编译器设计者认为这太微妙了,在某些情况下程序员会惊讶于最终调用了哪个重载。与其以可能出乎意料的方式编译程序,他们认为将其作为错误并迫使程序员消除歧义会更安全。

编译器在方法声明处发出警告,表明程序员为调用这些方法之一而编写的可能代码(如上所示)将导致编译时错误。

为了消除通话的歧义,人们不得不写

    test(1, (Integer i) -> { });

或为i 参数声明一些其他显式类型。另一种方法是在 lambda 之前添加强制转换:

    test(1, (Consumer<Integer>)i -> { });

但这可以说是更糟。您可能不希望 API 的调用者在每次调用时都必须处理这种事情。

Supplier 的情况不会出现这些警告,因为可以通过本地推理确定供应商的类型,而无需任何类型推断。

您可能需要重新考虑将这个 API 组合在一起的方式。如果您真的想要具有这些参数类型的方法,最好将方法重命名为testInttestLong 等,并完全避免重载。请注意,Java SE API 在类似的情况下已经做到了这一点,例如Comparator 上的comparingIntcomparingLongcomparingDouble;还有mapToIntmapToLongmapToDoubleStream

【讨论】:

  • 我们 (Eclipse JDT) 希望在这方面同步 ecjjavac 以实现 JLS 9.6.4.5。当 javac 发出可被@SuppressWarnings("overload") 抑制的警告时,您能否提供/链接描述?
  • 我仍然希望分析的一些定义/规范会引发@SuppressWarnings("overload") 抑制的警告,因为缺少这些,ecj 只能猜测类似的规则。我们很可能会收到很多新的警告,其中 ecj 认为 javac 需要的抑制是不必要的,反之亦然。根据 JLS 9.6.4.5,javac 团队应该对此类问题更加透明!
  • @StephanHerrmann 我没有给你答案,我只是想澄清一下似乎起作用的魔法咒语不是@SuppressWarnings("overload"),而是@SuppressWarnings("overloads")
  • 并注意“似乎可以工作”是相对的:即使在声明重载的地方禁止了警告,但从那一刻起重载实际上是不可用的,因为歧义会导致编译每个呼叫站点的错误。为了消除每个调用站点的歧义,您需要大量额外的符号,以至于使用不同的方法而不是相同方法的重载会容易得多。因此,即使您设法为此提供支持,也可能对任何人都没有任何实际用途。
  • @MikeNakis 我完全同意应该避免重载,而不是寻求以越来越复杂的方式使用它的方法。在合适的情况下,我会不断传播建议,以完全避免重载和功能接口的任何组合。作为一个工具实现者,我几乎不能坐下来声称:“你不应该这样做”。发出警告很有帮助。对于许多警告,将控制权交还给开发人员以使特定警告静音也很有帮助。在这种情况下,这种沉默是否谨慎确实是一个很好的问题。
猜你喜欢
  • 1970-01-01
  • 2018-04-22
  • 1970-01-01
  • 1970-01-01
  • 2019-04-28
  • 1970-01-01
  • 1970-01-01
  • 2020-04-04
  • 1970-01-01
相关资源
最近更新 更多