【问题标题】:Why can't I "static import" an "equals" method in Java?为什么我不能在 Java 中“静态导入”“equals”方法?
【发布时间】:2011-12-15 00:09:18
【问题描述】:

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

org.apache.commons.lang.ObjectUtils.equals(Object object1, Object object2)

唯一的缺点(例如,与 Google Guava 相比)是我无法静态导入该方法。 IE。这是没用的:

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

【问题讨论】:

  • 您是否考虑过创建自己的“实用程序”类来委托给该方法?然后,您可以将其命名为您想要的任何名称,然后它们会静态导入。
  • 我看过了,在 JLS 中找不到很好的描述。听到它在某个地方我不会感到惊讶,当然......
  • @tjg184:我不介意写ObjectUtils.equals(a, b)。这更像是一个学术问题。我想了解编译器...
  • @JonSkeet:我也没有找到任何东西,但那是我对规范缺乏理解:-)。但是在过去,我已经看到了 Eclipse 编译器的那种错误,所以它可能会朝着那个方向发展......
  • @Dhirendra 的修订答案看起来是正确的。另请注意,Guava 工程师将签名更改为 Objects.equal(o1, o2)。然而,JDK7 对象使用等号签名。

标签: java static import compiler-errors equals


【解决方案1】:

根据 Java 语言规范

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

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

静态导入JSRJLS

【讨论】:

  • 如果我错了,请纠正我,但我上面发布的代码与 1 相矛盾。
  • @Stefan 感谢您指出这一点。你是对的,这三点在 Java 规范请求jcp.org/aboutJava/communityprocess/jsr/tiger/static-import.html 中有,但是最终的 Java 语言规范没有 JSR 中提到的第一点,其余两点在 JLS 中。第二点和第三点可以在java.sun.com/docs/books/jls/third_edition/html/…的最后两行查看我已经相应地编辑了答案
  • 嗯,这是关于成员和类型,而不是方法。那不一样。此外,编译时错误是因为导入,而不是因为引用equals(..)...
  • ...还是我错过了什么?
  • 方法包含在“成员”类别中,请参阅 Java 语言规范的第 8.1.6 点。
【解决方案2】:

冲突实际上是与Object.equals()。所有类都继承自Object,因此具有导致此冲突的Object.equals() 方法。

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

(让我的 cmets 我自己回答。)

【讨论】:

  • 仍然没有意义。即使我在没有冲突的情况下按名称导入,编译器也会根据签名知道要调用哪个方法。例如,导入两个同名的静态方法。没问题。
  • 我同意。无论机制是什么,阅读 JLS 似乎它只使用名称。对于类型,它说“如果单静态导入声明导入了简单名称为 n 的类型,并且编译单元还声明了简单名称为 n 的顶级类型(第 7.6 节),则会发生编译时错误”。因此,如果它与方法相同,也就不足为奇了。
  • 我更新了我的问题以显示另一个案例。所以你的推理可能是对的
  • @Stefan 如果一个方法(即使具有不同的参数)在类(或超类)中定义,而另一个是静态导入的,则不起作用(刚刚测试)。
  • @Mister Smith 我知道它不起作用。问题是为什么以及是否在任何地方说明。您还可以毫无问题地从具有不同签名的 2 个不同对象导入具有相同名称的方法。所以编译器对导入方法的签名有一些了解。在这一点上,我认为没有其他原因,因为编译器的内部结构你不能这样做,而不是因为不允许这样做是有意义的。
【解决方案3】:

JLS 15.12.1。确定了两个原因,为什么方法可以“在范围内”:

  1. “...有一个封闭类型声明,该方法是其成员”
  2. "...由于一个或多个单静态导入..."

现在有两个因素促成了令人惊讶的结果:

  1. 此时只考虑方法的名称,稍后会出现签名。
  2. 上面提到的两个选项都与“否则”相关联。在第一种情况下,我们最终会查看可见方法的封闭类。在第二种情况下,我们使用静态导入。

这个“否则”意味着搜索范围仅限于尝试两个分支中的任何一个。 首先,我们必须决定是搜索封闭类型还是使用静态导入。封闭类型具有更高的优先级,我们找到正确名称的方法(Test.defaultIfNull()),搜索到此结束。当后来我们发现这个方法不兼容时,就没有回头尝试静态导入了。

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

从上面可以得出一个经验法则:重载只能在相同类型层次的方法中选择,不能在与继承无关的类型的方法中选择。

