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

时间:2009-11-17 17:26:27

标签: java enums

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

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

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

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

我应该使用哪个运算符?

15 个答案:

答案 0 :(得分:1403)

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

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

答案 1 :(得分:1020)

==可以使用enum吗?

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

  

JLS 8.9 Enums

     

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

     

尝试显式实例化枚举类型是编译时错误。 final clone中的Enum方法确保永远不会克隆enum常量,并且序列化机制的特殊处理可确保不会因反序列化而创建重复实例。禁止对枚举类型进行反射实例化。总之,这四件事确保enum类型的实例不会超出enum常量定义的实例。

     

因为每个enum常量只有一个实例,允许在比较两个对象引用时使用==运算符代替equals方法众所周知,其中至少有一个是指enum常数。 (equals中的Enum方法是final方法,仅在其参数上调用super.equals并返回结果,从而执行身份比较。)

这种保证足够强大,Josh Bloch建议,如果你坚持使用单例模式,实现它的最好方法是使用单元素enum(参见: Effective Java 2nd版本,第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的论据是:

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

答案 2 :(得分:77)

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

另外,如果你像这样编写==,实际上不需要使用equals()来编写空安全代码:

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

这是一种称为Compare Constants From The Left的最佳做法,你绝对应该遵循。

答案 3 :(得分:59)

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

然而:

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

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

答案 4 :(得分:13)

我更喜欢使用==代替equals

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

第一次枚举

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)而不是第一个枚举(<{1}}):

import second.pckg.Category;
...

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

因此,false item.getCategory() JAZZ == import second.pckg.Category; ... Category.JAZZ == item.getCategory() ui-icon-gear fa fa-cog pom.xml <dependency> <groupId>com.github.jepsar</groupId> <artifactId>primefaces-theme-jepsar</artifactId> <version>0.9.1</version> </dependency> faces-config.xml它可能有点难以看到。

因此,如果您改为使用运算符<application> <resource-handler>org.jepsar.primefaces.theme.jepsar.FontAwesomeResourceHandler</resource-handler> </application> ,则会出现编译错误:

  

operator ==无法应用于&#34; second.pckg.Category&#34;,&#34; first.pckg.Category&#34;

  if yellowView.isHidden == true {
            //how is the code?
            ibHeightOutletOfYellow.constant = 0; // hide here
        } else {
            //how is the code?
            ibHeightOutletOfYellow.constant = 50; // as per your needed

        }

答案 5 :(得分:12)

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

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运行此测试几次。 &#34; ==&#34;是如此快一点。

答案 6 :(得分:9)

如果枚举都正确且正确!!

答案 7 :(得分:6)

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

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

答案 8 :(得分:5)

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

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

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

  • equals()

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

由于这些原因,==equals()更受欢迎。

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

答案 9 :(得分:4)

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

答案 10 :(得分:2)

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

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

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

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

答案 11 :(得分:2)

tl; dr

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

Objects.equals( thisEnum , thatEnum )

Objects.equals

  

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

     

我应该使用哪个运算符?

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

示例

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

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

好处

我发现这种方法有很多好处:

  • 安全性
  • 紧凑,可读

工作原理

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

Java 10 source codeOpenJDK看自己:

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

答案 12 :(得分:1)

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

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

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

答案 13 :(得分:0)

我想补充polygenelubricants答案:

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

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

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

通过这种方式,您可以获得两种解决方案的所有优势:NPE保护,易于读取的代码以及编译时的类型兼容性检查。

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

答案 14 :(得分:-4)

如果将原始类型与其类版本进行比较,

==可以抛出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
    }
}