有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

参数中的Java类型提升

我偶然发现了这个片段:

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

共 (3) 个答案

  1. # 1 楼答案

    这的确是一个非常有趣的问题。让我们一步一步地浏览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。因此,这些方法中没有一种比另一种更具体。这就是为什么你在这里有歧义

    在以下示例中:

    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中的参数类型更具体,因为int < long和第二个参数类型对它们都是相同的,这就是选择方法1的原因

  2. # 2 楼答案

    我认为这与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需要确定其中哪一个是最具体的

    对于这些方法,没有一种更具体:

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

  3. # 3 楼答案

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

    printSum(1, 2);
    

    对于这三种方法来说都很混乱,因为这三种方法都是有效的:

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

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

    printSum(1L, 2L);
    

    对于double,您需要将其转换为:

    printSum((double)1, 2L);
    

    也请阅读@Erwin Bolwidt的评论