【讨论】:

  • 我知道这已经在不久前得到了回答,但是 (1) 这个问题仍然在外部引用,并且 (2) 没有一个答案对我来说是正确的。只有@Brice 对已接受答案的评论是在“推测”正确的方向。
  • 有趣的答案,与“否则”这个词有很好的联系。欢迎来到 Stack Overflow,Stephan。我怀疑,你就是这个Stephan Hermann, whom I've been talking to a couple of times? 在那种情况下,很高兴见到你:) 无论如何,提供更好的答案永远不会太晚。我已经重新阅读了其他答案,我同意你的理由。这现在更有意义了
【解决方案4】:

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

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

【讨论】:

  • 这是一个有趣的发现。不幸的是,到目前为止,我还没有找到关于该主题的 JLS 规范......
  • 嗯,我想它可能在the JSR。以后有时间我去看看。
【解决方案5】:

我也梳理了JLS3,也没有找到明确的答案。

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

我们可以调查一个类似的问题。简单类型名称既可以指导入类型,也可以指继承类型(超类的成员类型)。 Javac 选择了后者。这可能是因为 6.5.2 中的过程,它给导入的优先级最低。

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

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

【讨论】:

  • 好主意,尤其是关于Object 突然引入新方法。这描述了当今static imports 实现中的一个大缺陷!
  • +1 我完全同意你的最后一段。 @Lukas 我也分享你对这个缺陷的看法。
  • @Stefan:但我真诚地想知道这是否真的是一个错误。我再次检查了JLS。特别是关于阴影的部分:java.sun.com/docs/books/jls/third_edition/html/names.html#6.3.1。我猜想静态导入在某种程度上被Object.equals() 遮蔽了。但没有明确提及
【解决方案6】:

这不是一个真正的答案(只是更多的问题)。这证明编译器确实导入了带有签名的方法。

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

看起来编译器在不同的地方寻找方法,它会一一检查它们,但只按名称搜索会导致搜索过早终止。

【讨论】:

  • 非常好的发现。看起来确实相关。
  • ...实际上,您的发现确实是相关的,因为它也由 JLS 15.12.1 指定,它指的是“comb rule”。详情见Stefan Hermann's answer
【解决方案7】:

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

 ObjectUtils.equals(a, b);

【讨论】:

  • 我对@9​​87654322@ 没有依赖关系。这怎么会导致碰撞?
  • 冲突其实是和Object.equals()。所有类都继承自Object,因此具有导致此冲突的Object.equals() 方法。
  • @ChristofferHammarström:我看到这是碰撞。我在问为什么这是一个冲突,因为签名明显不同......
  • 因为名字是一样的。而且您是按名称而不是签名导入的。因此,您实际上无法导入名为 equals 的静态方法。或者更确切地说,您可以导入它,但不能使用它。我确实同意这应该可行。
  • @ChristofferHammarström:有趣的是,我可以导入它。但你是对的,这可能是原因。即使我还没有发现它在 JLS 中正式写成这样......
【解决方案8】:

实际上,我认为这更像是一个 Eclipse 问题,而不是其他任何事情。 如果您使用的是接收两个参数的 equals() 的重载版本,则不应与默认的 Object.equals() 发生冲突。

在 Eclipse 中有几个技巧可以用来让它识别静态导入:

1 - 将静态类型添加到 Organize Imports 前往:

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 - 将类型添加到 Content Assist 前往:

Window > Preferences > Java > Editor > Content Assist Favorites

然后点击“New Type”,然后选择你的类(在本例中,同样是 org.apache.commons.lang.ObjectUtils)

现在,您应该能够在方法的任何位置按 Ctrl+Space 并获得“equals(Object,Object)”方法作为可能的内容。如果您选择该方法,Eclipse 应该会自动为 equals 插入静态导入。

【讨论】:

  • 很遗憾地告诉你,我用 javac 测试过,但无法编译。
  • 感谢您的回答。在询问 Stack Overflow 之前,我已经检查了这些东西。具体来说,静态导入适用于内容辅助弹出窗口 (Ctrl+Space)。我可以看到ObjectUtils.equals() 方法。但是编译器不会喜欢它。另请参阅此答案和讨论以获取更多信息:stackoverflow.com/questions/7890853/…
  • 前段时间我遇到了一个非常相似的问题,这解决了。我想它并不适用于所有情况......对不起,我帮不了你:S
猜你喜欢
  • 1970-01-01
  • 2016-12-23
  • 1970-01-01
  • 2010-09-06
  • 2014-07-18
  • 1970-01-01
  • 2010-11-01
相关资源
最近更新 更多