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


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

我应该使用哪个运营商?


1421
2017-11-17 17:26


起源


我偶然发现了一个非常相似的问题: stackoverflow.com/questions/533922/... - Matt Ball
令我感到惊讶的是,在所有的答案中(特别是来自polygenelubricants的那个,详细解释了为什么==有效),没有提到==的另一大好处:它明确了枚举的工作原理(作为一组固定的单例)对象)。使用equals,它会导致人们认为可能会以某种方式存在同一个枚举“替代”的多个实例。 - Stuart Rossiter


答案:


两者在技术上都是正确的。如果你看一下源代码 .equals(),它只是按照 ==

我用 ==但是,因为这将是无效的。


1278
2017-11-17 17:29



就个人而言,我不喜欢'null safety'作为使用==的理由。 - Nivas
@Nivas:为什么不呢?你喜欢担心你的参数的顺序(这只适用于比较左边的常量,如Pascal的答案)?你喜欢总是先检查一个值是否为null .equals()它呢?我知道我没有。 - Matt Ball
请记住,在阅读代码时,“==”可能对读者来说不正确,直到他/她离开并查看所涉及类型的来源,然后看到它是枚举。从这个意义上说,阅读“.equals()”就不那么容易分散注意力了。 - Kevin Bourrillion
@Kevin - 如果您没有使用允许您在查看源代码时直接查看类型的IDE,那么您就是在欺骗自己 - 这不是正确IDE的问题。 - MetroidFan2002
有时代码会被重构,并且枚举可能被类替换,此时==不再保证是正确的,而equals继续无缝地工作。因此,equals显然是更好的选择。如果你想要null安全(因为你处理的代码库没有编写,以尽量减少使用空值),那么Apache ObjectUtils或Guava的Objects#等于(或者你可以自己动手)。我甚至都不会提到“表演”这个词,因为害怕有人用唐纳德·克努斯的书正确地打我。 - laszlok


能够 == 用于 enum

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

JLS 8.9枚举

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

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

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

这种保证足够强大,Josh Bloch建议,如果你坚持使用单例模式,实现它的最好方法是使用单个元素 enum (看到: Effective Java 2nd Edition,Item 3:使用私有构造函数或枚举类型强制执行singleton属性;也 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 是:

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

964
2018-05-30 04:38



不错的答案,但...... 1。 有用 :同意2。 它更快 :证明它为枚举:) 3。 它在运行时更安全 : Color nothing = null; 恕我直言是一个错误,应该修复,你的枚举有两个值,而不是三个(见汤姆霍廷的评论)4。 它在编译时更安全 : 好的但是 equals 仍然会回来 false。最后, 一世 不要买,我更喜欢 equals() 为了便于阅读(见Kevin的评论)。 - Pascal Thivent
有些人认为这是对Java的黑色印记 foo.equals(bar) 比...更具可读性 foo == bar - Bennett McElwee
它有效:直到你需要用类替换枚举作为重构的一部分。祝你好运找到使用==打破的所有代码。它更快:即使是真的(可疑的),还有Knuth引用的关于过早优化的内容。在运行时更安全:如果使用==是唯一将你与NullPointerException分开的东西,那么“安全”并不是第一个想到的词。编译时更安全:不是真的。它可以保护你免受比较错误类型的相当基本的错误,但同样,如果你的程序员是多么愚蠢,“安全”是你不是的一件事。 - laszlok
“如果你的程序员是多么愚蠢,”安全“是你不能做的事情。” - 我不同意这句话。这就是类型检查的全部内容:在编译时防止“哑”错误,甚至在代码运行之前。即使是聪明的人也会犯下愚蠢的错误,比承诺更能提供一些保障。 - ymajoros
@laszlok:当然,如果事情可能失败,他们就会。为一些愚蠢的错误设置围栏总是一件好事。对于不那么琐碎的错误,有足够的空间来创造,让编译器在您尝试运行代码之前处理这个错误。 - ymajoros


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

另外,实际上没有必要使用 == 如果你写你的安全代码写 equals() 喜欢这个:

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

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


72
2017-11-17 17:30



我这样做的时候 .equals()字符串值,但我仍然认为这是一种编写空安全代码的糟糕方法。我宁愿有一个运算符(或一个方法,但我知道[null对象] .equals在Java中不会因为点运算符的工作方式而在null中是安全的),它始终是空安全的,无论是什么命令它用于。语义上,“等于”运算符应该 总是 通勤。 - Matt Ball
好吧,因为Java没有Groovy的安全导航操作符(?.),我将继续使用这种做法,与常量相比,TBH,我不觉得它很糟糕,也许只是不那么“自然”。 - Pascal Thivent
一个 null 枚举通常是一个错误。你已经列举了所有可能的值,这是另一个值! - Tom Hawtin - tackline
除了明确注释为的值 @Nullable, 我认为 从左边比较常数 反模式因为它散布了关于哪些值可以或不可以为空的含糊不清。枚举类型几乎不应该是 @Nullable,因此空值将是编程错误;在这种情况下,越早抛出NPE,就越有可能在允许它为空的情况下找到编程错误。所以“最佳实践......你绝对应该遵循”在枚举类型方面是完全错误的。 - Tobias
比较左翼的常数最佳实践,如尤达风格的最佳英语。 - maaartinus


正如其他人所说的那样 == 和 .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 进入非枚举类。在这种重构的情况下, 有用 从上面指出是错误的。说服自己使用 == 是正确的,你需要检查有问题的值是否是一个 enum 或原始的;如果它是非enum类,它是错的,但很容易错过,因为代码仍然可以编译。使用时唯一的情况 .equals() 如果所讨论的价值是原始的,那就错了;在这种情况下,代码将无法编译,因此更难以错过。因此, .equals() 更容易识别是正确的,并且对未来的重构更安全。

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

总之,我仍然认为这些论点有利于使用 .equals() 对于 enum 类型。


56
2018-02-14 11:59



好吧,关于第3点,我可以使用 enum 作为一个 switch 目标,所以他们有点特别。 Strings也有点特别。 - CurtainDog
switch语句的语法既不包含==也不包含.equals(),所以我看不出你的意思。我的观点3.)是关于为读者验证代码的简单方法。只要switch语句编译,Switch语句的相等语义就是正确的。 - Tobias


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

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运行这个测试几次。 “==”的速度要快得多。


12
2017-10-14 21:19



我怀疑编译器分支预测可能会使此测试无效。智能编译器会认识到这两个if语句总是会计算为false,从而避免进行多次比较。一个非常聪明的编译器甚至可能会认识到y和x循环中的值对结果没有影响并优化整个事情...... - Darrel Hoffman
它可以,但绝对没有。我在答案中显示了编译器生成的实际字节码。 - ChrisCantrell
你们都很困惑。 :-)分支预测在运行时工作,因此不同的字节代码不是超级相关的。但是,在这种情况下,分支预测会有所帮助 都 同样的情况。 - vidstige
谁在乎?除非你在视频游戏中在循环内数千次比较枚举,否则额外的纳秒是没有意义的。 - user949300
除非你强制解释模式,否则字节码与性能无关。写一个 江铃控股 基准测试表明性能完全相同。 - maaartinus


如果枚举都是正确和正确的!!


10
2017-11-17 17:28





我更喜欢使用 == 代替 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而不是第一个没有意识到它:

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

10
2018-04-21 06:47





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

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


4
2017-10-26 20:40





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


4
2017-11-07 15:21