Use == (or !=) to Compare Java Enums

1 2 Page 2
Page 2 of 2
package dustin.examples

class EnumComparisonTest extends GroovyTestCase
{
   private EnumComparisonMain instance

   void setUp() {instance = new EnumComparisonMain()}

   /**
    * Demonstrate that while null is not watermelon, this check does not lead to
    * NPE because watermelon check uses {@code ==} rather than {@code .equals}.
    */
   void testIsWatermelonIsNotNull()
   {
      assertEquals("Null cannot be watermelon", false, instance.isFruitWatermelon(null))
   }

   /**
    * Demonstrate that a passed-in Object that is really a disguised Fruit can
    * be compared to the enum and found to be equal.
    */
   void testIsWatermelonDisguisedAsObjectStillWatermelon()
   {
      assertEquals("Object should have been watermelon",
                   true, instance.isObjectWatermelon(Fruit.WATERMELON))
   }

   /**
    * Demonstrate that a passed-in Object that is really a disguised Fruit can
    * be compared to the enum and found to be not equal.
    */
   void testIsOtherFruitDisguisedAsObjectNotWatermelon()
   {
      assertEquals("Fruit disguised as Object should NOT be watermelon",
                   false, instance.isObjectWatermelon(Fruit.ORANGE))
   }

   /**
    * Demonstrate that a passed-in Object that is really a null can be compared
    * without NPE to the enum and that they will be considered not equal.
    */
   void testIsNullObjectNotEqualToWatermelonWithoutNPE()
   {
      assertEquals("Null, even as Object, is not equal to Watermelon",
                   false, instance.isObjectWatermelon(null))
   }

   /** Demonstrate that test works when provided fruit is indeed watermelon. */
   void testIsWatermelonAWatermelon()
   {
      assertEquals("Watermelon expected for fruit", true, instance.isFruitWatermelon(Fruit.WATERMELON))
   }

   /** Demonstrate that fruit other than watermelon is not a watermelon. */
   void testIsWatermelonBanana()
   {
      assertEquals("A banana is not a watermelon", false, instance.isFruitWatermelon(Fruit.BANANA))
   }

   /**
    * Demonstrate that while null is not strawberry, this check does not lead to
    * NPE because strawberry check uses {@code ==} rather than {@code .equals}.
    */
   void testIsStrawberryIsNotNull()
   {
      assertEquals("Null cannot be strawberry", false, instance.isFruitStrawberry(null))
   }

   /**
    * Demonstrate that raspberry case throws NPE because of attempt to invoke
    * {@code .equals} method on null.
    */
   void testIsFruitRaspberryThrowsNpeForNull()
   {
      shouldFail(NullPointerException)
      {
         instance.isFruitRaspberry(null)
      }
   }

   /**
    * Demonstrate that raspberry case throws NPE even for Object because of
    * attempt to invoke {@code .equals} method on null.
    */
   void testIsObjectRaspberryThrowsNpeForNull()
   {
      shouldFail(NullPointerException)
      {
         instance.isObjectRaspberry(null)
      }
   }

   /**
    * Demonstrate that {@code .equals} approach works for comparing enums even if
    * {@code .equals} is invoked on passed-in object.
    */
   void testIsRaspberryDisguisedAsObjectRaspberry()
   {
      assertEquals("Expected object to be raspberry",
                   true, instance.isObjectRaspberry(Fruit.RASPBERRY))
   }

   /**
    * Demonstrate that while null is not grape, this check does not lead to NPE
    * because grape check invokes {@code .equals} method on the enum constant
    * being compared rather than on the provided {@code null}.
    */
   void testIsGrapeIsNotNull()
   {
      assertEquals("Null cannot be grape", false, instance.isFruitGrape(null))
   }
}

I don't describe what the various tests demonstrate much here because the comments on the tested class's methods and on the test methods cover most of that. In short, the variety of tests demonstrate that == is null-safe, and that equals is not null safe unless invoked on an enum constant or known non-null enum. There are a few other tests to ensure that the comparisons work as we'd expect them to work. The output from running the unit tests is shown next.

The GroovyTestCase method shouldFail reported that an NPE was indeed thrown when code attempted to call equals on an advertised enum that was really null. Because I used the overloaded version of this method that accepted the excepted exception as a parameter, I was able to ensure that it was an NPE that was thrown. The next screen snapshot shows what I would have seen if I had told the unit test that an IllegalArgumentException was expected rather than a NullPointerException.

I'm not the only one who thinks that == is preferable to equals when comparing enums. After I started writing this (because I saw what I deem to be a misuse of equals for comparing two enums again today for the umpteeth time), I discovered other posts making the same argument. The post == or equals with Java enum highlights the compile-time advantage of explicitly comparing two distinct types with ==: "Using == the compiler screams and tells us that we are comparing apples with oranges." Ashutosh's post Comparing Java Enums concludes that "using == over .equals() is more advantageous in two ways" (avoiding the NPE and getting the static compile-time check). The latter post also pointed me to the excellent reference Comparing Java enum members: == or equals()?.

The Dissenting View

All three references just mentioned had dissenting opinions either in the thread (the last reference) or as comments to the blog post (the first two references). The primary arguments for comparing enums with equals rather than == is consistency with comparing of other reference types (non-primitives). There is also an argument that equals is in a way more readable because a less experienced Java developer won't think it is wrong and he or she might think == is a bad idea. My argument to that is that it is good to know the difference and could provide a valuable teaching experience. Another argument against == is that equals gets compiled into the == form anyway. I don't see that so much as an argument against using ==. In fact, it really is a moot point in my mind if they are the same thing in byte code anyway. Furthermore, it doesn't take away from the advantages of == in terms of null safety and static type checking.

Don't Forget !=

All of the above arguments apply to the question of != versus !Enum.equals(Object), of course. The Java Tutorials section Getting and Setting Fields with Enum Types demonstrates comparing the same enum types and their example makes use of !=.

Conclusion

There is very little in software development that can be stated unequivocally and without controversy. The use of equals versus == for comparing enums is no exception to this. However, I have no problem comparing primitives in Java (as long as I ensure they are true primitives and not unboxed references) with == and likewise have no problem comparing enums with == either. The specification explicitly spells out that this is permissible as does the Java Tutorials (which uses != rather than !Enum.equals). I actually go further than that and state that it is preferable. If I can avoid NullPointerExceptions and still get the response I want (two compared things are not equal) and can move my error checking up to compile time all without extra effort, I'll take that deal just about every time.

Original posting available at http://marxsoftware.blogspot.com/ (Inspired by Actual Events)

Related:
1 2 Page 2
Page 2 of 2