Java 参数中的类型提升

Java type promotion in parameters

我偶然发现了这个片段:

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

这将导致编译错误:

Error:(15, 9) java: reference to printSum is ambiguous both method printSum(int,double) in ParamTest and method printSum(long,long) in ParamTest match

这怎么暧昧?由于第一个参数已经是一个 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);
    }
}

因为int值在java中也可以认为是double。意味着 double a = 3 是有效的并且与 long long b = 3 相同所以这就是它产生歧义的原因。 你打电话给

printSum(1, 2);

这三种方法都令人困惑,因为这三种方法都有效:

int a = 1;
double b =1;
long c = 1;

您可以将 L 放在末尾,以指定它是 long 值。例如:

printSum(1L, 2L);

对于 double 你需要转换它:

printSum((double)1, 2L);

同时阅读@Erwin Bolwidt 的评论

我认为这与JLS关于15.12.2.5. Choosing the Most Specific Method的具体规则有关。它指出:

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

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

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error. In cases such as an explicitly typed lambda expression argument (§15.27.1) or a variable arity invocation (§15.12.2.4), some flexibility is allowed to adapt one signature to the other.

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

对于这些方法,none可以确定的更具体:

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) 而不会出现编译错误。

确实是个很有意思的问题。让我们逐步了解 Java 语言规范。

  1. 当编译器试图识别可能适用的方法时,它做的第一件事就是 serching for methods applicable by Strict Invocation

  2. 在你的情况下没有这样的方法,所以下一步是find methods applicable by Loose Invocation

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

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

One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:

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

  • m2 is not generic, and m1 and m2 are applicable by strict or loose invocation, and where m1 has formal parameter types S1, ..., Sn and m2 has formal parameter types T1, ..., Tn, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).

简单地说,如果一个方法它的所有参数类型都更具体,那么这个方法就更具体。并且

A type S is more specific than a type T for any expression if S <: T (§4.10).

表达式S <: T表示ST的子类型。对于原语,我们有以下关系:

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的第一个参数更具体(如果你用整数值调用它们:printSum(1, 2))。 但是第二个参数对于方法2更为具体,因为long < double。因此 none 这些方法比另一种更具体。这就是为什么你在这里有歧义。

在下面的例子中:

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

方法一的第一个参数类型比方法二的参数类型更具体,因为int < long和第二个参数类型都一样,所以选择方法一.