比较Java enum成员:== 还是 equals()?

Comparing Java enum members: == or equals()?

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

1
2
3
4
5
6
7
8
public useEnums(SomeEnum a)
{
    if(a.equals(SomeEnum.SOME_ENUM_VALUE))
    {
        ...
    }
    ...
}

但是,我刚刚遇到一些代码使用equals操作符==而不是.equals():

1
2
3
4
5
6
7
8
public useEnums2(SomeEnum a)
{
    if(a == SomeEnum.SOME_ENUM_VALUE)
    {
        ...
    }
    ...
}

我应该用哪个接线员?


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

不过,我使用==,因为这将是无效安全的。


==能用于enum吗?

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

JLS 8.9 Enums

An enum type has no instances other than those defined by its enum constants.

It is a compile-time error to attempt to explicitly instantiate an enum type. The final clone method in Enum ensures that enum constants can never be cloned, and the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization. Reflective instantiation of enum types is prohibited. Together, these four things ensure that no instances of an enum type exist beyond those defined by the enum constants.

Because there is only one instance of each enum constant, it is permissible to use the == operator in place of the equals method when comparing two object references if it is known that at least one of them refers to an enum constant. (The equals method in Enum is a final method that merely invokes super.equals on its argument and returns the result, thus performing an identity comparison.)

这个保证是足够强的,Josh Bloch建议,如果你坚持使用单模式,最好的实现方法是使用一个元素EDOCX1×7(参见:有效的Java第二版,项目3:用私有构造函数或枚举类型来执行SuntLon属性;在Singleton中也使用线程安全)。

==equals有什么区别?

需要提醒的是,一般来说,==不是equals的可行替代品。然而,如果是这样(如与enum一起),有两个重要的区别需要考虑:

==从不抛出NullPointerException

1
2
3
4
5
enum Color { BLACK, WHITE };

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

==在编译时接受类型兼容性检查

1
2
3
4
5
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来举例说明。

Item 1: Consider static factory methods instead of constructors

[...] it allows an immutable class to make the guarantee that no two equal instances exist: a.equals(b) if and only if a==b. If a class makes this guarantee, then its clients can use the == operator instead of the equals(Object) method, which may result in improved performance. Enum types provide this guarantee.

综上所述,在enum上使用==的理由是:

  • 它起作用了。
  • 它更快。
  • 在运行时更安全。
  • 它在编译时更安全。


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

另一方面,如果您这样编写equals(),实际上不需要使用==编写空安全代码:

1
2
3
4
5
6
7
8
public useEnums(SomeEnum a)
{
    if(SomeEnum.SOME_ENUM_VALUE.equals(a))
    {
        ...
    }
    ...
}

这是一个被称为"比较左边常量"的最佳实践,您肯定应该遵循这一点。


正如其他人所说,==.equals()在大多数情况下都起作用。编译时的确定性,你没有比较完全不同类型的对象,其他人指出的是有效的和有益的,然而,比较两种不同编译时间类型对象的特定类型的bug也可以由FindBugs找到(并且可能通过Eclipse/ItLyLJ编译时检查),因此Java编译发现它并没有增加那么多额外的安全性。

