public class Primitive {
void m(Number b, Number ... a) {} // widening, autoboxing->widening->varargs
void m(byte b, Number ... a) {} // unboxing, autoboxing->widening->varargs
public static void main(String[] args) {
Byte b = 12;
Primitive obj = new Primitive();
obj.m(b, 23);
}
}
我已经搜索过并发现扩展优先级高于取消装箱,因此在上面的方法调用中,应该调用第一个方法,因为第二个参数对于两者都是相同的。但这不会发生。你可以解释一下吗?
答案 0 :(得分:7)
它无法在JDK 1.5,1.6和1.7中编译,但在JDK 1.8中工作。
更新:看起来它与第一个JDK8版本一起使用的事实实际上是一个错误:它在JDK 1.8.0_05中有效,但根据this question和答案medvedev1088,此代码将否在1.8.0_25中编译更长时间,这是符合JLS的行为
我认为这不是修复过的错误。相反,它更像是与Java 8中lambda表达式的方法调用机制相关的更改。
大多数人可能会同意关于“方法调用表达式”的部分是迄今为止Java语言规范中最复杂的不可理解的部分。可能有一整个工程师团队负责交叉检查和验证这一部分。所以任何陈述或任何尝试的推理都应该带着巨大的影响。 (即使它来自上述工程师)。但我会试一试,至少要充实其他人可能会参考的相关部分进行进一步分析:
考虑关于
的部分并且考虑到这两种方法都是“潜在适用的方法”(JLS7 / JLS8),那么相关的小节就是关于
的方法。对于JLS 7,它声明
当且仅当满足以下所有条件时,方法m才是适用的变量方法:
- 对于1 = i< n,ei的类型Ai可以通过方法调用转换为Si来转换。
- ...
(其他条件是指这里不相关的调用形式,例如真正使用 varargs的调用,或涉及泛型的调用)
参考示例:当b
可以通过方法调用转换转换为相应的形式方法参数时,方法适用于类型Byte
的实际参数表达式b
。根据JLS7中关于Method Invocation Conversion的相应部分,允许以下转换:
显然,根据此规范,有两个方法适用:
m(Number b, Number ... a)
适用于扩展参考转换m(byte b, Number ... a)
适用于拆箱转化您提到您“...发现扩大优先级高于取消装箱”,但这不适用于此:上面列出的条件不涉及任何“优先级”。它们被列为不同的选项。即使第一种方法是void m(Byte b, Number ... a)
,“身份转换”也适用,但它仍然只会被视为一个可能的转换,并且由于歧义而导致错误方法。< / p>
因此,据我所知,这解释了为什么它不与JDK7一起工作。我没有详细说明为什么 使用JDK8。但是变量arity方法的适用性定义在Identify Methods Applicable by Variable Arity Invocation in JLS 8中略有改变:
如果m不是泛型方法,则m适用于变量arity调用,如果,对于1≤i≤k,ei在松散调用上下文中与Ti或ei兼容,则与适用性无关(§15.12。 2.2)。
(我还没有深入研究“松散调用上下文”和第15.12.2.2节的定义,但这似乎是这里的关键区别)
除此之外,再一次提到你声明你“......发现扩大优先级高于取消装箱”:对于不的方法都是如此涉及varargs(并且根本不需要方法调用转换)。如果您省略了示例中的varags,那么找到匹配方法的过程将从Phase 1: Identify Matching Arity Methods Applicable by Subtyping开始。由于m(Number b)
是Byte b
的子类型,因此方法Byte
已经适用于参数Number
。没有理由进入Phase 2: Identify Matching Arity Methods Applicable by Method Invocation Conversion。在此阶段,通过从Byte
到byte
的拆箱进行方法调用转换将适用,但此阶段永远不会到达。