简体   繁体   English

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

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

I know that Java enums are compiled to classes with private constructors and a bunch of public static members.我知道 Java 枚举被编译为带有私有构造函数和一堆公共静态成员的类。 When comparing two members of a given enum, I've always used .equals() , eg在比较给定枚举的两个成员时,我一直使用.equals() ,例如

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

However, I just came across some code that uses the equals operator == instead of .equals():但是,我刚刚遇到了一些使用 equals 运算符==而不是 .equals() 的代码:

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

Which operator is the one I should be using?我应该使用哪个运算符?

Both are technically correct.两者在技术上都是正确的。 If you look at the source code for .equals() , it simply defers to == .如果您查看.equals()的源代码,它只是.equals() ==

I use == , however, as that will be null safe.但是,我使用== ,因为这将是空安全的。

Can == be used on enum ? ==可以用在enum吗?

Yes: enums have tight instance controls that allows you to use == to compare instances.是的:枚举具有严格的实例控制,允许您使用==来比较实例。 Here's the guarantee provided by the language specification (emphasis by me):这是语言规范提供的保证(我强调):

JLS 8.9 Enums JLS 8.9 枚举

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. Enumfinal clone方法确保永远无法克隆enum常量,并且序列化机制的特殊处理确保永远不会因反序列化而创建重复实例。 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.这四件事一起确保enum类型的实例不存在于enum常量定义的实例之外。

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

This guarantee is strong enough that Josh Bloch recommends, that if you insist on using the singleton pattern, the best way to implement it is to use a single-element enum (see: Effective Java 2nd Edition, Item 3: Enforce the singleton property with a private constructor or an enum type ; also Thread safety in Singleton ) Josh Bloch 推荐的这种保证足够强大,如果您坚持使用单例模式,实现它的最佳方法是使用单元素enum (请参阅: Effective Java 2nd Edition,Item 3:Enforce the singleton property with私有构造函数或枚举类型;还有单例中的线程安全


What are the differences between == and equals ? ==equals什么区别?

As a reminder, it needs to be said that generally, == is NOT a viable alternative to equals .提醒一下,需要说明的是,通常==不是equals的可行替代方案。 When it is, however (such as with enum ), there are two important differences to consider:然而,当它是(例如enum )时,需要考虑两个重要的区别:

== never throws NullPointerException ==从不抛出NullPointerException

enum Color { BLACK, WHITE };

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

== is subject to type compatibility check at compile time ==在编译时进行类型兼容性检查

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!

Should == be used when applicable?应该在适用时使用==吗?

Bloch specifically mentions that immutable classes that have proper control over their instances can guarantee to their clients that == is usable. Bloch 特别提到,对其实例进行适当控制的不可变类可以向其客户保证==可用。 enum is specifically mentioned to exemplify. enum被特别提到作为例证。

Item 1: Consider static factory methods instead of constructors第 1 条:考虑静态工厂方法而不是构造函数

[...] it allows an immutable class to make the guarantee that no two equal instances exist: a.equals(b) if and only if a==b . [...] 它允许不可变类保证不存在两个相等的实例: a.equals(b)当且仅当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.如果一个类做出了这个保证,那么它的客户端可以使用==运算符而不是equals(Object)方法,这可能会提高性能。 Enum types provide this guarantee.枚举类型提供了这种保证。

To summarize, the arguments for using == on enum are:总而言之,在enum上使用==的参数是:

  • It works.有用。
  • It's faster.它更快。
  • It's safer at run-time.它在运行时更安全。
  • It's safer at compile-time.在编译时更安全。

Using == to compare two enum values works, because there is only one object for each enum constant.使用==比较两个枚举值是有效的,因为每个枚举常量只有一个对象。

On a side note, there is actually no need to use == to write null-safe code, if you write your equals() like this:附带说明一下,如果您像这样编写equals() ,则实际上不需要使用==来编写空安全代码:

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

This is a best practice known as Compare Constants From The Left that you definitely should follow.这是一种称为从左侧比较常量的最佳实践,您绝对应该遵循。

As others have said, both == and .equals() work in most cases.正如其他人所说, ==.equals()在大多数情况下都有效。 The compile time certainty that you're not comparing completely different types of Objects that others have pointed out is valid and beneficial, however the particular kind of bug of comparing objects of two different compile time types would also be found by FindBugs (and probably by Eclipse/IntelliJ compile time inspections), so the Java compiler finding it doesn't add that much extra safety.您没有比较其他人指出的完全不同类型的对象的编译时确定性是有效且有益的,但是 FindBugs 也会发现比较两种不同编译时类型的对象的特定类型的错误(并且可能通过Eclipse/IntelliJ 编译时检查),因此 Java 编译器发现它并没有增加太多额外的安全性。

However:然而:

  1. The fact that == never throws NPE in my mind is a disadvantage of == .这样的事实==不会抛出NPE在我心目中是一个缺点== There should hardly ever be a need for enum types to be null , since any extra state that you may want to express via null can just be added to the enum as an additional instance.几乎不需要enum类型为null ,因为您可能想要通过null表达的任何额外状态都可以作为附加实例添加到enum中。 If it is unexpectedly null , I'd rather have a NPE than == silently evaluating to false.如果它意外地为null ,我宁愿有一个 NPE 而不是==默默地评估为假。 Therefore I disagree with the it's safer at run-time opinion;因此,我不同意运行时更安全的观点; it's better to get into the habit never to let enum values be @Nullable .最好养成永远不要让enum值为@Nullable的习惯。
  2. The argument that == is faster is also bogus. ==更快的论点也是虚假的。 In most cases you'll call .equals() on a variable whose compile time type is the enum class, and in those cases the compiler can know that this is the same as == (because an enum 's equals() method can not be overridden) and can optimize the function call away.在大多数情况下,您将在编译时类型为 enum 类的变量上调用.equals() ,在这些情况下,编译器可以知道这与==相同(因为enumequals()方法可以不被覆盖)并且可以优化函数调用。 I'm not sure if the compiler currently does this, but if it doesn't, and turns out to be a performance problem in Java overall, then I'd rather fix the compiler than have 100,000 Java programmers change their programming style to suit a particular compiler version's performance characteristics.我不确定编译器当前是否这样做,但如果没有,并且结果证明是 Java 整体的性能问题,那么我宁愿修复编译器也不愿让 100,000 个 Java 程序员改变他们的编程风格以适应特定编译器版本的性能特征。
  3. enums are Objects. enums是对象。 For all other Object types the standard comparison is .equals() , not == .对于所有其他 Object 类型,标准比较是.equals() ,而不是== I think it's dangerous to make an exception for enums because you might end up accidentally comparing Objects with == instead of equals() , especially if you refactor an enum into a non-enum class.我认为为enums例外是危险的,因为您最终可能会不小心将 Objects 与==而不是equals()进行比较,特别是如果您将enum重构为非枚举类。 In case of such a refactoring, the It works point from above is wrong.在这种重构的情况下,上面的It 工作点是错误的。 To convince yourself that a use of == is correct, you need to check whether value in question is either an enum or a primitive;为了让自己相信==的使用是正确的,您需要检查所讨论的值是enum还是原始值; if it was a non- enum class, it'd be wrong but easy to miss because the code would still compile.如果它是一个非enum类,它会是错误的但很容易错过,因为代码仍然可以编译。 The only case when a use of .equals() would be wrong is if the values in question were primitives;使用.equals()错误的唯一情况是所讨论的值是原始值; in that case, the code wouldn't compile so it's much harder to miss.在这种情况下,代码将无法编译,因此更难错过。 Hence, .equals() is much easier to identify as correct, and is safer against future refactorings.因此, .equals()更容易被识别为正确的,并且对未来的重构更安全。