然而:

  • 事实上,==从未在我的头脑中抛出NPE是==的一个缺点。几乎不需要将enum类型作为null类型,因为您可能希望通过null表达的任何额外状态都可以作为附加实例添加到enum中。如果这是出乎意料的null,我宁愿有一个NPE而不是==,悄悄地评估为假。因此,我不同意它在运行时的观点;最好养成习惯,不要让enum值为@Nullable
  • 认为==更快的论点也是假的。在大多数情况下,您将对编译时类型为枚举类的变量调用.equals(),在这些情况下,编译器可以知道这与==相同(因为enumequals()方法不能被重写),并且可以优化函数调用。我不确定编译器当前是否这样做,但是如果它没有,并且结果是Java中的一个性能问题,那么我宁愿修复编译器,而不是100000个Java程序员改变他们的编程风格以适应特定编译器版本的性能特征。
  • enums是物体。对于所有其他对象类型,标准比较是.equals(),而不是==。我认为对enums例外是危险的,因为最终可能会意外地将对象与==而不是equals()进行比较,特别是将enum重构为非枚举类时。在这种重构的情况下,从上面看它的工作点是错误的。为了让自己相信使用==是正确的,您需要检查所讨论的值是enum还是原语;如果它是非enum类,那么它是错误的,但很容易出错,因为代码仍然可以编译。如果使用.equals()是错误的,那么唯一的情况就是所讨论的值是原语;在这种情况下,代码不会编译,因此很难忽略。因此,.equals()更容易识别为正确的,并且对未来的重构更安全。
  • 事实上,我认为Java语言应该有定义的=ON对象,在左边的值上调用.RealSalk(),并为对象标识引入一个单独的运算符,但这不是Java定义的方式。

    总之,我仍然认为这些论点有利于使用.equals()作为enum类型。


    下面是一个比较这两种情况的粗略计时测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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());
        }  

    }

    对国际单项体育联合会逐一发表评论。以下是反汇编字节代码中上述两个比较:

    1
    2
    3
    4
    5
    6
    7
    8
     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

    另外一个原因是,除了这里已经讨论过的其他原因之外,您可以引入一个bug而不必意识到它。假设您有这个枚举,它完全相同,但在分开的pacakges中(这不常见,但可能发生):

    第一枚举:

    1
    2
    3
    4
    5
    6
    7
    8
    package first.pckg

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

    第二枚举:

    1
    2
    3
    4
    5
    6
    7
    8
    package second.pckg

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

    然后假设在item.category中使用与next相同的equals,即first.pckg.Category中的equals,但在没有意识到它的情况下,导入第二个枚举(second.pckg.Category)。

    1
    2
    3
    4
    import second.pckg.Category;
    ...

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

    所以你会得到所有的false到期是一个不同的枚举,尽管你期望是真的,因为item.getCategory()JAZZ。这可能有点难看。

    因此,如果使用操作符==,则会出现编译错误:

    operator == cannot be applied to"second.pckg.Category","first.pckg.Category"

    1
    2
    3
    4
    import second.pckg.Category;
    ...

    Category.JAZZ == item.getCategory()


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


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

    然而,在Sun JDK 6U10和更早的版本中有一个讨厌的bug(bugid 6277781),出于历史原因,它可能很有趣。这个bug妨碍了对反序列化枚举正确使用==,尽管这可能是一个小问题。


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


    使用==枚举很容易工作的原因是,每个定义的实例也是一个单例。所以使用==进行身份比较总是有效的。

    但是使用==是因为它与枚举一起工作意味着所有代码都与该枚举的使用紧密耦合。

    例如:枚举可以实现接口。假设您当前使用的枚举实现了接口1。如果以后,有人更改它或引入一个新的类impl1作为同一接口的实现。然后,如果您开始使用impl1实例,您将有许多代码要更改和测试,因为以前使用了==。

    因此,最好遵循被视为良好做法的做法,除非有任何合理的收益。


    DR

    另一种选择是Objects.equals效用方法。

    1
    Objects.equals( thisEnum , thatEnum )

    Objects.equals

    equals operator == instead of .equals()

    Which operator is the one I should be using?

    第三个选项是在EDCOX1 14的实用工具类上添加到Java 7和以后的静态EDCOX1 OR 1方法。

    例子

    下面是一个使用Month枚举的示例。

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

    效益

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

    • 零安全性
      • 都是空的吗?true
      • 要么是空的?false
      • 无抛掷NullPointerException的风险
    • 紧凑、可读

    它是如何工作的

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

    请参见OpenJDK的Java 10源代码:

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


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

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

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


    我想补充聚原润滑油的答案:

    我个人更喜欢平等。但它需要进行类型兼容性检查。我认为这是一个重要的限制。

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

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

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

    我还建议为枚举添加未定义的值。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
        }
    }


    我想明确强调一下==操作符和equals()方法之间的具体区别:

    equals()方法用于检查所涉及的引用变量引用的对象的内容是否相同。

    ==运算符检查涉及的引用变量是否引用同一对象。

    由实现类根据应用程序的需要提供这种差异。

    否则,默认行为将由EDCOX1的27个类(爪哇)提供,如http://DOCS.Oracle .COM/JavaSe/1.5.0/DOCS/API/Java/Lang/Objult.HTML*Aythas(Java.Lang.Object)中所解释的:

    The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).


    中间的枚举是一组常量整数。"=="与比较两个整数一样有效和正确。