提问



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


我应该使用哪个运营商?

最佳参考


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


然而,我使用==,因为它将是null安全的。

其它参考1


可以在enum上使用==吗?



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



  

JLS 8.9 Enums


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

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

  
  因为每个enum常量只有一个实例,允许在比较两个对象引用时使用==运算符代替equals方法,如果已知它其中至少有一个是指enum常数。 (Enum中的equals方法是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


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



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


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


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


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


这是一个最佳实践,称为左边的比较常数,你绝对应该遵循。[120]

其它参考3


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


然而:



  1. ==永远不会在我的脑海中抛出NPE的事实是==劣势null几乎不需要enum类型,因为您可能希望通过null表达的任何额外状态可以添加到enum中另一个例子。如果出乎意料地null,我宁愿使用NPE而不是==默默评估为假。因此,我不同意它在运行时更安全意见;最好养成不让enum值为@Nullable的习惯。

  2. == 更快的论点也是假的。在大多数情况下,你会在编译时类型为枚举类的变量上调用.equals(),在这种情况下,编译器可以知道这与==相同(因为enum]]s equals()方法不能被覆盖)并且可以优化函数调用。我不确定编译器当前是否这样做,但如果它没有,并且结果证明是整体上的Java性能问题,那么我宁愿修复编译器而不是让100,000个Java程序员改变他们的编程风格以适应特定的编译器版本的性能特征。

  3. enums是对象。对于所有其他对象类型,标准比较是.equals(),而不是==。我认为enums的异常是危险的,因为你最终会意外地将对象与==而不是equals()进行比较,特别是如果你将enum重构成一个非枚举类。如果进行这样的重构,它的工作从上面指出是错误的。为了说服自己使用==是正确的,你需要检查是否有值问题是enum或原语;如果它是非enum类,它错误但容易错过,因为代码仍然可以编译。使用.equals()错误的唯一情况是所讨论的值是原始值;在这种情况下,代码不会编译,所以它更难以错过。因此,.equals()更容易识别为正确,并且对未来的重构更安全。



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


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

其它参考4


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


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


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


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

其它参考5


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

其它参考6


我更喜欢使用==而不是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() 

其它参考7


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


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

其它参考8


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

其它参考9


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


一方面,它有==的优点,如其他答案中所述。


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

其它参考10


我想补充polygenelubricants答案:


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


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


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


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


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

其它参考11


您可以使用: ==,equals()或switch()阻止 来比较枚举,所有技术都是正确的,可满足您的需求。


查看本教程以了解有关Enum的常见操作的更多信息:如何在java中使用枚举[123]

其它参考12


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


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


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


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

其它参考13


中间的枚举是一组常数整数。 ==就像你比较两个整数一样有效和正确。

其它参考14


我想明确强调==运算符和equals()方法之间的这种特定区别:


equals()方法用于检查所涉及的引用变量的对象的内容是否相同。


==运算符检查所涉及的引用变量是否将引用到同一对象。


由应用程序根据需要提供这种区分,取决于实现类。


否则,默认行为将由Object类(在Java中)提供,其中如http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Object中所述。 HTML#等于(java.lang.Object中):[124]



  类Objectequals方法实现了对象上最具辨别力的等价关系;也就是说,对于任何非空引用值xy,当且仅当xy引用同一对象时,此方法返回true (x == y具有值true)。