I actually think that the Java language should have defined == on Objects to call .equals() on the left hand value, and introduce a separate operator for object identity, but that's not how Java was defined.我实际上认为 Java 语言应该在对象上定义 == 以在左侧值上调用 .equals(),并为对象标识引入一个单独的运算符,但这不是 Java 的定义方式。

In summary, I still think the arguments are in favor of using .equals() for enum types.总之,我仍然认为参数支持使用.equals()作为enum类型。

I prefer to use == instead of equals :我更喜欢使用==而不是equals

Other reason, in addition to the others already discussed here, is you could introduce a bug without realizing it.除了这里已经讨论的其他原因之外,其他原因是您可能会在没有意识到的情况下引入错误。 Suppose you have this enums which is exactly the same but in separated pacakges (it's not common, but it could happen):假设您有这个完全相同的枚举,但在单独的包中(这并不常见,但可能会发生):

First enum :第一个枚举

package first.pckg

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

Second enum:第二个枚举:

package second.pckg

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

Then suppose you use the equals like next in item.category which is first.pckg.Category but you import the second enum ( second.pckg.Category ) instead the first without realizing it:然后假设你在item.category使用了像 next 一样的item.category ,它是first.pckg.Category但你导入了第二个枚举( second.pckg.Category )而不是第一个没有意识到它:

import second.pckg.Category;
...

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

So you will get allways false due is a different enum although you expect true because item.getCategory() is JAZZ .因此,尽管您期望 true 是因为item.getCategory()JAZZ但由于是不同的枚举,您总是会得到false And it could be be a bit difficult to see.而且可能有点难看。

So, if you instead use the operator == you will have a compilation error:因此,如果您改为使用运算符== ,则会出现编译错误:

operator == cannot be applied to "second.pckg.Category", "first.pckg.Category"运算符 == 不能应用于“second.pckg.Category”、“first.pckg.Category”

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 

tl;dr tl;博士

Another option is the Objects.equals utility method.另一种选择是Objects.equals实用程序方法。

Objects.equals( thisEnum , thatEnum )

Objects.equals for null-safety Objects.equals用于 null 安全

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

Which operator is the one I should be using?我应该使用哪个运算符?

A third option is the static equals method found on the Objects utility class added to Java 7 and later.第三个选项是在Java 7及更高版本中添加Objects实用程序类中的静态equals方法。

Example例子

Here's an example using theMonth enum.这是使用Month枚举的示例。

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

Benefits好处

I find a couple benefits to this method:我发现这种方法有几个好处:

  • Null-safety零安全
  • Compact, readable紧凑、可读

How it works这个怎么运作

What is the logic used by Objects.equals ? Objects.equals使用的逻辑是什么?

See for yourself, from the Java 10 source code of OpenJDK :亲自查看OpenJDKJava 10 源代码

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

Here is a crude timing test to compare the two:这是一个粗略的时间测试来比较两者:

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());
    }   

}

Comment out the IFs one at a time.一次注释掉一个 IF。 Here are the two compares from above in disassembled byte-code:以下是上面反汇编字节码中的两个比较:

 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

The first (equals) performs a virtual call and tests the return boolean from the stack.第一个 (equals) 执行虚拟调用并测试堆栈中的返回布尔值。 The second (==) compares the object addresses directly from the stack.第二个 (==) 直接从堆栈中比较对象地址。 In the first case there is more activity.在第一种情况下有更多的活动。

I ran this test several times with both IFs one at a time.我用两个 IF 一次一个地运行了这个测试。 The "==" is ever so slightly faster. “==”总是稍微快一点。

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

One of the Sonar rules is Enum values should be compared with "==" .声纳规则之一是Enum values should be compared with "==" The reasons are as follows:原因如下:

