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

我知道Java枚举被编译为带有私有构造函数和一堆公共静态成员的类。

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

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

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

哪个操作员是我应该使用的?

938496 次浏览

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

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

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

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

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

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

这是一个被称为从左边比较常量的最佳实践,你绝对应该遵循。

==可以用在enum上吗?

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

JLS 8.9枚举

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

尝试显式实例化枚举类型是编译时错误。Enum中的final clone方法确保enum常量永远不能被克隆,序列化机制的特殊处理确保反序列化永远不会创建重复实例。禁止枚举类型的反射实例化。这四件事一起确保enum类型的实例不存在超过enum常量定义的实例。

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

这种保证足够强大,Josh Bloch建议,如果您坚持使用单例模式,实现它的最佳方法是使用单元素enum(请参阅:Java第2版生效,第3项:使用私有构造函数或枚举类型强制执行单例属性;另请参阅Singleton中的线程安全


==equals有什么区别?

需要提醒的是,一般来说,==不是equals的可行替代品。然而,当它是时(例如enum),有两个重要的区别需要考虑:

==从不抛出NullPointerException

enum Color { BLACK, WHITE };
Color nothing = null;if (nothing == Color.BLACK);      // runs fineif (nothing.equals(Color.BLACK)); // throws NullPointerException

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

enum Color { BLACK, WHITE };enum Chiral { LEFT, RIGHT };
if (Color.BLACK.equals(Chiral.LEFT)); // compiles fineif (Color.BLACK == Chiral.LEFT);      // DOESN'T COMPILE!!! Incompatible types!

==应该在适用的情况下使用吗?

Bloch特别提到,对其实例具有适当控制的不可变类可以向其客户端保证==是可用的。

项目1:考虑静态工厂方法而不是构造函数

[…]它允许不可变类保证不存在两个相等的实例:a.equals(b)当且仅当a==b。如果一个类做出这种保证,那么它的客户端可以使用==运算符而不是equals(Object)方法,这可能会提高性能。枚举类型提供了这种保证。

总而言之,在enum上使用==的参数是:

  • 它奏效了。
  • 它更快。
  • 在运行时更安全。
  • 在编译时更安全。

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

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一次一个地运行了这个测试几次。“==”稍微快一点。

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

但是:

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

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

综上所述,我仍然认为参数赞成对enum类型使用.equals()

我想补充多基因洗脱剂的答案:

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

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

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

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

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

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

但是,Sun JDK 6u10及更早版本中存在一个令人讨厌的bug(BugId6277781),由于历史原因可能会很有趣。这bug阻止在反序列化枚举上正确使用==,尽管这可能是一种角落情况。

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

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

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

第一个枚举

package first.pckg
public enum Category {JAZZ,ROCK,POP,POP_ROCK}

第二个枚举:

package second.pckg
public enum Category {JAZZ,ROCK,POP,POP_ROCK}

然后假设您使用了像item.category中的next这样的等于,即first.pckg.Category,但您导入了第二个枚举(second.pckg.Category)而不是第一个枚举而没有意识到:

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

因此,您将始终得到false,因为它是一个不同的枚举,尽管您期望为true,因为item.getCategory()JAZZ。这可能有点难以看到。

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

运算符==不能应用于"second.pckg.类别","first.pckg.类别"

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

简而言之,两者都有优点和缺点。

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

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

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

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

例如:枚举可以实现一个接口。假设你当前使用的是一个实现Interface1的枚举。如果稍后有人更改了它或引入了一个新的类Imp1作为同一接口的实现。然后,如果你开始使用Imp1的实例,由于之前使用了==,你将有很多代码需要更改和测试。

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

tl; dr

另一个选项是#0实用程序方法。

Objects.equals( thisEnum , thatEnum )

Objects.equals为空安全

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

哪个操作员是我应该使用的?

第三个选项是#1实用程序类添加到Java7及更高版本上的静态#0方法。

示例

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

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

好处

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

  • 零安全
    • 均为nulltrue
    • falsefalse
    • 没有风险#0
  • 简洁易读

它是如何运作的

#0使用的逻辑是什么?

看看你自己,从OpenJDKJava10源代码

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

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

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

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

  • equals()更安全

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

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

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

还有一件事要补充到所有其他优秀的答案中。当你使用一个简单的lambda时,我确实更喜欢equals而不是==,因为你可以使用方法引用。

考虑以下lambdas:

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