【问题标题】:Why do I get an ambiguity error in this code?为什么我在此代码中出现歧义错误?
【发布时间】:2022-01-13 20:10:15
【问题描述】:

假设我们有这 3 个类:

class A { }
class B extends A { }

public class App {
    static void f(int i, A a) { }
    static void f(float j, B b) { }
   
    static public void main() {
        int i = 0;
        B b = new B();
        App.f(i, b);
    }
}

这会产生错误:

App.java:11: error: reference to f is ambiguous
        App.f(i, b);
           ^
  both method f(int,A) in App and method f(float,B) in App match
1 error

既然i是整数,为什么不选择f(int, A)类型?

【问题讨论】:

  • bBi可以自动加宽到float时,为什么不选择f(float, B)类型?
  • @thatotherguy:由于从intlongfloat 的转换可能是有损的,因此对于一种语言来说,将数字类型的“精确”拟合视为更重要是有意义的而不是参考类型的精确拟合。然而,Java 的规则旨在优先考虑简单性而不是明智的极端情况行为,其效果是将 long 类型的值传递给 Math.round() 将产生 int 类型的值。
  • @RafikBouloudene:如果存在一个重载,其中 every 参数与传递的值匹配或优于任何替代值,则将使用它。在您的双参数示例中,第一个重载对第二个参数的匹配不如第二个,第二个重载对第一个参数的匹配不如第一个。

标签: java


【解决方案1】:

之所以模棱两可,有两个原因:

  • 两个重载都适用,并且;
  • 没有一个重载比另一个更具体

请注意,f(int, A) 重载和f(float, B) 重载都可以使用参数(i, b) 调用,因为存在从intfloat 的隐式转换,以及从B 的隐式转换到A

当有不止一种适用的方法时会发生什么? Java应该选择最具体的方法。这在语言规范的§15.12.2.5 中有描述。事实证明,这些重载中的一个并非比另一个更具体。

一种适用的方法 m1 比另一种适用的方法更具体 方法 m2,用于使用参数表达式 e1、...、ek、if 的调用 以下任何一项都是正确的:

  • m2 是通用的 [...]

  • m2 不是泛型的,m1 和 m2 可以通过严格或松散的调用来应用,其中 m1 有形参类型 S1, ..., Sn 和 m2 有形参类型 T1, ..., Tn,类型 Si 更具体 比所有 i (1 ≤ i ≤ n, n = k) 的参数 ei 的 Ti。

  • m2 不是通用的,m1 和 m2 可以通过变量 arity 调用来应用 [...]

只有第二点适用于f 的两个重载。为了使其中一个重载比另一个更具体,一个重载的每个参数类型都必须比另一个重载中的相应参数类型更具体。

对于任何表达式,如果 S <: t href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.10" rel="nofollow" target="_blank">§4.10),类型 S 比类型 T 更具体。

注意“<:>B 显然是A 的子类型。 float 实际上是int 的超类型(不是子类型!)。这可以从§4.10.1 中列出的直接子类型关系推导出来。因此,没有一个重载比另一个更具体。

语言规范继续讨论最具体方法,这并不真正适用于f。最后,它说:

否则方法调用不明确,出现编译时错误。

更多示例

static void f(int x) {}
static void f(float x) {}

当使用 int 调用时不会有歧义,因为 int 重载更具体。

static void f(int x, B a) {}
static void f(float x, A a) {}

当使用参数类型调用时,(int, A) 不会有歧义,因为 (int, B) 重载更具体。

static void f(int x, A a) {}
static void f(float x, A a) {}

当使用参数类型调用时,(int, A) 并没有歧义,因为(int, A) 重载更具体。请注意,子类型关系是自反的(即AA 的子类型)。

【讨论】:

    【解决方案2】:

    两件事:

    • 类型扩展(第一个参数)
    • 继承(第二个参数)

    Type wideningfloat 是比int 更“宽”的类型,因此传递的int 值可以轻松打包到更大的float 框中。当我们想将float“打包”到int 中时,它不起作用,因为我们可能会丢失浮点数。 在提供的情况下,拥有int 我们可以潜在地向它传递两个函数。

    fFloat(float f){}
    int intValue = 221;
    fFloat(intValue); // inside f, intValue is treated as 221.0
    
    fInt(int i){}
    float floatVal = 221.221;
    fInt(floatVal); // what should compiler do with remaining .221 part?
    

    InheritanceA 是基类,而B 是 A 的子类之一。因此,当我们将方法参数声明为基类时,我们可以同时提供两者基类及其实例(但我们可以在f(int i, A a) 中使用B b 实例,就像A 类型一样。因此,我们也可以将B b = new B(); 传递给这两种方法。

    class A {
    public void f() {}
    }
    
    class B extends A {
    public void g() {}
    }
    
    fA(A a){}
    B bInst = new B();
    fA(bInst); // you can refer only to f() method, not g()
    
    fB(B b){}
    A aInst = new A();
    fB(aInst); // you cannot do this, as subclass can have some additional
    // stuff, that superclass does not have
    
    //but even
    A aInstBImpl = new B();
    fB(aInstBImpl); // will not work without explicit casting as it is
    // kind of A instance with B implementation - here polymorphism comes into action
    

    由于所有 2 个参数(从 2 个可能)都可以适用于这两种方法,而无需任何明确的“采取行动”,因此会出现歧义错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多