为什么我不能在Java中“静态导入”一个“equals”方法?

时间:2011-10-25 14:27:32

标签: java static import compiler-errors equals

我喜欢在这里使用这种方法:

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编译器问题?

更新

这也不起作用:

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 (<nulltype>,<nulltype>)
defaultIfNull(null, null);
    ^
1 error

8 个答案:

答案 0 :(得分:16)

碰撞实际上是Object.equals()。所有类都继承自Object,因此使用Object.equals()方法导致此冲突。

您是按名称导入,而不是通过签名导入。实际上,您无法导入名为equals的静态方法。或者更确切地说,您可以导入它,但不能使用它。我同意这应该有用。

(我的评论是我自己的答案。)

答案 1 :(得分:14)

根据Java语言规范

  
      
  1. 如果单静态导入声明导入一个简单的成员       name为n,编译单元也包含单一类型导入       声明,导入一个简单名称为n的类型,即编译时       发生错误。 (即使两个声明都引用,也会发生此错误       同一类型,理由是它使用两种不同的混淆       冗余导入相同类型的机制。)
  2.   
  3. 如果单静态导入声明导入一个简单的成员        name为n,编译单元也声明了顶级类型        其简单名称为n,发生编译时错误。
  4.   

因此,在您的情况下,上面提到的第2点是您遇到编译时错误的原因。因此,即使方法签名不同,如果名称相同,则编译时错误也是如此。

静态导入JSRJLS

答案 2 :(得分:5)

我做了一些测试。我注意到的第一件事是你只需要一个静态导入语句用于多个同名方法。

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的一部分或编译器规范,它指定了编译器静态导入的解析以及它们如何被本地方法破坏。

答案 3 :(得分:4)

我还通过JLS3进行了梳理,无法找到明确的答案。

按照15.12.1,首先我们需要确定声明/继承equals方法的单个类。这里我们有两个候选类,规范似乎没有解决冲突的规则。

我们可以研究一个类似的问题。简单类型名称可以指导入类型或继承类型(超类的成员类型)。 Javac挑选了后者。这可能是因为6.5.2中的程序,它为进口提供了最低优先级。

如果适用相同的原则,导入的ObjectUtils.equals应该屈服于继承的Object.equals。然后根据15.12.2.1,equals中没有Object方法可能适用于表达式equals(obj1, obj2)

就个人而言,我更喜欢导入优先于继承,因为导入更接近。它还稳定了名称的含义。在当前方案中,假设Object没有equals方法,表达式equals(obj1, obj2)指的是ObjectUtils.equals;现在假设Object添加了equals方法,这是一个完全无辜的举动,突然子类无法编译。更糟糕的情况是:新的equals方法具有兼容的签名;子类仍在编译,但表达式的含义会默默地改变。

答案 4 :(得分:2)

这不是一个真正的答案(在某种程度上只是更多的问题)。这证明编译器确实使用签名导入方法。

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

这也可能是相关的。内部类中的一个方法“隐藏”具有不同签名的外部类中的静态方法。

http://ideone.com/pWUf1

看起来编译器有不同的地方可以查找方法,并逐个检查它们,但只按名称搜索,导致搜索提前终止。

答案 5 :(得分:2)

JLS 15.12.1。确定了两个原因,为什么一个方法可以在范围内&#34;:

  1. &#34; ...有一个封闭式声明,该方法是其成员&#34;
  2. &#34; ...由于一个或多个单一静态导入...&#34;
  3. 现在有两个因素导致令人惊讶的结果:

    1. 此时只考虑方法的名称,签名稍后。
    2. 上面提到的两个替代方案与&#34;否则&#34;有关。在第一种情况下,我们最终查看可见方法的封闭类。在第二种情况下,我们使用静态导入。
    3. 这&#34;否则&#34;意味着搜索范围仅限于尝试两个分支中的任何一个。 首先,我们必须决定是搜索封闭类型还是使用静态导入。封闭类型具有更高的优先级,我们找到一个正确名称的方法(Test.defaultIfNull()),搜索在这里结束。稍后我们发现这种方法不兼容时,不会再尝试静态导入了。

      这种情况在JLS中并不罕见,方法查找的其他问题也是分阶段组织的,其中一个阶段的部分匹配可能会阻止在后续阶段找到更好的匹配。固定与可变的arity匹配是这个概念的另一个例子。在所有情况下,效果都是编译器不会搜索整个可能的解决方案空间,但在做出某些决策之后,整个分支都会被切断并且从未被访问过。

      经验法则可以从上面得出:重载只能在相同类型层次结构的方法中进行选择,它不能在继承无关的类型方法之间进行选择。

答案 6 :(得分:-2)

这是与java.awt的方法冲突,你需要像这样引用包:

 ObjectUtils.equals(a, b);

答案 7 :(得分:-4)

实际上我认为这比任何其他东西都更像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插入静态导入。