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

Luk*_*der 29 java import static compiler-errors equals

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

org.apache.commons.lang.ObjectUtils.equals(Object object1, Object object2)
Run Code Online (Sandbox Code Playgroud)

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

import static org.apache.commons.lang.ObjectUtils.equals;
Run Code Online (Sandbox Code Playgroud)

...因为我的Eclipse编译器在编写时无法正确链接该方法

equals(obj1, obj2);
Run Code Online (Sandbox Code Playgroud)

错误是:

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() {
  }
}
Run Code Online (Sandbox Code Playgroud)

javac错误消息:

Test.java:5: defaultIfNull() in Test cannot be applied to (<nulltype>,<nulltype>)
defaultIfNull(null, null);
    ^
1 error
Run Code Online (Sandbox Code Playgroud)

Chr*_*röm 16

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

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

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


dps*_*dce 15

根据Java语言规范

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

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

静态导入JSRJLS

  • 如果我错了,请纠正我,但我在上面发布的代码与1相矛盾. (2认同)
  • 方法包含在"成员"类别中,请参阅Java语言规范的第8.1.6节. (2认同)

Ste*_*ann 8

JLS 15.12.1。确定方法可以“在范围内”的两个原因:

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

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

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

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

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

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


irr*_*ble 5

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

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

我们可以调查一个类似的问题。简单类型名称可以同时引用导入类型或继承类型(超类的成员类型)。Javac选择后者。这可能是由于6.5.2中的过程使导入具有最低的优先级。

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

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


Bri*_*128 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
  }
Run Code Online (Sandbox Code Playgroud)

然后我注意到它在实例上下文中不起作用:

  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
  }

}
Run Code Online (Sandbox Code Playgroud)

但是如果我用类自己的静态方法破坏静态导入:

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!
}
Run Code Online (Sandbox Code Playgroud)

它在静态环境中工作的事实对我来说是合理的.但是,似乎静态导入方法的分辨率与类的已定义静态方法之间存在显着差异.

摘要:

  • 从实例上下文静态导入时,无法访问与实例方法同名的方法.
  • 可以从实例上下文访问来自同一个具有相同名称的类的静态方法.
  • 尽管有签名(参数和返回值),静态导入使您可以访问该类中具有相同名称的所有静态方法.

我有兴趣看到JLS的一部分或编译器规范,它指定编译器静态导入的解析以及它们如何被本地方法破坏.