【问题标题】:Comparing Java enum members: == or equals()?比较 Java 枚举成员:== 或 equals()?
【发布时间】:2010-12-17 13:13:46
【问题描述】:

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

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

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

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

我应该使用哪个运算符?

【问题讨论】:

  • 我刚刚偶然发现了一个非常相似的问题:stackoverflow.com/questions/533922/…
  • 令我惊讶的是,在所有答案中(尤其是来自 polygenelubricants 的答案,它详细解释了 == 的工作原理)没有提到 == 的另一大好处:它明确说明了枚举的方式工作(作为一组固定的单例对象)。使用 equals 时,人们会认为同一枚举“替代”的多个实例可能会以某种方式浮动。

标签: java enums


【解决方案1】:

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

不过,我使用 ==,因为它是 null 安全的。

【讨论】:

  • 另一个使用==而不是equals的参数是类型的编译时检查。 myEnum.MY_CONSTANT.equals("Some String") 将编译,myEnum.MY_CONSTANT == "Some String" 不会编译,因为 "Some String" 不是同一类型,编译器可以预先确定它
【解决方案2】:

== 可以用在enum 上吗?

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

JLS 8.9 Enums

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

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

因为每个enum 常量只有一个实例,在比较两个对象引用时,如果知道至少一个其中指的是enum 常量。 (Enum 中的equals 方法是一个final 方法,它仅在其参数上调用super.equals 并返回结果,从而执行身份比较。)

这个保证足够强大,Josh Bloch 建议,如果你坚持使用单例模式,最好的实现方法是使用单元素enum(参见:Effective Java 2nd Edition,第 3 项:使用私有构造函数或枚举类型强制执行单例属性;还有Thread safety in Singleton)


==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 上使用== 的论据是:

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

【讨论】:

    【解决方案3】:

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

    顺便说一句,实际上没有必要使用== 来编写空安全代码,如果你像这样编写equals()

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

    这是一个被称为Compare Constants From The Left 的最佳实践,您绝对应该遵循。

    【讨论】:

    • null 枚举通常是一个错误。你已经得到了所有可能值的枚举,这是另一个!
    • 从左侧比较常量是一种最佳实践,例如尤达风格的最佳英语。
    【解决方案4】:

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

    但是:

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

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

    总之,我仍然认为这些论点支持将.equals() 用于enum 类型。

    【讨论】:

      【解决方案5】:

      我更喜欢使用== 而不是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 中使用了等号,即first.pckg.Category,但是您在没有意识到的情况下导入了第二个枚举 (second.pckg.Category) 而不是第一个:

      import second.pckg.Category;
      ...
      
      Category.JAZZ.equals(item.getCategory())
      

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

      因此,如果您改为使用运算符==,您将遇到编译错误:

      operator == 不能应用于“second.pckg.Category”、“first.pckg.Category”

      import second.pckg.Category; 
      ...
      
      Category.JAZZ == item.getCategory() 
      

      【讨论】:

      • 这是一个重要的检查,值得一提
      【解决方案6】:

      tl;博士

      另一个选项是Objects.equals 实用方法。

      Objects.equals( thisEnum , thatEnum )
      

      Objects.equals 用于零安全

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

      我应该使用哪个运算符?

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

      示例

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

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

      好处

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

      • 空安全
      • 紧凑、易读

      工作原理

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

      您自己看看,来自OpenJDKJava 10 source code

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

      【讨论】:

        【解决方案7】:

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

        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
        

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

        我一次用两个 IF 运行了几次这个测试。 “==”的速度比以往任何时候都快。

        【讨论】:

          【解决方案8】:

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

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

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

          • equals()更安全

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

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

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

          【讨论】:

            【解决方案9】:

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

            【讨论】:

              【解决方案10】:

              使用== 以外的任何东西来比较枚举常量都是无稽之谈。就像comparing class objects with equals - 不要这样做!

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

              【讨论】:

                【解决方案11】:

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

                【讨论】:

                  【解决方案12】:

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

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

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

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

                  【讨论】:

                    【解决方案13】:

                    总之,两者各有利弊。

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

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

                    【讨论】:

                      【解决方案14】:

                      只有一件事可以添加到所有其他出色的答案中。当您使用简单的 lambda 时,我更喜欢 equals 而不是 ==,因为您可以使用方法引用。

                      考虑以下 lambda:

                      Stream.of(SomeEnum.A, SomeEnum.B).anyMatch(e -> e == SomeEnum.B);
                      Stream.of(SomeEnum.A, SomeEnum.B).anyMatch(e -> e.equals(SomeEnum.B));
                      

                      后者可以转换为:

                      Stream.of(SomeEnum.A, SomeEnum.B).anyMatch(SomeEnum.B::equals));
                      

                      【讨论】:

                        【解决方案15】:

                        我想补充多基因润滑剂答案:

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

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

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

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

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

                        【讨论】:

                        • 为什么这个解决方案比==更好?使用==,您可以获得 NPE 保护、易于阅读的代码和编译时类型兼容性检查,并且您无需编写任何自定义的类似 equals 的方法即可获得所有这些。
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-05-04
                        • 1970-01-01
                        • 2023-03-09
                        • 2023-03-17
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多