参数中的Java类型提升


20

我偶然发现了以下片段:

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的引用是不明确的,在ParamTest中的方法printSum(int,double)和在ParamTest中的方法printSum(long,long)都匹配

这是如何模棱两可的?因为第一个参数已经是一个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);
    }
}

2
编译器可以映射您的调用printSum(1,2); 到printSum(long a,long b)或printSum(int a,double b),因此模棱两可。您必须通过精确指定以下类型来显式地帮助编译器进行选择:printSum(1,2d)
Ravindra Ranwala,

6
您错误地引用了错误消息,并且区别非常重要。实际的错误消息是:Error:(15, 9) java: reference to printSum is ambiguous both method printSum(int,double) in ParamTest and method printSum(long,long) in ParamTest match-不是模棱两可的方法,而是方法的调用是模棱两可的。
欧文·博威

1
@ErwinBolwidt错误消息来自eclipse,对不起,我在那里引用错误。无论如何,我仍然不理解它,因为添加printSum(int a,long b)会删除错误消息。
riruzen

2
JLS-5.3 => 如果在宽松的调用上下文中允许的转换不能将表达式的类型转换为参数的类型,则会发生编译时错误。似乎适用于上下文,但并不是很容易推断出该如何做。+1
纳曼

1
我们肯定需要一个规范的问题:stackoverflow.com/…。”
Marco13,19年

Answers:


17

我认为这与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会识别该方法,可以在没有编译时错误的情况下调用其他方法,如果找到该方法,则将其用作最具体的方法,否则将其称为歧义错误?我认为,这是@riruzen的正确答案。
Sandeep Kokate,

谢谢!我用(double,int)vs(double,long)进行了尝试,它确实消除了歧义,然后(double,int)vs(long,double)像预期的那样模棱两可。
riruzen

7

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

  1. 当编译器试图识别可能适用的方法时,它所做的第一件事就是为Strict Invocation所适用的方法进行服务

  2. 在您的情况下,没有这样的方法,因此下一步是找到适用于Loose Invocation的方法

  3. 此时,所有方法都匹配,因此从松散调用可应用的方法中选择最具体的方法(第15.12.2.5节)。

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

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

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

  • m2不是通用的,并且m1和m2可通过严格调用或宽松调用来应用,并且m1具有形式参数类型S1,...,Sn,而m2具有形式参数类型T1,...,Tn,则Si类型更多。对于所有i(1≤i≤n,n = k),自变量ei比Ti特定。

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

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

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

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的原因。


我没有拒绝您的答案,但是这个答案不能解释为什么(int,double)与(long,long)模棱两可,因为很明显(int,double)应该相对于第一个参数更具体。
riruzen

3
它确实解释了@riruzen:double没有比更加具体long。和用于将被选择的方法中的所有类型的参数必须更具体地:型Si比的Ti更加具体为参数EI对于所有的i(1≤I≤N,N- = K)
基里尔西蒙诺夫

它应该是最多+1

0

因为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);

对于双倍,您需要将其转换:

printSum((double)1, 2L);

还阅读了@Erwin Bolwidt的评论


是的,编译器对这3种方法都感到困惑,第一个编译器会寻找确切的类型,如果找不到确切的类型,则尝试查找兼容的类型,在这种情况下,所有类型都是兼容的。
另一个编码器,

2
如果我有4个方法声明而不是3个,则模棱两可:public static void printSum(int a,double b)public static void printSum(int a,long b) public static void printSum(long a,long b)public static void printSum(double a,long b),所以虽然我同意您的回答,但仍然无法回答问题。如果我没有记错的话,Java会自动进行类型自动升级。我只是不明白为什么它与那些3变得模棱两可
。– riruzen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.