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


1735

我知道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)
    {
        ...
    }
    ...
}

我应该使用哪个运算符?


7
我对面就有一个非常类似的问题绊倒:stackoverflow.com/questions/533922/...
马特·鲍尔

39
我很惊讶,在所有答案中(特别是来自polygenelubricants的答案,其中详细解释了==的工作原理)没有提到==的另一个大好处:它明确表明了枚举的工作方式(作为固定的单例集)对象)。使用equals会使人们认为某种程度上可以存在同一枚举“替代”的多个实例。
斯图尔特·罗西特

6
把事情简单化。“ ==”更具可读性,并且可以工作,因为默认情况下枚举是单例的。参考
lokesh

Answers:


1574

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

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


64
就我个人而言,我不喜欢将“零安全性”作为使用==的理由。
Nivas

257
@Nivas:为什么不呢?您是否想担心参数的顺序(仅适用于比较左边的常量,如Pascal的回答)?您是否喜欢在.equals()输入值之前始终检查其值是否为null ?我知道我不知道
Matt Ball

86
请记住,在阅读代码时,“ ==“可能对读者来说是不正确的,直到他/她离开并查看所涉及类型的源代码,然后才看到它是一个枚举。从这个意义上讲,阅读“ .equals()”可以减少分心。
凯文·布瑞里恩

60
@Kevin-如果您没有使用允许您在查看源代码时直接查看类型的IDE,那是在欺骗自己-正确的IDE并不是问题。
MetroidFan2002

63
有时代码会被重构,并且枚举有可能被类替换,此时==不再保证正确,而equals继续无缝运行。因此,平等显然是更好的选择。如果您想要空值安全(因为您在未编写代码的基础上工作,以尽量减少空值的使用),则可以使用Apache ObjectUtils或Guava的Objects#equal(或者可以自己滚动)。我什至不提“表现”一词,因为担心有人正确地用唐纳德·努斯的一本书打我。
laszlok 2013年

1113

可以==使用enum吗?

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

JLS 8.9枚举

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

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

因为只有一个每个实例enum常数,它允许使用的==操作者代替的equals比较两个对象的引用时,如果已知它们中的至少一个是指方法enum常数。(equalsin Enum中的final方法是仅调用super.equals其自变量并返回结果,从而执行身份比较的方法。)

Josh Bloch建议的这种保证足够强大,如果您坚持使用单例模式,则实现它的最佳方法是使用单元素enum(请参阅:有效的Java 2nd Edition,项目3:使用私有构造函数或枚举类型;也包括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)if和only if a==b。如果一个类保证了这一点,那么它的客户可以使用==运算符而不是equals(Object)方法,这可能会提高性能。枚举类型提供了这种保证。

总而言之,使用==on 的参数为enum

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

27
不错的答案,但是... 1. 它起作用:同意2. 更快:为枚举证明:) 3. 它在运行时更安全Color nothing = null;是恕我直言是一个错误,应该修复,您的枚举有两个值,而不是三个(请参阅Tom Hawtin的评论。)4 .编译时更安全:好的,但是equals仍然会返回false。最后,不购买它,我更喜欢它equals()的可读性(请参阅Kevin的评论)。
Pascal Thivent 2010年

201
这是Java的一个黑标,有些人认为它foo.equals(bar)比可读性更好foo == bar
Bennett McElwee 2012年

19
它的工作原理是:在重构之前,需要用类替换枚举。祝您使用==打破所有代码。它更快:即使是true(可疑),也有Knuth引用过早优化。在运行时更安全:如果仅使用==可以将您与NullPointerException分开,那么“安全”并不是您想到的第一个单词。编译时更安全:并非如此。它可以保护您免受比较错误类型的基本错误的困扰,但是同样,如果您的程序员如此愚蠢,“安全”就是您所不想要的一件事。
laszlok 2013年

35
“如果您的程序员这么愚蠢,那么“安全”就是您所不想要的一件事。” -我不同意这句话。这就是类型检查的全部内容:在编译时(甚至在代码运行之前)防止“愚蠢”错误。即使是聪明的人也会犯傻的错误,有些保证比承诺要好。
ymajoros

7
@laszlok:当然可以,但是如果事情失败了,他们只会失败。有一些愚蠢的错误的栅栏总是一件好事。对于较少的琐碎错误,还有很多创造力的空间,让编译器在尝试运行代码之前先处理这一问题。
ymajoros 2014年

88

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

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

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

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


9
我在.equals()输入字符串值时会执行此操作,但是我仍然认为这是编写空安全代码的一种卑鄙的方式。我宁愿有一个运算符(或方法,但我知道[null object] .equals在Java中永远不会是null安全的,因为点运算符是如何工作的),无论如何,它始终是null安全的语义上,“等于”运算符应始终上下班。
Matt Ball

2
好的,由于Java没有Groovy(?.)的“安全导航”运算符,因此在与常量进行比较时,我将继续使用这种做法,而TBH,我认为它并不糟糕,也许不那么“自然”。
Pascal Thivent 09年

