繁体   English   中英

比较 Java 枚举成员:== 或 equals()?

[英]Comparing Java enum members: == or equals()?

我知道 Java 枚举被编译为带有私有构造函数和一堆公共静态成员的类。 在比较给定枚举的两个成员时,我一直使用.equals() ,例如

public useEnums(SomeEnum a)
{
    if(a.equals(SomeEnum.SOME_ENUM_VALUE))
    {
        ...
    }
    ...
}

但是,我刚刚遇到了一些使用 equals 运算符==而不是 .equals() 的代码:

public useEnums2(SomeEnum a)
{
    if(a == SomeEnum.SOME_ENUM_VALUE)
    {
        ...
    }
    ...
}

我应该使用哪个运算符?

两者在技术上都是正确的。 如果您查看.equals()的源代码,它只是.equals() ==

但是,我使用== ,因为这将是空安全的。

==可以用在enum吗?

是的:枚举具有严格的实例控制,允许您使用==来比较实例。 这是语言规范提供的保证(我强调):

JLS 8.9 枚举

除了由其枚举常量定义的实例之外,枚举类型没有其他实例。

尝试显式实例化枚举类型是编译时错误。 Enumfinal clone方法确保永远无法克隆enum常量,并且序列化机制的特殊处理确保永远不会因反序列化而创建重复实例。 禁止枚举类型的反射实例化。 这四件事一起确保enum类型的实例不存在于enum常量定义的实例之外。

因为只有一个每个实例enum常数,允许使用==操作者在适当位置的equals比较两个对象的引用时,如果已知它们中的至少一个是指方法enum常数 Enumequals方法是一个final方法,它仅调用super.equals的参数并返回结果,从而执行身份比较。)

