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

Mat*_*all 1645 java enums

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

public useEnums(SomeEnum a)
{
    if(a.equals(SomeEnum.SOME_ENUM_VALUE))
    {
        ...
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

public useEnums2(SomeEnum a)
{
    if(a == SomeEnum.SOME_ENUM_VALUE)
    {
        ...
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

我应该使用哪个运营商?

Rev*_*nzo 1485

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

==但是,我使用它,因为它将是null安全的.

  • @Nivas:为什么不呢?你喜欢担心你的参数的顺序(它只适用于比较左边的常量,如Pascal的答案)?你是否喜欢在`.equals()之前检查一个值是否为空?我知道我没有. (245认同)
  • 请记住,在阅读您的代码时,"=="可能对读者来说不正确,直到他/她离开并查看所涉及类型的来源,然后看到它是枚举.从这个意义上说,阅读".equals()"就不那么容易分散注意力了. (85认同)
  • 有时代码会被重构,并且枚举可能被类替换,此时==不再保证是正确的,而equals继续无缝地工作.因此,equals显然是更好的选择.如果你想要null安全(因为你处理的代码库没有编写,以尽量减少使用空值),那么Apache ObjectUtils或Guava的Objects#等于(或者你可以自己动手).我甚至都不会提到"表演"这个词,因为担心有人会用唐纳德·克努特的书正确地打我. (62认同)
  • 就个人而言,我不喜欢将'null safety'作为使用==的理由. (61认同)
  • @Kevin - 如果您没有使用允许您在查看源代码时直接查看类型的IDE,那么您就是在欺骗自己 - 这不是适当IDE的问题. (56认同)
  • 不,实际上这不是一个合理的假设,因为人们将ALL_CAPS名称用于许多种类的常量. (28认同)
  • @KevinBourrillion - 按照惯例,`enum`名字写在所有大写字母中......因此,如果有人遇到一个全都是大写字母的名字,那么他们认为它是一个`final`原语(IE,一个`int)是不合理的. `或`long`)或`enum`,其中任何一个都正确使用`==`on? (16认同)
  • @Carl Smotricz,等于最有可能被JIT内联,因此性能不是问题 (12认同)
  • @laszlok它看起来是一个合理的参数但是一旦你研究了'Enum#equals()`的实现,你会发现这样的参数无效.在`Enum#equals()`里面它只是做`==`,这意味着,如果类被类加载器替换,`==`和`equals`都会失败(除非你为你的枚举编写自己的等号)这很少是明智的选择).所以,就你提到的方面而言,"等于"并不比"=="更好. (8认同)
  • 用类替换枚举并不常见.==符合基元之间的比较,这对我来说很好. (8认同)
  • @laszlok只需编译时类型检查就足以选择`==`而不是`equals()`.当然,个人倾向于加权"编译时类型检查"与"轻松用类替换枚举"但我完全不同意你继续提到"使用== ......没有充分理由" (7认同)
  • 使用“==”而不是“equals”的另一个论点是编译时类型检查。`myEnum.MY_CONSTANT.equals("Some String")` 将编译,而 `myEnum.MY_CONSTANT == "Some String"` 不会编译,因为 `"Some String"` 不是同一类型,编译器可以预先确定它 (7认同)
  • 为此,您始终可以反转条件:`MyEnum.CONST.equals(nullableMyEnum)` (7认同)
  • 枚举是最终的,所以==工作正常.就个人而言,我只是将它作为常量并使用==进行比较. (6认同)
  • 我的第二个答案是,对运行时性能的微小改进也是如此. (5认同)
  • 如果您担心null安全性,并且这也适用于字符串常量,请将要比较的值放在左侧:"blah".equals(variablePossiblyNull)和SomeEnum.CONSTANT.equals(enumReferencePossiblyNull)都将work,但只有.equals适用于枚举常量和字符串.如果你真的*非常偏执,请查看apache commons lang中的ObjectUtils.equals(ob1,ob2),它将处理所有情况! (5认同)
  • 请参阅有关类型安全性的其他答案:==在编译时需要进行类型兼容性检查 (5认同)
  • 当您使用IDE在编辑代码时重新加载类时,您可能有两个单独的编译枚举,即使它们应该是一个.然后`MyEnum.CONSTANT_ONE!= MyEnum.CONSTANT_ONE`为真. (3认同)
  • @laszlok如果你在谈论"将代码的使用替换为代码中的类",那么我认为将原始类型的使用替换为类之间没有任何区别.您不希望它自动运行吗? (3认同)
  • SomeEnum.SOME_ENUM_VALUE.equals(a)也是"null safe" (3认同)
  • @AdrianShum我同意.轶事:一旦我们有一个字符串常量`X`和一个调用`X.equals(x)`的字符串`x`的方法.然后`X`被重构为枚举.这种方法被打破,但很长一段时间没有人注意到. (3认同)
  • @AdrianShum这正是我所说的,你的例子就是现实,它与用类替换原语有同样的麻烦.在枚举上使用==可能会被重构成一个类,就是没有充分理由要求这个麻烦. (2认同)
  • 从Java 7中,我发现Objects.equals是最佳选择 (2认同)
  • 事实是,枚举是最终静态单例,因此应遵循将ALL_CAPS用于最终静态对象的命名约定。如果将枚举重构为类,并且对象不再是最终静态对象,则您正在更改枚举/类的协定。 (2认同)
  • 我还要补充一点,“==”对于 NPE 来说是安全的。例如,如果我们在可为 null 的类型上调用 equals,我们可能会引发 NPE `nullableMyEnum.equals(MyEnum.CONST)`。 (2认同)

pol*_*nts 1067

可以==enum吗?

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

JLS 8.9枚举

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

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

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

这种保证足够强大,Josh Bloch建议,如果你坚持使用单例模式,实现它的最好方法是使用单个元素enum(参见:Effective Java 2nd Edition,Item 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
Run Code Online (Sandbox Code Playgroud)

== 在编译时受类型兼容性检查的影响

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!
Run Code Online (Sandbox Code Playgroud)

==适用时应该使用?

Bloch特别提到,对其实例进行适当控制的不可变类可以保证其客户端==可用.enum具体提到举例说明.

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

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

总之,使用参数==enum有:

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

  • 这是对Java的黑色标记,有些人认为`foo.equals(bar)`比`foo == bar`更具可读性 (186认同)
  • "如果你的程序员是多么愚蠢,"安全"是你不能做的事情." - 我不同意这句话.这就是类型检查的全部内容:在编译时防止"哑"错误,甚至在代码运行之前.即使是聪明的人也会犯愚蠢的错误,比承诺更能提供一些保障. (32认同)
  • 很好的答案,但...... 1.*它的工作原理*:同意2.*它更快*:证明它为枚举:) 3.*它在运行时更安全*:`颜色没有= null;`是恕我直言,一个bug和应该修复,你的枚举有两个值,而不是三个(参见Tom Hawtin的评论)4.*它在编译时更安全*:好的,但是`equals`仍然会返回`false`.最后,*I*不买它,我更喜欢`equals()`的可读性(参见Kevin的评论). (27认同)
  • 它可以工作:直到你需要用类替换枚举作为重构的一部分.祝你好运找到使用==打破的所有代码.它更快:即使是真的(可疑的),Knuth引用了关于过早优化的内容.在运行时更安全:如果使用==是唯一将你与NullPointerException分开的东西,那么"安全"并不是第一个想到的词.在编译时更安全:不是真的.它可以保护你免受比较错误类型的相当基本的错误,但同样,如果你的程序员是多么愚蠢,"安全"是你不是的一件事. (19认同)
  • @laszlok:当然,如果事情可能失败,他们就会这样做.为一些愚蠢的错误设置围栏总是一件好事.对于不那么微不足道的错误,有足够的空间来创造,让编译器在您尝试运行代码之前处理这个错误. (7认同)
  • @Pascal:我同意`null`论证很弱,因为`null`一开始就有争议(CAR Hoare"十亿美元错误"等).另请参阅我的问题:http://stackoverflow.com/questions/2887761/is-it-a-bad-idea-if-equalsnull-throws-nullpointerexception-instead //我认为编译时类型兼容性检查很重要并且通过返回"false"比在运行时"默默地失败"要好得多.我实际上并不关心速度优势,因为它不太重要; 我只是在重复布洛赫所说的话. (5认同)
  • 我正在使用枚举,因为我想要强类型和编译时检查.从这个角度来看,`.equals()`远不如`==`. (5认同)
  • 我承认我自己的论据"反对"编译时检查也很弱(并且编译时检查实际上可能是`==`的最佳参数,即使"静默失败"对我来说似乎太强了).关于Bloch关于速度的观点,我当然不是说他错了,我只是不相信它适用于枚举(并且他写了*may*).这就是为什么我认为挑战你的专业人士是合法的:)但你有我的+1,它仍然是一个很好的答案. (3认同)
  • 如果重新排序`.equals`使得常量与潜在的空值进行比较,它将永远不会抛出`NullPointerException`.所以我不确定我是否接受了你的观点,即`==`永远不会抛出一个NPE,因为有办法编写`.equals`,不允许它抛出一个. (3认同)
  • @laszlok很高兴提醒我:P然后我没有理由使用`.equals()`来表示枚举.我仍然认为"equals()的参数使得将枚举更改为普通类更容易"是脆弱的.如果你采用这样的论证,你甚至应该对所有原始类型使用`((Object)primitiveVar).equals(anotherVar)`而不是`primitiveVar == anotherVar`,因为你永远不知道什么时候你想要替换你的使用原始类型到类. (3认同)

Pas*_*ent 84

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

另外,==如果您这样编写,则实际上不需要使用写入null安全代码equals():

public useEnums(final SomeEnum a) {
    if (SomeEnum.SOME_ENUM_VALUE.equals(a)) {
        …
    }
    …
}
Run Code Online (Sandbox Code Playgroud)

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

  • "null"枚举通常是一个错误.你已经列举了所有可能的值,这是另一个值! (32认同)
  • 比较左翼的常数最佳实践,如尤达风格的最佳英语. (19认同)
  • 我在`.equals()`字符串值时这样做,但我仍然认为这是一种编写空安全代码的糟糕方法.我宁愿有一个运算符(或一个方法,但我知道[null对象] .equals在Java中不会因为点运算符的工作原理而在null中是安全的),它始终是空安全的,无论是什么命令它用于.语义上,"等于"运算符应*总是*通勤. (8认同)
  • 除了明确注释为`@Nullable`的值之外,我认为_Compare Constants From The Left_是一个反模式,因为它传播了关于哪些值可以或不可以为空的模糊性.枚举类型几乎不应该是`@Nullable`,因此空值将是一个编程错误; 在这种情况下,越早抛出NPE,就越有可能在允许它为空的情况下找到编程错误.所以"最佳实践......你绝对应该遵循"在枚举类型方面是完全错误的. (7认同)
  • 好吧,由于Java没有Groovy的安全导航操作符(`?.`),我将继续使用这种做法与常量进行比较,TBH,我发现它并不蹩脚,也许只是不那么"自然" ". (2认同)

Tob*_*ias 58

正如其他人所说,无论是==.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类型.

  • 好吧,关于第3点,我可以使用`enum`作为`switch`目标,所以它们有点特别.`String`s也有点特别. (4认同)

Pau*_*Pau 15

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

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

第一个枚举:

package first.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}
Run Code Online (Sandbox Code Playgroud)