32
null枚举通常是一个错误。您已经对所有可能的值进行了枚举,这是另一个!
Tom Hawtin-抢险活动

8
除了明确标记为的值外@Nullable,我认为从左边比较常量是一种反模式,因为它散布了哪些值可以为null或不能为null的模糊性。枚举类型几乎不应该是@Nullable,因此null值将是编程错误。在这种情况下,您越早抛出NPE,就越有可能在允许它为null的位置发现编程错误。因此,当涉及到枚举类型时,“最佳实践……您一定要遵循”是完全错误的。
Tobias

24
比较“从左常量”的最佳实践,例如Yoda风格的最佳英语。
maaartinus 2014年

58

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

然而:

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

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

综上所述,笔者仍然认为论点赞成使用.equals()enum类型。


4
好吧,关于第3点,我可以enum用作switch目标,因此它们有些特殊。Strings也有点特殊。
CurtainDog 2014年

switch语句的语法既不包含==也不包含.equals(),所以我看不出您的意思是什么。我的观点3.)是关于为读者验证代码有多么容易。只要switch语句编译,switch语句的相等语义都是正确的。
Tobias

17

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

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

第一个枚举

package first.pckg

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

第二枚举:

package second.pckg

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

然后假设您使用等价于下一个的equals item.categoryfirst.pckg.Category但是您导入了第二个枚举(second.pckg.Category)而不是第一个枚举,而没有意识到:

import second.pckg.Category;
...

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

因此,false尽管您期望true是因为item.getCategory()is,但您将总是得到一个不同的枚举JAZZ。这可能很难看。

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

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

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 

你提到的非常好的观点。因此,我要做的就是应用myenum.value(),然后对NPE安全性进行==比较。
Java Main

14

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

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多次运行了此测试。“ ==”的速度是如此之快。


我怀疑编译器分支预测可能会使此测试无效。一个聪明的编译器会认识到,这两个if语句都将始终为false,从而避免进行多次比较。一个真正聪明的编译器甚至可能意识到y和x循环中的值将不会对结果产生影响,并且会优化整个过程……
Darrel Hoffman

可以,但是绝对不能。我在答案中显示了实际的编译器生成的字节码。
ChrisCantrell

你们都很困惑。:-)分支预测在运行时有效,因此不同的字节代码不是超级相关。但是,在这种情况下,分支预测将同等地帮助这两种情况。
vidstige

7
谁在乎?除非您在视频游戏中在循环内将枚举进行数千次比较,否则多余的纳秒是没有意义的。
user949300

除非您强制使用解释模式,否则字节码与性能无关。编写一个JMH基准测试以查看性能完全相同。
maaartinus


7

tl; dr

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

Objects.equals( thisEnum , thatEnum )

Objects.equals 为零安全

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

我应该使用哪个运算符?

第三个选项是在添加到Java 7及更高版本equalsObjects实用程序类上找到的静态方法。

这是使用Month枚举的示例。

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

好处

我发现此方法有很多好处:

  • 零安全
  • 紧凑,可读

怎么运行的

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

OpenJDKJava 10源代码中亲自了解一下:

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

6

除了==比较枚举常量以外,使用其他任何方法都是没有意义的。这就像class对象进行比较equals –请勿这样做!

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


4

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


1

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

但是使用==因为它可以与枚举一起使用,这意味着所有代码都与该枚举的使用紧密相关。

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

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


不错的答案,但是这里已经提到
shmosel

嗯 那时,问题将是关于在接口上使用==还是等于。加上各种问题,用可变的实现替换不可变类型的实现是否真的明智。在担心合理的收益之前,弄清楚什么是良好的实践可能是明智的。(imho ==是更好的做法)。
罗宾·戴维斯

1

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

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

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

  • 比...更安全 equals()

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

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

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


0

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

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

另一方面,如果您出于某种原因使用枚举,则用其他方法(正常的类实例)替换枚举==。(BTDT。)


-1

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

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

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

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

这样,您将获得两种解决方案的全部优势:NPE保护,易于阅读的代码以及在编译时进行类型兼容性检查。

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


4
为什么这个解决方案比哪个更好==?有了==NPE保护,易于阅读的代码以及编译时类型兼容性检查,您无需编写任何类似于自定义equals的方法即可
马特·鲍尔

1
一个UNDEFINED值可能是一个好主意。当然,在Java 8中,您可以使用Optional
托马斯2015年

-7

==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
    }
}

2
这个问题的范围很明确,原始类型不在范围之内。
汤姆(Tom)

@Tom在这里进行的讨论具有误导性,==永远不会抛出NPEs。这个问题有足够的答案,但是如果像我这样花了2个小时来尝试bug /功能的人读到了此内容,这可能是一个很好的参考。
ayushgp

1
当您不忽略此问题的主题时,我无法抛出NPE。即使将答案保留在此处,您是否也认为与您有相同问题的人通过搜索其他内容可以找到此答案?该人将需要搜索他们显然不使用的枚举。:这个答案会使得对这个问题更有意义stackoverflow.com/questions/7520432/...
汤姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.