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.

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

Related: