方法使用Java 8三元条件和未装箱的原语过载歧义 [英] Method overload ambiguity with Java 8 ternary conditional and unboxed primitives

查看:111
本文介绍了方法使用Java 8三元条件和未装箱的原语过载歧义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下是Java 7中的代码编译,但不是openjdk-1.8.0.45-31.b13.fc21。

The following is code compiles in Java 7, but not openjdk-1.8.0.45-31.b13.fc21.

static void f(Object o1, int i) {}
static void f(Object o1, Object o2) {}

static void test(boolean b) {
    String s = "string";
    double d = 1.0;
    // The supremum of types 'String' and 'double' is 'Object'
    Object o = b ? s : d;
    Double boxedDouble = d;
    int i = 1;
    f(o,                   i); // fine
    f(b ? s : boxedDouble, i); // fine
    f(b ? s : d,           i); // ERROR!  Ambiguous
}

编译器声称最后一个方法调用不明确。

The compiler claims the last method call ambiguous.

如果我们将 f 的第二个参数的类型从 int 更改为 Integer ,然后代码在两个平台上编译。为什么发布的代码不能在Java 8中编译?

If we change the type of the second parameter of f from int to Integer, then the code compiles on both platforms. Why doesn't the posted code compile in Java 8?

推荐答案

让我们首先考虑一个没有三元组的简化版本有条件且不在Java HotSpot VM上编译(版本1.8.0_25-b17):

Let's first consider a simplified version that doesn't have a ternary conditional and doesn't compile on Java HotSpot VM (build 1.8.0_25-b17):

public class Test {

    void f(Object o1, int i) {}
    void f(Object o1, Object o2) {}

    void test() {
        double d = 1.0;

        int i = 1;
        f(d, i); // ERROR!  Ambiguous
    }
}

编译错误是:

Error:(12, 9) java: reference to f is ambiguous
both method f(java.lang.Object,int) in test.Test and method f(java.lang.Object,java.lang.Object) in test.Test match

根据 JLS 15.12.2。编译时步骤2:确定方法签名


如果一种方法适用于严格调用之一(§) 15.12.2.2),松散调用(§15.12.2.3)或变量arity调用(§15.12.2.4)。

A method is applicable if it is applicable by one of strict invocation (§15.12.2.2), loose invocation (§15.12.2.3), or variable arity invocation (§15.12.2.4).

调用已有与调用上下文有关,这里解释 JLS 5.3。调用上下文

Invocation has to do with invocation context which is explained here JLS 5.3. Invocation Contexts

如果方法调用不涉及装箱或拆箱,则应用严格调用。当方法调用涉及装箱或拆箱时,则应用松散调用。

When no boxing or unboxing is involved for a method invocation then strict invocation applies. When boxing or unboxing is involved for a method invocation then loose invocation applies.

确定适用的方法分为3个阶段。

Identifying applicable methods is divided into 3 phases.

第一阶段(§15.12.2.2)执行重载解析而不允许装箱或拆箱转换,或使用变量arity方法调用。 如果在此阶段没有找到适用的方法,则处理继续到第二阶段。

The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.

第二阶段(§15.12.2.3)执行过载允许装箱和拆箱时的分辨率,但仍然排除使用变量arity方法调用。 如果在此阶段没有找到适用的方法,则处理继续到第三阶段。

The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.

第三阶段(第15.12.2.4节)允许重载与变量arity方法,装箱和拆箱相结合。

The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.

对于我们的情况,没有适用于严格调用的方法。这两种方法都适用于松散调用,因为双值必须加框。

For our case there are no methods applicable by strict invocation. Both methods are applicable by loose invocation since the double value has to be boxed.

根据 JLS 15.12.2.5选择最具体的方法


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

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.

然后:


对于带有参数表达式e1,...,ek的调用,一个适用的方法m1比另一个适用的
方法m2更具体,如果
以下任何一个都是真的:

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:


  1. m2是通用的,并推断m1比m2更具体b $ b参数表达式e1,...,ekby§18.5.4。

  1. m2 is generic, and m1 is inferred to be more specific than m2 for argument expressions e1, ..., ek by §18.5.4.

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

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).

m2不是通用的,m1和m2适用于变量arity
调用,并且m1
的前k个变量arity参数类型为S 1,...,Sk和m2
的第一个k变量arity参数类型是T1,...,Tk,对于所有i,参数ei
的类型Si比Ti更具体(1 ≤i≤k)。另外,如果m2有k + 1个参数,那么
第k + 1个变量arity参数类型m1是
k +第1个变量arity参数类型m2的子类型。

m2 is not generic, and m1 and m2 are applicable by variable arity invocation, and where the first k variable arity parameter types of m1 are S1, ..., Sk and the first k variable arity parameter types of m2 are T1, ..., Tk, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ k). Additionally, if m2 has k+1 parameters, then the k+1'th variable arity parameter type of m1 is a subtype of the k+1'th variable arity parameter type of m2.

上述条件是一种方法可能比另一种方法更具体的唯一情况。

The above conditions are the only circumstances under which one method may be more specific than another.

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

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

可能看起来第二个条件匹配这种情况,但实际上并不是因为 int 不是Object的子类型: int < ;: 对象。但是,如果我们在f方法签名中用Integer替换int,则此条件将匹配。请注意,方法中的第一个参数符合此条件,因为 Object <: Object 为真。

It may look that the 2nd condition matches for this case but in fact it doesn't because int is not a subtype of Object: it's not true that int <: Object. However if we replace int with Integer in the f method signature this condition would match. Note that the 1st parameter in methods matches this condition since Object <: Object is true.

根据 $ 4.10 无子类型/ supertype关系在基元类型和类/接口类型之间定义。因此, int 不是 Object 的子类型。因此 int 并不比 Object 更具体。

According to $4.10 no subtype/supertype relation is defined between primitive types and Class/Interface types. So int is not a subtype of Object for example. Thus int is not more specific than Object.

因为在2种方法中没有更多特定方法因此可能没有严格更具体的并且可能没有最具体的方法(JLS在同一段中给出了这些术语的定义 JLS 15.12.2.5选择最具体的方法)。所以这两种方法都是最大特定的

Since among the 2 methods there are no more specific methods thus there can be no strictly more specific and can be no most specific method (the JLS gives definitions for those terms in the same paragraph JLS 15.12.2.5 Choosing the Most Specific Method). So both methods are maximally specific.

在这种情况下,JLS提供了2个选项:

In this case the JLS gives 2 options:


如果所有最大特定方法都具有覆盖等效签名(§8.4.2)...

If all the maximally specific methods have override-equivalent signatures (§8.4.2) ...

这不是我们的情况,因此

This is not our case, thus


否则,方法调用不明确,并且发生编译时错误。

Otherwise, the method invocation is ambiguous, and a compile-time error occurs.

根据JLS,我们案例的编译时错误看起来有效。

The compile-time error for our case looks valid according to the JLS.

如果我们将方法参数类型从int更改为Integer会怎样?

在这种情况下,两种方法仍适用于松散调用。但是,使用Integer参数的方法比具有2个Object参数的方法更具体,因为Integer< ;:Object。使用Integer参数的方法严格更具体,更具体,因此编译器会选择它而不会抛出编译错误。

In this case both methods are still applicable by loose invocation. However the method with Integer parameter is more specific than the method with 2 Object parameters since Integer <: Object. The method with Integer parameter is strictly more specific and most specific thus the compiler will choose it and not throw a compile error.

如果我们将double改为double会发生什么此行加倍:double d = 1.0;?

在这种情况下,严格调用恰好有1种方法:不需要装箱或取消装箱调用此方法:f(Object o1,int i)。对于另一种方法,你需要对int值进行装箱,因此它适用于松散调用。编译器可以选择严格调用适用的方法,因此不会抛出编译器错误。

In this case there is exactly 1 method applicable by strict invocation: no boxing or unboxing is required for invocation of this method: f(Object o1, int i). For the other method you need to do boxing of int value so it's applicable by loose invocation. The compiler can choose the method applicable by strict invocation thus no compiler error is thrown.

正如Marco13在他的评论中指出的那样,在这篇文章中讨论了类似的案例为什么这种方法重载不明确?

As Marco13 pointed out in his comment there is a similar case discussed in this post Why is this method overloading ambiguous?

正如答案中所解释的那样,有一些重大变化与Java 7和Java 8之间的方法调用机制。这解释了为什么代码在Java 7中编译而在Java 8中编译。

As explained in the answer there were some major changes related to the method invocation mechanisms between Java 7 and Java 8. This explains why the code compiles in Java 7 but not in Java 8.

现在是有趣的部分!

让我们添加一个三元条件运算符:

Let's add a ternary conditional operator:

public class Test {

    void f(Object o1, int i) {
        System.out.println("1");
    }
    void f(Object o1, Object o2) {
        System.out.println("2");
    }

    void test(boolean b) {
        String s = "string";
        double d = 1.0;
        int i = 1;

        f(b ? s : d, i); // ERROR!  Ambiguous
    }

    public static void main(String[] args) {
        new Test().test(true);
    }
}

编译器抱怨模糊的方法调用。
JLS 15.12.2 在执行方法调用时没有规定与三元条件运算符相关的任何特殊规则。

The compiler complains about ambiguous method invocation. The JLS 15.12.2 doesn't dictate any special rules related to ternary conditional operators when performing method invocations.

但是 JLS 15.25条件运算符? : JLS 15.25.3。参考条件表达式。前者将条件表达式分为3个子类别:布尔值,数值和引用条件表达式。条件表达式的第二个和第三个操作数分别具有String和double类型。根据JLS,我们的条件表达式是一个引用条件表达式。

However there are JLS 15.25 Conditional Operator ? : and JLS 15.25.3. Reference Conditional Expressions. The former one categorizes conditional expressions into 3 subcategories: boolean, numeric and reference conditional expression. The second and third operands of our conditional expression have types String and double respectively. According to the JLS our conditional expression is a reference conditional expression.

然后根据 JLS 15.25.3。参考条件表达式我们的条件表达式是一个多边形引用条件表达式,因为它出现在一个调用上下文中。因此,我们的多条件表达式的类型是Object(调用上下文中的目标类型)。从这里我们可以继续步骤,好像第一个参数是Object,在这种情况下,编译器应该选择使用int作为第二个参数的方法(而不是抛出编译器错误)。

Then according to JLS 15.25.3. Reference Conditional Expressions our conditional expression is a poly reference conditional expression since it appears in an invocation context. The type of our poly conditional expression thus is Object (the target type in the invocation context). From here we could continue the steps as if the first parameter is Object in which case the compiler should choose the method with int as the second parameter (and not throw the compiler error).

棘手的部分是来自JLS的这个说明:

The tricky part is this note from JLS:


第二和第三操作数表达式同样出现在目标类型为T的相同类型的上下文中。

its second and third operand expressions similarly appear in a context of the same kind with target type T.

从这里我们可以假设(也是poly在名称中暗示这一点)在方法调用的上下文中,应该独立考虑2个操作数。这意味着当编译器必须决定是否需要对此类参数进行装箱操作时,它应该查看每个操作数并查看是否需要装箱。对于我们的具体情况String不需要拳击和双重将需要拳击。因此,编译器决定对于两个重载方法,它应该是一个松散的方法调用。进一步的步骤与我们使用双值时代替三元条件表达式的情况相同。

From this we can assume (also the "poly" in the name implies this) that in the context of method invocation the 2 operands should be considered independently. What this means is that when the compiler has to decide whether a boxing operation is required for such argument it should look into each of the operands and see if a boxing may be required. For our specific case String doesn't require boxing and double will require boxing. Thus the compiler decides that for both overloaded methods it should be a loose method invocation. Further steps are the same as in the case when instead of a ternary conditional expression we use a double value.

从上面的解释看来,JLS本身似乎含糊不清当应用于重载方法时,与条件表达式相关的部分中含糊不清,所以我们不得不做出一些假设。

From the explanation above it seems that the JLS itself is vague and ambiguous in the part related to conditional expressions when applied to overloaded methods so we had to make some assumptions.

有趣的是我的IDE(IntelliJ IDEA)没有检测到最后一种情况(使用三元条件表达式)作为编译器错误。它根据JDK的java编译器检测到的所有其他情况。这意味着JDK java编译器或内部IDE解析器都有错误。

What's interesting is that my IDE (IntelliJ IDEA) doesn't detect the last case (with the ternary conditional expression) as a compiler error. All other cases it detects according to the java compiler from JDK. This means that either JDK java compiler or the internal IDE parser has a bug.

这篇关于方法使用Java 8三元条件和未装箱的原语过载歧义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