Testing equality of an enum value with equals() is perfectly valid because an enum is an Object and every Java developer knows == should not be used to compare the content of an Object.使用equals()测试枚举值的equals()是完全有效的,因为枚举是一个对象,每个 Java 开发人员都知道==不应该用于比较对象的内容。 At the same time, using == on enums:同时,在枚举上使用==

  • provides the same expected comparison (content) as equals()提供与equals()相同的预期比较(内容equals()

  • is more null-safe than equals()equals()更空安全

  • provides compile-time (static) checking rather than runtime checking提供编译时(静态)检查而不是运行时检查

For these reasons, use of == should be preferred to equals() .由于这些原因,应该优先使用==不是equals()

Last but not least, the == on enums is arguably more readable (less verbose) than equals() .最后但并非最不重要的一点是,枚举上的==可以说比equals()更具可读性(更简洁equals()

Using anything other than == to compare enum constants is nonsense.使用==以外的任何东西来比较枚举常量都是无稽之谈。 It's like comparing class objects with equals – don't do it!这就像class对象与equals进行比较——不要这样做!

However, there was a nasty bug (BugId 6277781 ) in Sun JDK 6u10 and earlier that might be interesting for historical reasons.但是,由于历史原因,Sun JDK 6u10 及更早版本中存在一个令人讨厌的错误 ( BugId 6277781 )。 This bug prevented proper use of == on deserialized enums, although this is arguably somewhat of a corner case.这个错误阻止了在反序列化枚举上正确使用== ,尽管这可以说是一种极端情况。

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

The reason enums work easily with == is because each defined instance is also a singleton.枚举很容易与 == 一起工作的原因是因为每个定义的实例也是一个单例。 So identity comparison using == will always work.因此,使用 == 进行身份比较将始终有效。

But using == because it works with enums means all your code is tightly coupled with usage of that enum.但是使用 == 因为它适用于枚举意味着您的所有代码都与该枚举的使用紧密结合。

For example: Enums can implement an interface.例如:枚举可以实现一个接口。 Suppose you are currently using an enum which implements Interface1.假设您当前正在使用一个实现 Interface1 的枚举。 If later on, someone changes it or introduces a new class Impl1 as an implementation of same interface.如果稍后有人更改它或引入一个新类 Impl1 作为同一接口的实现。 Then, if you start using instances of Impl1, you'll have a lot of code to change and test because of previous usage of ==.然后,如果您开始使用 Impl1 的实例,由于以前使用 ==,您将需要更改和测试大量代码。

Hence, it's best to follow what is deemed a good practice unless there is any justifiable gain.因此,除非有任何合理的收益,否则最好遵循被认为是好的做法。

In short, both have pros and cons.简而言之,两者各有利弊。

On one hand, it has advantages to use == , as described in the other answers.一方面,如其他答案中所述,使用==具有优势。

On the other hand, if you for any reason replace the enums with a different approach (normal class instances), having used == bites you.另一方面,如果您出于任何原因用不同的方法(普通类实例)替换枚举,使用==咬你。 (BTDT.) (BTDT。)

I want to complement polygenelubricants answer:我想补充 polygenelubricants 答案:

I personally prefer equals().我个人更喜欢equals()。 But it lake the type compatibility check.但它需要类型兼容性检查。 Which I think is an important limitation.我认为这是一个重要的限制。

To have type compatibility check at compilation time, declare and use a custom function in your enum.要在编译时进行类型兼容性检查,请在枚举中声明并使用自定义函数。

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

With this, you got all the advantage of both solution: NPE protection, easy to read code and type compatibility check at compilation time.有了这个,您就获得了两种解决方案的所有优势:NPE 保护、易于阅读的代码和编译时的类型兼容性检查。

I also recommend to add an UNDEFINED value for enum.我还建议为枚举添加一个 UNDEFINED 值。

== can throw NullPointerException if a primitive type is being compared to its Class version. 如果将原始类型与其类版本进行比较,则==可以引发NullPointerException For example: 例如:

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