我知道 Java 枚举被编译为具有私有构造函数和一堆公共静态成员的类。在比较给定枚举的两个成员时,我总是使用 .equals()
,例如
public useEnums(SomeEnum a)
{
if(a.equals(SomeEnum.SOME_ENUM_VALUE))
{
...
}
...
}
但是,我刚刚遇到了一些使用等号运算符 ==
而不是 .equals() 的代码:
public useEnums2(SomeEnum a)
{
if(a == SomeEnum.SOME_ENUM_VALUE)
{
...
}
...
}
我应该使用哪个运算符?
两者在技术上都是正确的。如果您查看 .equals()
的源代码,它只是遵循 ==
。
但是,我使用 ==
,因为它是 null 安全的。
== 可以用在枚举上吗?
是:枚举具有严格的实例控制,允许您使用 ==
来比较实例。这是语言规范提供的保证(我强调):
JLS 8.9 枚举枚举类型除了由其枚举常量定义的实例之外没有实例。尝试显式实例化枚举类型是编译时错误。 Enum 中的 final clone 方法确保了枚举常量永远不会被克隆,而序列化机制的特殊处理确保了不会因为反序列化而创建重复的实例。禁止枚举类型的反射实例化。这四件事一起确保枚举类型的实例不存在超出枚举常量定义的实例。因为每个枚举常量只有一个实例,所以在比较两个对象引用时,如果知道其中至少一个引用了枚举常量,则允许使用 == 运算符代替 equals 方法。 (Enum 中的 equals 方法是 final 方法,它仅在其参数上调用 super.equals 并返回结果,从而执行身份比较。)
这个保证足够强大,Josh Bloch 建议,如果你坚持使用单例模式,实现它的最佳方式是使用单元素 enum
(参见:Effective Java 2nd Edition,Item 3:使用私有构造函数或枚举类型强制执行单例属性;同样Thread safety in 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
上使用 ==
的论据是:
有用。
它更快。
它在运行时更安全。
在编译时更安全。
使用 ==
比较两个枚举值是可行的,因为每个枚举常量只有一个对象。
附带说明一下,如果您像这样编写 equals()
,实际上不需要使用 ==
来编写 null 安全代码:
public useEnums(final SomeEnum a) {
if (SomeEnum.SOME_ENUM_VALUE.equals(a)) {
…
}
…
}
这是您绝对应该遵循的称为 Compare Constants From The Left 的最佳做法。
null
枚举通常是一个错误。您已经获得了所有可能值的枚举,这是另一个!
正如其他人所说,==
和 .equals()
在大多数情况下都有效。您没有比较其他人指出的完全不同类型的对象的编译时间确定性是有效和有益的,但是 FindBugs 也可以发现比较两种不同编译时间类型的对象的特定错误(可能由Eclipse/IntelliJ 编译时检查),因此发现它的 Java 编译器不会增加太多额外的安全性。
然而:
== 在我的脑海中从来没有抛出 NPE 的事实是 == 的一个缺点。几乎不需要枚举类型为 null,因为您可能想通过 null 表达的任何额外状态都可以作为附加实例添加到枚举中。如果它意外地为空,我宁愿有一个 NPE 而不是 == 默默地评估为假。因此,我不同意运行时更安全的观点;最好养成永远不要让枚举值成为@Nullable 的习惯。 == 更快的论点也是虚假的。在大多数情况下,您将对编译时类型为枚举类的变量调用 .equals(),在这些情况下,编译器可以知道这与 == 相同(因为枚举的 equals() 方法不能覆盖)并且可以优化函数调用。我不确定编译器当前是否这样做,但如果没有,并且结果证明这是 Java 整体的性能问题,那么我宁愿修复编译器,也不愿让 100,000 名 Java 程序员改变他们的编程风格以适应特定编译器版本的性能特征。枚举是对象。对于所有其他对象类型,标准比较是 .equals(),而不是 ==。我认为对枚举进行例外处理是很危险的,因为您最终可能会意外地将 Objects 与 == 而不是 equals() 进行比较,尤其是当您将枚举重构为非枚举类时。在这种重构的情况下,上面的 It works 点是错误的。为了说服自己使用 == 是正确的,您需要检查所讨论的值是枚举还是原始值;如果它是一个非枚举类,那将是错误的,但很容易错过,因为代码仍然可以编译。使用 .equals() 错误的唯一情况是所讨论的值是原始值;在这种情况下,代码将无法编译,因此更难错过。因此, .equals() 更容易识别为正确的,并且对未来的重构更安全。
我实际上认为 Java 语言应该在 Objects 上定义 == 以在左侧值上调用 .equals() ,并为对象标识引入一个单独的运算符,但这不是 Java 的定义方式。
总之,我仍然认为这些论点支持将 .equals()
用于 enum
类型。
我更喜欢使用 ==
而不是 equals
:
除了这里已经讨论过的其他原因之外,其他原因是您可能会在没有意识到的情况下引入错误。假设你有这个完全相同的枚举,但在分开的包中(这并不常见,但它可能会发生):
第一个枚举:
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
due is a different enum 尽管你期望 true 因为 item.getCategory()
是 JAZZ
。而且可能有点难以看到。
因此,如果您改为使用运算符 ==
,您将遇到编译错误:
运算符 == 不能应用于“second.pckg.Category”、“first.pckg.Category”
import second.pckg.Category;
...
Category.JAZZ == item.getCategory()
tl;博士
另一个选项是 Objects.equals
实用程序方法。
Objects.equals( thisEnum , thatEnum )
Objects.equals 用于空安全
等于运算符 == 而不是 .equals()
我应该使用哪个运算符?
第三个选项是在 Objects
实用程序类 added to Java 7 及更高版本中找到的静态 equals
方法。
例子
下面是使用 Month
枚举的示例。
boolean areEqual = Objects.equals( Month.FEBRUARY , Month.JUNE ) ; // Returns `false`.
好处
我发现这种方法有几个好处:
Null-safety Both null ➙ true 要么 null ➙ false 没有抛出 NullPointerException 的风险
两者都为空➙真
要么为空➙假
没有抛出 NullPointerException 的风险
紧凑,可读
这个怎么运作
Objects.equals
使用的逻辑是什么?
从 OpenJDK 的 Java 10 source code 中亲自查看:
return
( a == b )
||
(
a != null
&&
a.equals( b )
)
;
Objects.equals
与 ==
。 ==
在上述场景中的行为与 Objects.equals
完全相同。
这是一个粗略的时间测试来比较两者:
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 运行了几次这个测试。 “==”比以往任何时候都快。
声纳规则之一是 Enum values should be compared with "=="
。原因如下:
使用 equals() 测试枚举值的相等性是完全有效的,因为枚举是一个对象,每个 Java 开发人员都知道 == 不应该用于比较对象的内容。同时,在枚举上使用 ==:提供与 equals() 相同的预期比较(内容)比 equals() 提供编译时(静态)检查而不是运行时检查更安全由于这些原因,使用== 应该优先于 equals()。
最后但并非最不重要的一点是,枚举上的 ==
可以说比 equals()
更具可读性(不那么冗长)。
在枚举的情况下,两者都是正确和正确的!
使用 ==
以外的任何东西来比较枚举常量都是无稽之谈。就像 comparing class
objects with equals
- 不要这样做!
但是,由于历史原因,Sun JDK 6u10 及更早版本中存在一个令人讨厌的错误 (BugId 6277781)。这个错误阻止了在反序列化枚举上正确使用 ==
,尽管这可以说是一种极端情况。
枚举是为 public static final field
声明的每个枚举常量(不可变)返回一个实例(如单例)的类,因此 ==
运算符可用于检查它们的相等性,而不是使用 equals()
方法
枚举与 == 一起工作的原因是因为每个定义的实例也是一个单例。因此,使用 == 进行身份比较将始终有效。
但是使用 == 因为它与枚举一起使用意味着您的所有代码都与该枚举的使用紧密耦合。
例如:枚举可以实现一个接口。假设您当前正在使用实现 Interface1 的枚举。如果稍后,有人更改它或引入一个新类 Impl1 作为相同接口的实现。然后,如果您开始使用 Impl1 的实例,您将有大量代码需要更改和测试,因为之前使用了 ==。
因此,除非有任何合理的收益,否则最好遵循被认为是好的做法。
只需将一件事添加到所有其他出色的答案中。当您使用简单的 lambda 时,我更喜欢 equals
而不是 ==
,因为您可以使用方法引用。
考虑以下 lambda:
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));
我想补充 polygenelubricants 答案:
我个人更喜欢equals()。但它湖类型兼容性检查。我认为这是一个重要的限制。
要在编译时进行类型兼容性检查,请在枚举中声明并使用自定义函数。
public boolean isEquals(enumVariable) // compare constant from left
public static boolean areEqual(enumVariable, enumVariable2) // compare two variable
有了这个,您就获得了这两种解决方案的所有优势:NPE 保护、易于阅读的代码和编译时的类型兼容性检查。
我还建议为枚举添加一个 UNDEFINED 值。
==
更好?使用 ==
,您可以获得 NPE 保护、易于阅读的代码和编译时类型兼容性检查,并且您无需编写任何自定义的类似 equals 的方法即可获得所有这些。
简而言之,两者各有利弊。
一方面,如其他答案中所述,使用 ==
具有优势。
另一方面,如果您出于任何原因用不同的方法(普通类实例)替换枚举,使用 ==
会咬你。 (BTDT。)
==
而不是equals
的另一个参数是类型的编译时检查。myEnum.MY_CONSTANT.equals("Some String")
会编译而myEnum.MY_CONSTANT == "Some String"
不会,因为"Some String"
不是同一类型,编译器可以预先确定它