通过实验,我发现Java非静态方法会覆盖范围中所有相同名称的方法,即使是在静态上下文中。即使不允许参数重载。像
import java.util.Arrays;
import static java.util.Arrays.toString;
public class A {
public static void bar(Object... args) {
Arrays.toString(args);
toString(args); //toString() in java.lang.Object cannot be applied to (java.lang.Object[])
}
}
我在规范中找不到关于这个的任何信息。这是个bug吗?如果不是,有什么理由实现这样的语言吗?
UPD:Java 6不编译此示例。问题是——为什么
解释很简单,尽管它不会改变行为高度非直觉的事实:
当解析要调用的方法时,编译器要做的第一件事就是找到具有正确名称的方法的最小封闭范围。只有这样,其他事情才会出现,比如超负荷解决和游戏中的合作。
现在这里发生的是,包含toString()
方法的最小封闭范围是从Object
继承它的类a。因此,我们在那里停下来,不再进一步搜索。遗憾的是,接下来编译器试图在给定的范围内找到最适合的方法,并注意到它不能调用其中的任何方法,并给出了一个错误。
这意味着永远不会静态导入名称与Object中的方法相同的方法,因为自然在作用域中的方法优先于静态导入(JLS详细描述了方法阴影,但对于这个问题,我认为只记住它要简单得多)。
编辑:@alf友好地提交了JLS的正确部分,该部分描述了那些想要了解全貌的人的方法调用。这相当复杂,但问题也不简单,所以这是意料之中的事。
它不是覆盖。如果它确实起作用,this.toString()
仍将访问A
的方法,而不是Arrays.toString
,就像发生重写时的情况一样。
语言规范解释了静态导入只影响static
方法和类型的分辨率:
在包p的编译单元c中,导入名为n的字段的单个静态导入声明d在整个c中隐藏由静态导入按需声明导入的任何静态字段n的声明。
在包p的编译单元c中,导入具有签名s的名为n的方法的单个静态导入声明d与通过c中的静态导入按需声明导入的签名s的任何静态方法的声明形成阴影
在包p的编译单元c中,导入名为n的类型的单个静态导入声明d隐藏以下声明:
- 由c中的静态按需导入声明导入的任何名为n的静态类型
- 在p的另一个编译单元(§7.3)中声明的任何顶级类型(§7.6),命名为n
- 通过c中的类型按需进口声明(§7.5.2)进口的任何名为n的类型。贯穿c
静态导入不会遮蔽非静态方法或内部类型。
因此toString
不会遮蔽非静态方法。由于名称toString
可以引用A
的非静态方法,因此它不能引用Arrays
的static
方法,因此toString
绑定到范围中唯一可用的名为toString
的方法,即String toString()
。该方法不能接受任何参数,因此会出现编译错误。
第15.12.1节解释了方法解析,并且必须完全重写,以允许在static
方法内而不是在member
方法内隐藏不可用的方法名称。
我的猜测是,语言设计者希望保持方法解析规则的简单性,这意味着无论是否出现在static
方法中,相同的名称都意味着相同的东西,唯一改变的是可用的。
如果您尝试遵循类似的代码,则将不会得到任何编译器错误
import static java.util.Arrays.sort;
public class StaticImport {
public void bar(int... args) {
sort(args); // will call Array.sort
}
}
这个编译而你没有编译的原因是toString()
(或在类Object中定义的任何其他方法)的作用域仍然是Object类,因为Object是你类的父级。因此,当编译器从Object类中找到这些方法的匹配签名时,就会给出编译器错误。在我的例子中,由于Object类没有sort(int[])
方法,因此编译器正确地将其与静态导入匹配。
我不认为这是一个bug或与正常导入不同的东西。例如,在正常导入的情况下,如果您有一个与导入的类同名的私有类,则导入的类将不会反映出来。