Java静态导入

27

通过实验,我发现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吗?如果不是,有没有理由实现这样的语言呢?

更新:Java 6不能编译这个示例。问题是 - 为什么?


它似乎在引用其父类对象的 toString() 方法。 - jmj
2
在我看来,整个静态导入功能都是一个糟糕的概念,会污染命名空间并且影响代码可读性。不要偷懒,手动输入静态函数所在的封闭类/接口并不难,我们有集成开发环境可以帮助我们完成这个任务。尽量避免使用静态导入。 - buc
@buc 很好的观点。但我现在想要理解它。 - Stan Kurilin
8
@buc 呃,静态导入在许多情况下都非常适用,并且可以极大地增加可读性。我无法想象在没有它们的情况下使用模拟库;那将是一种残忍行为。 - Dave Newton
1
@Dave 我同意,你只需要了解其中的缺陷——因为那些可能很糟糕。在这种情况下并不是那么糟糕,因为我们只会得到一个编译错误,但是在最坏的情况下,我们可能会调用错误的函数,这肯定会让调试变得有趣。 - Voo
显示剩余2条评论
4个回答

25

解释很简单,尽管这并不改变行为高度不直观的事实:

在解析要调用的方法时,编译器首先找到最小的包含方法名称的范围。然后才进行其他事情,如重载决议等。

现在,在此发生的情况是包含 toString() 方法的最小包围范围是从Object继承它的A类。因此,我们停在那里,不再搜索更远。不幸的是,接下来编译器尝试在给定的范围内找到最佳匹配的方法,并注意到它无法调用任何一个方法,并报错。

这意味着永远不要静态导入与Object中的方法名称相同的方法,因为自然在范围内的方法优先于静态导入(JLS详细描述了方法阴影,但对于这个问题,我认为仅需记住这一点就足够了)。

编辑:@alf 友好地提交了JLS的正确部分(描述方法调用),供那些想要了解整个情况的人查看。它相当复杂,但问题也不简单,所以可以预期。


4
如果您需要JLS链接,请访问http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.1 - alf
@alf 谢谢,现在回答好多了。如果我知道在JLS中哪里可以找到它的描述,我会自己添加的。 - Voo

4
这并不是覆盖。即使它起作用,this.toString()仍将访问A的方法,而不是如果进行重写,则会访问Arrays.toString的方法。 语言规范解释了静态导入只影响static方法和类型的解析:
单个静态导入声明d在编译单元c中,该编译单元位于包p中,导入名为n的字段,遮蔽了由静态导入-on-demand声明在c中导入的任何名为n的静态字段,在整个c中。
单个静态导入声明d在编译单元c中,该编译单元位于包p中,导入名称为n且签名为s的方法,遮蔽了由静态导入-on-demand声明在c中导入的具有签名s的任何静态方法,在c中。
单个静态导入声明d在编译单元c中,该编译单元位于包p中,导入名为n的类型,遮蔽了:
由静态导入-on-demand声明在c中导入的任何静态类型n。
任何在p的另一个编译单元(§7.3)中声明的顶级类型(§7.6)名为n。
在c中的类型-import-on-demand声明(§7.5.2)中导入的任何名为n的类型。整个c。
静态导入不遮蔽非静态方法或内部类型。
因此,toString不会遮蔽非静态方法。由于名称toString可能引用A的非静态方法,它不能引用Arrays的静态方法,因此toString绑定到唯一可用于范围内的命名为toString的方法,即String toString()。该方法不能接受任何参数,因此会出现编译错误。 第15.12.1节解释了方法解析,并且必须完全重新编写才能允许在static方法中遮蔽不可用的方法名称,但不在member方法中。
我猜测语言设计者想保持方法解析规则简单,这意味着相同的名称意味着无论它出现在static方法中还是不出现,都具有相同的含义,唯一改变的是哪些可用。

由于方法 bar 是静态的,没有实例可以调用 toString() - chrisbunney
2
@StasKurilin [par. 6.3.1](http://java.sun.com/docs/books/jls/third_edition/html/names.html#34133)更清楚地解释了它。它说,实例方法`Object.toString()`的声明会遮蔽其范围内的*任何其他方法*。这包括`Arrays.toString(Object[])`的静态导入。这意味着,有效地说,该静态导入没有*任何作用* - 编译器甚至不会考虑它来解决方法调用。 - millimoose
3
@inerdial,确实如此,但如果这个名称不是在java.lang.Object中定义的名称,那么它可能会产生影响。例如,import static Foo.compareTo; 可能在一个顶层类中有效,但在另一个实现了Comparable接口的类中无效。 - Mike Samuel

1

如果您尝试遵循类似的代码,那么您将不会收到任何编译器错误。

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[])方法,因此编译器正确地将其与静态导入匹配。

0

我认为这不是一个错误或与正常导入有所不同的问题。例如,在正常导入的情况下,如果您有一个与导入的类同名的私有类,则导入的类将不会被反映。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接