Thursday, June 23, 2011

Java 7 Objects-Powered Compact Equals

Obi Ezechukwu's blog High-Octane Java recently featured the post Compact Equals, which compares an implementation of a Java class's overridden equals method in traditional form versus one implemented using arrays of the class's fields and then invoking Arrays.equals on those arrays. I am using this blog post to build upon these examples to demonstrate the utility of Java 7's Objects.equals(Object, Object) method and will demonstrate the usefulness of Objects.hash(Object...) along the way.

In the post Compact Equals, Ezechukwu shows one implementation of a traditional equals method for the four String fields and single Integer field defined in the Compact Equals post. I have adapted the "traditional" approach very slightly here (mostly formatting):

Address.equals(Object): 'Traditional' equals Implementation
@Override
   public boolean equals(Object obj)
   {
      if (this == obj)
      {
         return true;
      }
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }

      final Address other = (Address) obj;

      if (city == null)
      {
         if (other.city != null)
         {
            return false;
         }
      }
      else if (!city.equals(other.city))
      {
         return false;
      }

      if (country == null)
      {
         if (other.country != null)
         {
            return false;
         }
      }
      else if (!country.equals(other.country))
      {
         return false;
      }

      if (houseNumber == null)
      {
         if (other.houseNumber != null)
         {
            return false;
         }
      }
      else if (!houseNumber.equals(other.houseNumber))
      {
         return false;
      }

      if (stateOrProvince == null)
      {
         if (other.stateOrProvince != null)
         {
            return false;
         }
      }
      else if (!stateOrProvince.equals(other.stateOrProvince))
      {
         return false;
      }

      if (street == null)
      {
         if (other.street != null)
         {
            return false;
         }
      }
      else if (!street.equals(other.street))
      {
         return false;
      }

      return true;
   }

This is very verbose and is a good example of the main reason I began switching from loathing the ternary operator to loving it. I was tired of these extremely verbose equals methods implementations and liked the implementations using the ternary operator much better. As I began writing more and more equals methods with ternaries, I became more comfortable with them. The next example is adapted from the Address class to use the ternary operators instead. In this case, I let NetBeans 7.0 do the work of generating the equals method because I knew that its automatic generation also employs the ternary operator for comparing field Strings. This implementation is shown in the next code listing.

AddressNb.equals(Object): Ternary Makes Equals More Concise
@Override
   public boolean equals(Object obj)
   {
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      final AddressNb other = (AddressNb) obj;
      if (this.houseNumber != other.houseNumber && (this.houseNumber == null || !this.houseNumber.equals(other.houseNumber)))
      {
         return false;
      }
      if ((this.street == null) ? (other.street != null) : !this.street.equals(other.street))
      {
         return false;
      }
      if ((this.city == null) ? (other.city != null) : !this.city.equals(other.city))
      {
         return false;
      }
      if ((this.stateOrProvince == null) ? (other.stateOrProvince != null) : !this.stateOrProvince.equals(other.stateOrProvince))
      {
         return false;
      }
      if ((this.country == null) ? (other.country != null) : !this.country.equals(other.country))
      {
         return false;
      }
      return true;
   }

The ternary operator makes the code less verbose, but Ezechukwu highlights another interesting approach in Compact Equals. His example populates arrays with the fields of each object being compared and then evaluates the equality of the two arrays using Arrays.equals(Object[], Object[]). An adapted version of this is shown in the next code listing.

AddressV2.equals(Object): Using Arrays.equals to Compare Fields
@Override
   public boolean equals(Object obj) 
   {
      boolean result;

      if (this == obj)
      {
         result = true;
      }
      else if (obj!=null && getClass() == obj.getClass())
      {
         final AddressV2 other = (AddressV2) obj;

         final Object[] fields = 
            {houseNumber, street, city, stateOrProvince, country};

         final Object[] otherFields = 
            {other.houseNumber, other.street, other.city, 
             other.stateOrProvince, other.country};

         result = Arrays.equals(fields, otherFields);
      }
      else
      {
         result = false;
      }

      return result;
   }

Things get much better for Java developers implementing equals methods in Java 7. As I blogged about in JDK 7: The New Objects Class, JDK 7 offers a class called Objects that provides an Objects.equals(Object,Object) method that promises to make equals methods succinct (it also has a deepEquals(Object,Object) method). This highly attractive conciseness is shown in the next code listing.

AddressO7.equals(Object): JDK 7 Objects.equals(Object,Object) to the Rescue!
@Override
   public boolean equals(Object obj) 
   {
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      final AddressO7 other = (AddressO7) obj;
      return    Objects.equals(this.houseNumber, other.houseNumber)
             && Objects.equals(this.street, other.street)
             && Objects.equals(this.city, other.city)
             && Objects.equals(this.stateOrProvince, other.stateOrProvince)
             && Objects.equals(this.country, other.country);
   }

Now that's compact!

It gets even better with the JDK 7-introduced Objects class. It is common wisdom that a consistent hashCode() method should be implemented whenever an equals(Object) method is overridden. In fact, NetBeans warns of this and its automatically generated hashCode() method looks like that shown next.

hashCode() Corresponding to Any of Above equals(Object) Implementations
@Override
   public int hashCode()
   {
      int hash = 3;
      hash = 53 * hash + (this.houseNumber != null ? this.houseNumber.hashCode() : 0);
      hash = 53 * hash + (this.street != null ? this.street.hashCode() : 0);
      hash = 53 * hash + (this.city != null ? this.city.hashCode() : 0);
      hash = 53 * hash + (this.stateOrProvince != null ? this.stateOrProvince.hashCode() : 0);
      hash = 53 * hash + (this.country != null ? this.country.hashCode() : 0);
      return hash;
   }

The Objects class enables a much more elegant solution as shown in the next code listing.

Objects-powered hashCode() Implementation
@Override
   public int hashCode()
   {
      return Objects.hash(
         this.houseNumber, this.street, this.city, this.stateOrProvince, this.country);
   }

That's compact too!

I liked the Compact Equals post because it provided an interesting perspective on using Arrays.equals to compare two instances' fields and I also liked it because it provided me with a good starting point to work from to demonstrate the value of the JDK 7 Objects class in writing compact equals and hashCode implementations. The JDK 7 Objects class is going to change the way we write and maintain some of our most common methods in Java. It's a very welcome addition.

3 comments:

Martijn Verburg said...

It'll be interesting to see what Spring Roo and Grails make of all of this. I'm certainly not against Roo/Grails giving a decent first attempt at an equals and hashcode method...

Kimble said...

I like that Java is getting less verbose. But I'll probably continue to use the equals and hashcode builders provided by Apache commons.

Dustin said...

Martijn and Kimble:

Thanks for the feedback. I too am interested to see what Spring Roo, Grails, Groovy (implementation behind @EqualsAndHashCode and @Canonical), Project Lombok, and other JVM-based languages and frameworks do with this. Indeed, as much as I have liked Apache Commons and its EqualsBuilder and HashCodeBuilder, I'd like to see them get a facelift for newer versions of Java and they could make use of this new standardized approach themselves. I definitely plan to use Objects in my own new classes when I need to implement my own common methods, but it'd be nice to have the frameworks we use take advantage of the new standardized functionality as well.

Thanks again for your responses.

Dustin