为什么我不能在Java中“静态导入”一个“等于”方法?
我喜欢在这里使用这个方法:
org.apache.commons.lang.ObjectUtils.equals(Object object1, Object object2)
唯一的缺点(例如,与Google Guava相比),我无法静态导入该方法。 也就是说这没用:
import static org.apache.commons.lang.ObjectUtils.equals;
…因为我的Eclipse编译器在编写时无法正确链接该方法
equals(obj1, obj2);
错误是:
Object类型中的方法equals(Object)不适用于参数(…,…)
这是为什么? 如果在任何超类型中存在具有相同名称(但不是相同的签名)的方法,我的静态导入方法是否不适用? 这是在JLS中正式指定的吗? 还是一些Eclipse编译问题?
UPDATE
这也不起作用:
import static org.apache.commons.lang.ObjectUtils.defaultIfNull; public class Test { void test() { defaultIfNull(null, null); // ^^ compilation error here } void defaultIfNull() { } }
javac错误消息:
Test.java:5: defaultIfNull() in Test cannot be applied to (,) defaultIfNull(null, null); ^ 1 error
JLS 15.12.1 。 确定了两个原因,为什么方法可以“在范围内”:
- “…有一个封闭类型声明,该方法是其成员”
- “……由于一个或多个单静态导入……”
现在有两个因素导致令人惊讶的结果:
- 此时只考虑方法的名称,签名稍后会出现。
- 上面提到的两个替代方案与“否则”相关联。 在第一种情况下,我们最终查看可见方法的封闭类。 在第二种情况下,我们使用静态导入。
这“否则”意味着搜索范围仅限于尝试两个分支中的任何一个。 首先,我们必须决定是搜索封闭类型还是使用静态导入。 封闭类型具有更高的优先级,我们找到一个正确名称的方法(Test.defaultIfNull()),搜索结束于此。 稍后我们发现这种方法不兼容时,就不会再尝试静态导入了。
这种情况在JLS中并不罕见,方法查找的其他问题也是分阶段组织的,其中一个阶段的部分匹配可能阻止在后续阶段找到更好的匹配。 固定arity与变量arity匹配是这个概念的另一个例子。 在所有情况下的效果是编译器不会搜索整个可能的解决方案空间,但是在做出某些决策之后,整个分支被切断并且从未访问过。
可以从上面导出经验法则:重载只能在相同类型层次结构的方法中进行选择,它不能在inheritance无关的类型方法之间进行选择。
碰撞实际上是Object.equals()
。 所有类都inheritance自Object
,因此具有导致此冲突的Object.equals()
方法。
您是按名称导入,而不是通过签名导入。 实际上,您无法导入名为equals
的静态方法。 或者更确切地说,您可以导入它,但不能使用它。 我同意这应该有用。
(我的评论是我自己的答案。)
根据Java语言规范
- 如果单静态导入声明导入其简单名称为n的成员,并且编译单元还包含导入简单名称为n的类型的单类型导入声明,则会发生编译时错误。 (即使两个声明都引用相同类型,也会发生此错误,理由是使用两种不同的机制来冗余导入相同类型会让人感到困惑。)
- 如果单静态导入声明导入一个简单名称为n的成员,并且编译单元也声明了一个简单名称为n的顶级类型,则会发生编译时错误。
所以在你的情况下,上面提到的第2点是你得到编译时错误的原因。 因此,即使方法签名不同,如果名称相同则编译时错误。
静态导入JSR和JLS
我做了一些测试。 我注意到的第一件事是你只需要一个静态import语句用于多个同名方法。
public class EqualsClass { public static boolean equals(Object o1, Object o2) { return o1 == null ? o2 == null : o1.equals(o2); } public static boolean equals(Object o1, Object o2, Object o3) { return equals(o1, o2) && equals(o2, o3); } } import static mypackage.EqualsClass.equals; public class TestClass { public static void main() { Object o1 = new Object(); Object o2 = new Object(); equals(o1, o2); // Compiles - static context Object o3 = new Object(); equals(o1, o2, o3); // No extra static import required }
然后我注意到它在实例上下文中不起作用:
public void someInstanceMethod() { Object o1 = new Object(); Object o2 = new Object(); equals(o1, o2); // Does not compile - instance context Object o3 = new Object(); equals(o1, o2, o3); // As expected does not compile } }
但是如果我用类自己的静态方法破坏静态导入:
public static boolean equals(Object o1, Object o2) { return EqualsClass.equals(o1, o2); // Compiles } public void someInstanceMethod() { equals(new Object(), new Object()); // Compiles!! equals(new Object(), new Object(), new Object()); // Doesn't compile! }
它在静态环境中工作的事实对我来说是合理的。 但是,似乎静态导入方法的分辨率与类的已定义静态方法之间存在显着差异。
概要:
- 从实例上下文静态导入时,无法访问与实例方法同名的方法。
- 可以从实例上下文访问具有相同名称的同一类中的静态方法。
- 尽管有签名(参数和返回值),静态导入使您可以访问该类中具有相同名称的所有静态方法。
我有兴趣看到JLS的一部分或编译器规范,它指定编译器静态导入的解析以及它们如何被本地方法破坏。
我还梳理了JLS3,但找不到明确的答案。
根据15.12.1,首先我们需要确定声明/inheritanceequals
方法的单个类。 这里我们有两个候选类,规范似乎没有解决冲突的规则。
我们可以研究一个类似的问题。 简单类型名称可以指导入类型或inheritance类型(超类的成员类型)。 Javac挑选了后者。 这可能是因为6.5.2中的程序,它为import提供了最低优先级。
如果适用相同的原则,则导入的ObjectUtils.equals
应该生成inheritance的Object.equals
。 然后根据15.12.2.1,在Object
中没有可能适用于表达式equals(obj1, obj2)
equals
方法
就个人而言,我更希望导入优先于inheritance,因为导入更接近。 它还稳定了名称的含义。 在当前的方案中,假设Object
没有equals
方法,表达式equals(obj1, obj2)
引用ObjectUtils.equals
; 现在假设Object
添加了equals
方法,一个完全无辜的移动,突然子类不编译。 更糟糕的情况是:新的equals
方法具有兼容的签名; 子类仍然编译,但表达式的含义默默地改变。
这不是一个真正的答案(在某种程度上只是更多的问题)。 这certificate编译器确实使用签名导入方法。
package test; public class Foo { public static void equal(Object o1) { System.out.println("Foo.equal Object"); } public static void equal(Integer o1) { System.out.println("Foo.equal Integer"); } } package test; public class Bar { public static void equal(Number o1) { System.out.println("Bar.equal Number"); } } import static test.Foo.equal; import static test.Bar.equal; public static void main(String args[]) throws Exception { equal((Object)null); equal((Number)null); equal((Integer)null); } Output: Foo.equal Object Bar.equal Number Foo.equal Integer
这也可能是相关的。 内部类中的方法“隐藏”具有不同签名的外部类中的静态方法。
看起来编译器在不同的地方寻找方法,并逐个检查它们,但只按名称搜索,导致搜索过早终止。
这是与java.awt的方法冲突,你需要像这样引用包:
ObjectUtils.equals(a, b);
实际上我认为这比任何其他东西都更像Eclipse问题。 如果您正在使用接收两个参数的equals()的重载版本,则不应与默认的Object.equals()冲突。
Eclipse中有一些技巧可以用来识别静态导入:
1 – 添加静态类型以组织导入转到:
Window > Preferences > Java > Code Style > Organize Imports
然后单击“New Static”,然后单击“Types”,然后选择您的类(在本例中为org.apache.commons.lang.ObjectUtils)
仍在“组织导入”面板上时,取消选择
"Do not create imports for types starting with lowercase letter"
(不要忘记这一点,这很重要)
2 – 将类型添加到内容辅助转到:
Window > Preferences > Java > Editor > Content Assist Favorites
然后单击“New Type”,然后选择你的类(在这种情况下,再次,org.apache.commons.lang.ObjectUtils)
现在,您可以在方法的任何位置按Ctrl + Space并获取“equals(Object,Object)”方法作为可能的内容。 如果选择该方法,Eclipse应自动为equals插入静态导入。