Most new Java developers quickly learn that they should generally compare Java Strings using String.equals(Object) rather than using ==
. This is emphasized and reinforced to new developers repeatedly because they almost always mean to compare String content (the actual characters forming the String) rather than the String's identity (its address in memory). I contend that we should reinforce the notion that ==
can be used instead of Enum.equals(Object). I provide my reasoning for this assertion in the remainder of this post.
There are four reasons that I believe using ==
to compare Java enums is almost always preferable to using the "equals" method:
- The
==
on enums provides the same expected comparison (content) asequals
- The
==
on enums is arguably more readable (less verbose) thanequals
- The
==
on enums is more null-safe thanequals
- The
==
on enums provides compile-time (static) checking rather than runtime checking
The second reason listed above ("arguably more readable") is obviously a matter of opinion, but that part about "less verbose" can be agreed upon. The first reason I generally prefer ==
when comparing enums is a consequence of how the Java Language Specification describes enums. Section 8.9 ("Enums") states:
It is a compile-time error to attempt to explicitly instantiate an enum type. The final clone method in Enum ensures that enum constants can never be cloned, and the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization. Reflective instantiation of enum types is prohibited. Together, these four things ensure that no instances of an enum type exist beyond those defined by the enum constants.
Because there is only one instance of each enum constant, it is permissible to use the == operator in place of the equals method when comparing two object references if it is known that at least one of them refers to an enum constant. (The equals method in Enum is a final method that merely invokes super.equals on its argument and returns the result, thus performing an identity comparison.)
The excerpt from the specification shown above implies and then explicitly states that it is safe to use the ==
operator to compare two enums because there is no way that there can be more than one instance of the same enum constant.
The fourth advantage to ==
over .equals
when comparing enums has to do with compile-time safety. The use of ==
forces a stricter compile time check than that for .equals
because Object.equals(Object) must, by contract, take an arbitrary Object
. When using a statically typed language such as Java, I believe in taking advantage of the advantages of this static typing as much as possible. Otherwise, I'd use a dynamically typed language. I believe that one of the recurring themes of Effective Java is just that: prefer static type checking whenever possible.
For example, suppose I had a custom enum called Fruit
and I tried to compare it to the class java.awt.Color. Using the ==
operator allows me to get a compile-time error (including advance notice in my favorite Java IDE) of the problem. Here is a code listing that tries to compare a custom enum to a JDK class using the ==
operator:
/**
* Indicate if provided Color is a watermelon.
*
* This method's implementation is commented out to avoid a compiler error
* that legitimately disallows == to compare two objects that are not and
* cannot be the same thing ever.
*
* @param candidateColor Color that will never be a watermelon.
* @return Should never be true.
*/
public boolean isColorWatermelon(java.awt.Color candidateColor)
{
// This comparison of Fruit to Color will lead to compiler error:
// error: incomparable types: Fruit and Color
return Fruit.WATERMELON == candidateColor;
}
The compiler error is shown in the screen snapshot that comes next.
Although I'm no fan of errors, I prefer them to be caught statically at compile time rather than depending on runtime coverage. Had I used the equals
method for this comparison, the code would have compiled fine, but the method would always return false
false because there is no way a dustin.examples.Fruit
enum will be equal to a java.awt.Color
class. I don't recommend it, but here is the comparison method using .equals
:
/**
* Indicate whether provided Color is a Raspberry. This is utter nonsense
* because a Color can never be equal to a Fruit, but the compiler allows this
* check and only a runtime determination can indicate that they are not
* equal even though they can never be equal. This is how NOT to do things.
*
* @param candidateColor Color that will never be a raspberry.
* @return {@code false}. Always.
*/
public boolean isColorRaspberry(java.awt.Color candidateColor)
{
//
// DON'T DO THIS: Waste of effort and misleading code!!!!!!!!
//
return Fruit.RASPBERRY.equals(candidateColor);
}
The "nice" thing about the above is the lack of compile-time errors. It compiles beautifully. Unfortunately, this is paid for with a potentially high price.
The final advantage I listed of using ==
rather than Enum.equals
when comparing enums is the avoidance of the dreaded NullPointerException. As I stated in Effective Java NullPointerException Handling, I generally like to avoid unexpected NullPointerException
s. There is a limited set of situations in which I truly want the existence of a null to be treated as an exceptional case, but often I prefer a more graceful reporting of a problem. An advantage of comparing enums with ==
is that a null can be compared to a non-null enum without encountering a NullPointerException
(NPE). The result of this comparison, obviously, is false
.
One way to avoid the NPE when using .equals(Object)
is to invoke the equals
method against an enum constant or a known non-null enum and then pass the potential enum of questionable character (possibly null) as a parameter to the equals
method. This has often been done for years in Java with Strings to avoid the NPE. However, with the ==
operator, order of comparison does not matter. I like that.
I've made my arguments and now I move onto some code examples. The next listing is a realization of the hypothetical Fruit enum mentioned earlier.
Fruit.java
package dustin.examples;
public enum Fruit
{
APPLE,
BANANA,
BLACKBERRY,
BLUEBERRY,
CHERRY,
GRAPE,
KIWI,
MANGO,
ORANGE,
RASPBERRY,
STRAWBERRY,
TOMATO,
WATERMELON
}
The next code listing is a simple Java class that provides methods for detecting if a particular enum or object is a certain fruit. I'd normally put checks like these in the enum itself, but they work better in a separate class here for my illustrative and demonstrative purposes. This class includes the two methods shown earlier for comparing Fruit
to Color
with both ==
and equals
. Of course, the method using ==
to compare an enum to a class had to have that part commented out to compile properly.
EnumComparisonMain.java
package dustin.examples;
public class EnumComparisonMain
{
/**
* Indicate whether provided fruit is a watermelon ({@code true} or not
* ({@code false}).
*
* @param candidateFruit Fruit that may or may not be a watermelon; null is
* perfectly acceptable (bring it on!).
* @return {@code true} if provided fruit is watermelon; {@code false} if
* provided fruit is NOT a watermelon.
*/
public boolean isFruitWatermelon(Fruit candidateFruit)
{
return candidateFruit == Fruit.WATERMELON;
}
/**
* Indicate whether provided object is a Fruit.WATERMELON ({@code true}) or
* not ({@code false}).
*
* @param candidateObject Object that may or may not be a watermelon and may
* not even be a Fruit!
* @return {@code true} if provided object is a Fruit.WATERMELON;
* {@code false} if provided object is not Fruit.WATERMELON.
*/
public boolean isObjectWatermelon(Object candidateObject)
{
return candidateObject == Fruit.WATERMELON;
}
/**
* Indicate if provided Color is a watermelon.
*
* This method's implementation is commented out to avoid a compiler error
* that legitimately disallows == to compare two objects that are not and
* cannot be the same thing ever.
*
* @param candidateColor Color that will never be a watermelon.
* @return Should never be true.
*/
public boolean isColorWatermelon(java.awt.Color candidateColor)
{
// Had to comment out comparison of Fruit to Color to avoid compiler error:
// error: incomparable types: Fruit and Color
return /*Fruit.WATERMELON == candidateColor*/ false;
}
/**
* Indicate whether provided fruit is a strawberry ({@code true}) or not
* ({@code false}).
*
* @param candidateFruit Fruit that may or may not be a strawberry; null is
* perfectly acceptable (bring it on!).
* @return {@code true} if provided fruit is strawberry; {@code false} if
* provided fruit is NOT strawberry.
*/
public boolean isFruitStrawberry(Fruit candidateFruit)
{
return Fruit.STRAWBERRY == candidateFruit;
}
/**
* Indicate whether provided fruit is a raspberry ({@code true}) or not
* ({@code false}).
*
* @param candidateFruit Fruit that may or may not be a raspberry; null is
* completely and entirely unacceptable; please don't pass null, please,
* please, please.
* @return {@code true} if provided fruit is raspberry; {@code false} if
* provided fruit is NOT raspberry.
*/
public boolean isFruitRaspberry(Fruit candidateFruit)
{
return candidateFruit.equals(Fruit.RASPBERRY);
}
/**
* Indicate whether provided Object is a Fruit.RASPBERRY ({@code true}) or
* not ({@code false}).
*
* @param candidateObject Object that may or may not be a Raspberry and may
* or may not even be a Fruit!
* @return {@code true} if provided Object is a Fruit.RASPBERRY; {@code false}
* if it is not a Fruit or not a raspberry.
*/
public boolean isObjectRaspberry(Object candidateObject)
{
return candidateObject.equals(Fruit.RASPBERRY);
}
/**
* Indicate whether provided Color is a Raspberry. This is utter nonsense
* because a Color can never be equal to a Fruit, but the compiler allows this
* check and only a runtime determination can indicate that they are not
* equal even though they can never be equal. This is how NOT to do things.
*
* @param candidateColor Color that will never be a raspberry.
* @return {@code false}. Always.
*/
public boolean isColorRaspberry(java.awt.Color candidateColor)
{
//
// DON'T DO THIS: Waste of effort and misleading code!!!!!!!!
//
return Fruit.RASPBERRY.equals(candidateColor);
}
/**
* Indicate whether provided fruit is a grape ({@code true}) or not
* ({@code false}).
*
* @param candidateFruit Fruit that may or may not be a grape; null is
* perfectly acceptable (bring it on!).
* @return {@code true} if provided fruit is a grape; {@code false} if
* provided fruit is NOT a grape.
*/
public boolean isFruitGrape(Fruit candidateFruit)
{
return Fruit.GRAPE.equals(candidateFruit);
}
}
I decided to approach demonstration of the ideas captured in the above methods via unit tests. In particular, I make use of Groovy's GroovyTestCase. That class for using Groovy-powered unit testing is in the next code listing.
EnumComparisonTest.groovy