Josh Bloch 推荐的这种保证足够强大,如果您坚持使用单例模式,实现它的最佳方法是使用单元素enum (请参阅: Effective Java 2nd Edition,Item 3:Enforce the singleton property with私有构造函数或枚举类型;还有单例中的线程安全


==equals什么区别?

提醒一下,需要说明的是,通常==不是equals的可行替代方案。 然而,当它是(例如enum )时,需要考虑两个重要的区别:

==从不抛出NullPointerException

enum Color { BLACK, WHITE };

Color nothing = null;
if (nothing == Color.BLACK);      // runs fine
if (nothing.equals(Color.BLACK)); // throws NullPointerException

==在编译时进行类型兼容性检查

enum Color { BLACK, WHITE };
enum Chiral { LEFT, RIGHT };

if (Color.BLACK.equals(Chiral.LEFT)); // compiles fine
if (Color.BLACK == Chiral.LEFT);      // DOESN'T COMPILE!!! Incompatible types!

应该在适用时使用==吗?

Bloch 特别提到,对其实例进行适当控制的不可变类可以向其客户保证==可用。 enum被特别提到作为例证。

第 1 条:考虑静态工厂方法而不是构造函数

[...] 它允许不可变类保证不存在两个相等的实例: a.equals(b)当且仅当a==b 如果一个类做出了这个保证,那么它的客户端可以使用==运算符而不是equals(Object)方法,这可能会提高性能。 枚举类型提供了这种保证。

总而言之,在enum上使用==的参数是:

  • 有用。
  • 它更快。
  • 它在运行时更安全。
  • 在编译时更安全。

使用==比较两个枚举值是有效的,因为每个枚举常量只有一个对象。

附带说明一下,如果您像这样编写equals() ,则实际上不需要使用==来编写空安全代码:

public useEnums(final SomeEnum a) {
    if (SomeEnum.SOME_ENUM_VALUE.equals(a)) {
        …
    }
    …
}

这是一种称为从左侧比较常量的最佳实践,您绝对应该遵循。

正如其他人所说, ==.equals()在大多数情况下都有效。 您没有比较其他人指出的完全不同类型的对象的编译时确定性是有效且有益的,但是 FindBugs 也会发现比较两种不同编译时类型的对象的特定类型的错误(并且可能通过Eclipse/IntelliJ 编译时检查),因此 Java 编译器发现它并没有增加太多额外的安全性。

然而:

  1. 这样的事实==不会抛出NPE在我心目中是一个缺点== 几乎不需要enum类型为null ,因为您可能想要通过null表达的任何额外状态都可以作为附加实例添加到enum中。 如果它意外地为null ,我宁愿有一个 NPE 而不是==默默地评估为假。 因此,我不同意运行时更安全的观点; 最好养成永远不要让enum值为@Nullable的习惯。
  2. ==更快的论点也是虚假的。 在大多数情况下,您将在编译时类型为 enum 类的变量上调用.equals() ,在这些情况下,编译器可以知道这与==相同(因为enumequals()方法可以不被覆盖)并且可以优化函数调用。 我不确定编译器当前是否这样做,但如果没有,并且结果证明是 Java 整体的性能问题,那么我宁愿修复编译器也不愿让 100,000 个 Java 程序员改变他们的编程风格以适应特定编译器版本的性能特征。
  3. enums是对象。 对于所有其他 Object 类型,标准比较是.equals() ,而不是== 我认为为enums例外是危险的,因为您最终可能会不小心将 Objects 与==而不是equals()进行比较,特别是如果您将enum重构为非枚举类。 在这种重构的情况下,上面的It 工作点是错误的。 为了让自己相信==的使用是正确的,您需要检查所讨论的值是enum还是原始值; 如果它是一个非enum类,它会是错误的但很容易错过,因为代码仍然可以编译。 使用.equals()错误的唯一情况是所讨论的值是原始值; 在这种情况下,代码将无法编译,因此更难错过。 因此, .equals()更容易被识别为正确的,并且对未来的重构更安全。

我实际上认为 Java 语言应该在对象上定义 == 以在左侧值上调用 .equals(),并为对象标识引入一个单独的运算符,但这不是 Java 的定义方式。

总之,我仍然认为参数支持使用.equals()作为enum类型。

我更喜欢使用==而不是equals

除了这里已经讨论的其他原因之外,其他原因是您可能会在没有意识到的情况下引入错误。 假设您有这个完全相同的枚举,但在单独的包中(这并不常见,但可能会发生):

第一个枚举

package first.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

第二个枚举:

package second.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

然后假设你在item.category使用了像 next 一样的item.category ,它是first.pckg.Category但你导入了第二个枚举( second.pckg.Category )而不是第一个没有意识到它:

import second.pckg.Category;
...

Category.JAZZ.equals(item.getCategory())

因此,尽管您期望 true 是因为item.getCategory()JAZZ但由于是不同的枚举,您总是会得到false 而且可能有点难看。

因此,如果您改为使用运算符== ,则会出现编译错误:

运算符 == 不能应用于“second.pckg.Category”、“first.pckg.Category”

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 

tl;博士

另一种选择是Objects.equals实用程序方法。

Objects.equals( thisEnum , thatEnum )

Objects.equals用于 null 安全

等于运算符 == 而不是 .equals()

我应该使用哪个运算符?

第三个选项是在Java 7及更高版本中添加Objects实用程序类中的静态equals方法。

例子

这是使用Month枚举的示例。

boolean areEqual = Objects.equals( Month.FEBRUARY , Month.JUNE ) ;  // Returns `false`.

好处

我发现这种方法有几个好处:

  • 零安全
  • 紧凑、可读

这个怎么运作

Objects.equals使用的逻辑是什么?

亲自查看OpenJDKJava 10 源代码

return 
    ( a == b ) 
    || 
    ( 
        a != null 
        && 
        a.equals( b )
    )
;

这是一个粗略的时间测试来比较两者:

import java.util.Date;

public class EnumCompareSpeedTest {

    static enum TestEnum {ONE, TWO, THREE }

    public static void main(String [] args) {

        Date before = new Date();
        int c = 0;

        for(int y=0;y<5;++y) {
            for(int x=0;x<Integer.MAX_VALUE;++x) {
                if(TestEnum.ONE.equals(TestEnum.TWO)) {++c;}
                if(TestEnum.ONE == TestEnum.TWO){++c;}              
            }
        }

        System.out.println(new Date().getTime() - before.getTime());
    }   

}

一次注释掉一个 IF。 以下是上面反汇编字节码中的两个比较:

 21  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 24  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 27  invokevirtual EnumCompareSpeedTest$TestEnum.equals(java.lang.Object) : boolean [28]
 30  ifeq 36

 36  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 39  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 42  if_acmpne 48

第一个 (equals) 执行虚拟调用并测试堆栈中的返回布尔值。 第二个 (==) 直接从堆栈中比较对象地址。 在第一种情况下有更多的活动。

我用两个 IF 一次一个地运行了这个测试。 “==”总是稍微快一点。

在枚举的情况下,两者都是正确的!!

声纳规则之一是Enum values should be compared with "==" 原因如下:

使用equals()测试枚举值的equals()是完全有效的,因为枚举是一个对象,每个 Java 开发人员都知道==不应该用于比较对象的内容。 同时,在枚举上使用==

  • 提供与equals()相同的预期比较(内容equals()

  • equals()更空安全

  • 提供编译时(静态)检查而不是运行时检查

由于这些原因,应该优先使用==不是equals()

最后但并非最不重要的一点是,枚举上的==可以说比equals()更具可读性(更简洁equals()

使用==以外的任何东西来比较枚举常量都是无稽之谈。 这就像class对象与equals进行比较——不要这样做!

但是,由于历史原因,Sun JDK 6u10 及更早版本中存在一个令人讨厌的错误 ( BugId 6277781 )。 这个错误阻止了在反序列化枚举上正确使用== ,尽管这可以说是一种极端情况。

枚举是为public static final field (不可变)声明的每个枚举常量返回一个实例(如单例)的类,以便==运算符可用于检查它们的相等性,而不是使用equals()方法

枚举很容易与 == 一起工作的原因是因为每个定义的实例也是一个单例。 因此,使用 == 进行身份比较将始终有效。

但是使用 == 因为它适用于枚举意味着您的所有代码都与该枚举的使用紧密结合。

例如:枚举可以实现一个接口。 假设您当前正在使用一个实现 Interface1 的枚举。 如果稍后有人更改它或引入一个新类 Impl1 作为同一接口的实现。 然后,如果您开始使用 Impl1 的实例,由于以前使用 ==,您将需要更改和测试大量代码。

因此,除非有任何合理的收益,否则最好遵循被认为是好的做法。

简而言之,两者各有利弊。

一方面,如其他答案中所述,使用==具有优势。

另一方面,如果您出于任何原因用不同的方法(普通类实例)替换枚举,使用==咬你。 (BTDT。)

我想补充 polygenelubricants 答案:

我个人更喜欢equals()。 但它需要类型兼容性检查。 我认为这是一个重要的限制。

要在编译时进行类型兼容性检查,请在枚举中声明并使用自定义函数。

public boolean isEquals(enumVariable) // compare constant from left
public static boolean areEqual(enumVariable, enumVariable2) // compare two variable

有了这个,您就获得了两种解决方案的所有优势:NPE 保护、易于阅读的代码和编译时的类型兼容性检查。

我还建议为枚举添加一个 UNDEFINED 值。

如果将原始类型与其类版本进行比较,则==可以引发NullPointerException 例如:

private static Integer getInteger() {
    return null;
}

private static void foo() {
    int a = 10;

    // Following comparison throws a NPE, it calls equals() on the 
    // non-primitive integer which is itself null. 
    if(a == getInteger()) { 
        // Some code
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM