当前位置: 首页 > 面试题库 >

为什么此方法重载模棱两可?

云伯寅
2023-03-14
问题内容

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

我已经搜索过,发现加宽优先级比拆箱优先,因此在上述方法调用中,应该调用第一个方法,因为两个参数都相同。但这不会发生。你能解释一下吗?


问题答案:

它无法在JDK 1.5、1.6和1.7中进行编译,但可以在JDK 1.8中工作。

更新 :这似乎是一个事实,即它与第一JDK8版本的工作实际上是一个错误:它曾在JDK
1.8.0_05,但根据这个问题,并通过medvedev1088答案,这个代码不会 再在1.8.0_25编译,这是符合JLS的行为

我认为这不是已修复的错误。相反,它是与Java 8中lambda表达式的方法调用机制相关的更改的影响。

大多数人可能会同意,关于“方法调用表达式”的部分是迄今为止Java语言规范中最复杂,最难以理解的部分。可能会有整个工程师团队负责交叉检查和验证此部分。因此,任何陈述或任何试图进行的推理都应花费大量的盐。(即使来自上述工程师)。但我会尝试一下,至少充实其他人可能会参考以进行进一步分析的相关部分:

考虑有关

  • JLS 7中的方法调用表达式
  • JLS 8中的方法调用表达式

并且考虑到这两种方法都是“潜在适用方法”(JLS7
/
JLS8),则相关的小节是关于

  • 阶段3:确定JLS7中适用的可变Arity方法
  • 阶段3:确定JLS8中可变Arity调用可应用的方法

对于JLS 7,它指出

当且仅当满足以下所有条件时,方法m是 适用的可变对数方法

  • 对于1 = i <n,可以通过方法调用转换将ei的类型Ai转换为Si。

(其他条件指的是此处不相关的调用形式,例如,真正 使用 varargs的调用或涉及泛型的调用)

参见示例:当可以通过方法调用转换将其转换为相应的形式方法参数时,方法适用于b类型的实际参数表达式。根据有关JLS7中方法调用转换的相应部分,允许进行以下转换:Byte``b

  • 身份转换(第5.1.1节)
  • 不断扩大的原始转换(第5.1.2节)
  • 扩展参考转换(第5.1.5节)
  • 装箱转换(第5.1.7节),然后可选地扩大参考转换
  • 取消装箱转换(第5.1.8节),然后可以选择加宽原始转换。

显然,根据此规范,可以采用 两种 方法:

  • m(Number b, Number ... a) 通过扩展参考转换适用
  • m(byte b, Number ... a) 通过拆箱转换适用

您提到您 “ …发现扩展优先级高于拆箱” ,但这在此处不适用:上面列出的条件不涉及任何“优先级”。它们被列为不同的选项。即使第一种方法是void m(Byte b, Number ... a)适用的,“身份转换”也将适用,但是它仍然仅算作 一种 可能的转换,并且由于含糊不清而导致错误方法。

因此,据我所知,这解释了为什么它 不适 用于JDK7。我没有详细弄清楚它为什么 没有 工作,JDK8。但是,在JLS
8中,可变可变方法的适用性的定义在“可变可变调用的适用方法的识别”中发生了细微变化:

如果m不是通用方法,则对于1≤i≤k,或者ei在宽松的调用上下文中与Ti兼容,或者ei与适用性无关(第15.12.2.2节),则m可通过可变arity调用来应用。

(我尚未深入研究“松散调用上下文”和第15.12.2.2节的定义,但这似乎是关键的区别)

顺便说一句,再次提到您 “ …发现扩展优先级比拆箱优先” 的说法:对于
涉及varargs(并且根本不需要方法调用转换)的方法,这是正确的。如果您在示例中遗漏了变量,那么查找匹配方法的过程将从阶段1:确定适用于子类型的匹配Arity方法开始。该方法由于是的子类型,因此m(Number b)已经适用于该参数。没有理由进入第2阶段:确定适用于方法调用转换的匹配Arity方法。在此阶段,通过从以下位置取消装箱来进行方法调用转换Byte b``Byte``NumberBytebyte将适用,但从来没有达到这个阶段。



 类似资料:
  • 但是,当我尝试相同的示例时,通过将Integer更改为Object,代码编译得很好,输出为String 谁能帮助我理解为什么当输出来自其中有字符串的方法时,签名中有对象的方法是必需的。以及类型错误不明确的原因是什么。

  • 问题内容: 我有一个无法执行的SQL查询: 该表的PK为(int)。该视图是对该表的查询,该表与其他一些表结合在一起以决定我们是否信任某个人。该视图只有一列:。 当我尝试执行此查询时,出现以下错误: 消息209,级别16,状态1,第3 行列名称“ CreatedDate”不明确。 我知道这个错误告诉我列名是不明确的,我需要在表的别名’p’开头。 该查询有效: 我不明白的是为什么我需要在语句中而不是

  • 无法找出正确的方法来使用匹配器来识别我要处理的exchange方法的重载。我正在打的电话:

  • 我有一节简单的课 这将输出为10,没有任何错误!!!我原以为这会给我一个ClassCastException,其中有些错误,比如Integer不能转换为HashMap。 出于好奇和愤怒,我尝试了返回值,如下所示

  • 谁能解释为什么我在代码后面得到“对'end'的引用是模棱两可的”?我明白这是因为与STD::END发生冲突。但是如果我把结束放在主函数中,它不会显示错误。在全局范围内定义与在主函数范围内定义有何不同?

  • 问题内容: 我想知道当您尝试捕获StackOverflowError并提出以下方法时会发生什么: 现在我的问题是: 为什么此方法打印“ 4”? 我以为是因为在调用堆栈上需要3个段,但是我不知道3的来源。当您查看的源代码(和字节码)时,通常导致的方法调用要多于3(因此,调用堆栈上的3个段是不够的)。如果是由于优化而应用了Hotspot VM(方法内联),我想知道其他VM上的结果是否会有所不同。 编辑