第二个枚举:

package second.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}
Run Code Online (Sandbox Code Playgroud)

然后,假设你使用等号就像在接下来的item.category这是first.pckg.Category,但你导入第二枚举(second.pckg.Category),而不是第一个没有意识到这一点:

import second.pckg.Category;
...

Category.JAZZ.equals(item.getCategory())
Run Code Online (Sandbox Code Playgroud)

所以你会得到false一个不同的枚举,虽然你期望是真的,因为item.getCategory()JAZZ.它可能有点难以看到.

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

operator ==不能应用于"second.pckg.Category","first.pckg.Category"

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 
Run Code Online (Sandbox Code Playgroud)


Chr*_*ell 13

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

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

}
Run Code Online (Sandbox Code Playgroud)

一次一个地评论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
Run Code Online (Sandbox Code Playgroud)

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

我一次用两个IF运行这个测试几次."=="的速度要快得多.

  • 谁在乎?除非你在视频游戏中在循环内数千次比较枚举,否则额外的纳秒是没有意义的. (6认同)

Sur*_*ran 10

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


小智 9

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

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

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

  • 比空值更安全 equals()

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

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

最后但并非最不重要的==一点是,on 枚举可以说比equals().


Bas*_*que 6

tl; dr

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

Objects.equals( thisEnum , thatEnum )
Run Code Online (Sandbox Code Playgroud)

Objects.equals 为零安全

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

我应该使用哪个运算符?

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

这是使用Month枚举的示例。

boolean areEqual = Objects.equals( Month.FEBRUARY , Month.JUNE ) ;  // Returns `false`.
Run Code Online (Sandbox Code Playgroud)

好处

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

  • 零安全
  • 紧凑,可读

怎么运行的

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

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

return (a == b) || (a != null && a.equals(b));
Run Code Online (Sandbox Code Playgroud)

  • 对于“Objects.equals”与“==”,您所说的好处都不成立。在上述场景中,“==”的行为与“Objects.equals”完全相同。 (3认同)

Tom*_*mas 5

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

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


ΦXo*_*a ツ 5

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