参数中的 Java 类型提升

2022-09-02 10:13:07

我偶然发现了这个片段:

public class ParamTest {
    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, long b) {
        System.out.println("In long " + (a + b));
    }

    public static void printSum(double a, long b) {
        System.out.println("In doubleLONG " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

这将导致编译错误:

错误:(15, 9) java: 对 printSum 的引用是模棱两可的,两种方法 printSum(int,double) 在 ParamTest 中和方法 printSum(long,long) 在 ParamTest 匹配中都是不明确的

这怎么是模棱两可的?在这种情况下,不应该只提升第二个参数,因为第一个参数已经是一个int吗?在这种情况下,第一个参数不需要提升,对吧?

如果我更新代码以添加另一个方法,则编译成功:

public static void printSum(int a, long b) {
    System.out.println(String.format("%s, %s ", a, b));
}

让我展开讨论,以澄清一下。下面的代码会导致多义性:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, long b) {
        System.out.println("In long " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

那么下面的代码会导致歧义:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(double a, long b) {
        System.out.println("In doubleLONG " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

但是,这一个会导致歧义:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, double b) {
        System.out.println("In longDBL " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

答案 1

我认为这与JLS关于15.12.2.5的特定规则有关。选择最具体的方法。它指出:

如果多个成员方法既可访问又适用于方法调用,则需要选择一个成员方法来为运行时方法调度提供描述符。Java 编程语言使用选择最具体方法的规则。

Java如何选择最具体的方法在文本中进一步解释:

非正式的直觉是,如果第一个方法处理的任何调用都可以在没有编译时错误的情况下传递给另一个方法,则一种方法比另一个方法更具体。在显式类型的 lambda 表达式参数 (§15.27.1) 或变量 arity 调用 (§15.12.2.4) 等情况下,允许一些灵活性来使一个签名适应另一个签名。

在您的示例中,所有方法都是可访问的,并且适用于方法调用,因此,Java需要确定其中哪一个最具体

对于这些方法,没有一个可以确定为更具体:

public static void printSum(int a, double b) {
    System.out.println("In intDBL " + (a + b));
} // int, double cannot be passed to long, long or double, long without error

public static void printSum(long a, long b) {
    System.out.println("In long " + (a + b));
} // long , long cannot be passed to int, double or double, long without error

public static void printSum(double a, long b) {
    System.out.println("In doubleLONG " + (a + b));
} // double, long cannot be passed to int, double or long, long without error

第四种方法之所以能消除歧义,正是因为它满足了最具体的必要条件。

public static void printSum(int a, long b) {
    System.out.println(String.format("%s, %s ", a, b));
}

也就是说,(int,long)可以传递给(int,double),(long,long)或(double,long),而不会出现编译错误。


答案 2

这确实是一个非常有趣的问题。让我们逐步了解 Java 语言规范。

  1. 当编译器尝试识别可能适用的方法时,它做的第一件事就是搜索严格调用适用的方法

  2. 在你的情况下,没有这样的方法,所以下一步是找到松散调用适用的方法

  3. 此时,所有方法都匹配,因此在通过松散调用适用的方法中选择最具体的方法 (§15.12.2.5)。

这是一个关键时刻,所以让我们仔细看看这个。

一个适用的方法 m1 比另一个适用的方法 m2 更具体,用于使用参数表达式 e1, ..., ek 的调用(如果满足以下任一条件):

(我们只对以下情况感兴趣):

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

简而言之,如果一个方法的所有参数类型都更具体,则该方法更具体。和

对于任何表达式,如果 S <:T (§4.10),则类型 S 比类型 T 更具体。

表达式表示 是 的子类型。对于基元,我们具有以下关系:S <: TST

double > float > long > int

因此,让我们看看您的方法,看看哪一个比其他方法更具体。

public static void printSum(int a, double b) {  // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(double a, long b) { // method 2
    System.out.println("In doubleLONG " + (a + b));
}

在此示例中,方法 1 的第一个参数显然比方法 2 的第一个参数更具体(如果用整数值调用它们:)。但第二个参数对于方法 2 更具体,因为 .因此,这些方法中没有一个比另一个方法更具体。这就是为什么你在这里有一个模棱两可的地方。printSum(1, 2)long < double

在以下示例中:

public static void printSum(int a, double b) { // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(long a, double b) { // method 2
    System.out.println("In longDBL " + (a + b));
}

方法 1 的第一个参数类型比方法 2 中的参数类型更具体,因为第二个参数类型对于它们都是相同的,这就是选择方法 1 的原因。int < long


推荐