Saturday, March 26, 2011

JDK 7: The New Objects Class

It was announced approximately 18 months ago that JDK 7 would include a new java.util.Objects class that would "hold commonly-written utility methods." As part of this announcement, Joe Darcy asked the community, "What other utility methods would have broad enough use and applicability to go into a common java.util class?" There were forums and posts on the matter and I blogged about this forthcoming class. The JDK 7 preview release includes this class and it can be tried out now. In this post, I look at use of most of the methods provided by this class and look at how NetBeans 6.9 already uses this class in some of its auto-generated methods.

The java.util.Objects class is new to JDK 7 and its Javadoc states that the class is "since 1.7" and describes the class as: "This class consists of static utility methods for operating on objects. These utilities include null-safe or null-tolerant methods for computing the hash code of an object, returning a string for an object, and comparing two objects." There are currently nine static methods with public accessibility: a compare method, a deepEquals method (for comparing arrays for equality), an equals method, a hash code calculation method potentially for multiple objects, a hashCode method for a single object, overloaded requireNonNull methods, and overloaded toString methods.

There are some general themes associated with the methods provided by the new Objects class. The methods tend to increase null safety. In other words, use of these methods is something I'd be happy to add to my list of Java NullPointerException handling techniques. When one desires an instance's hash code or string representation or one wants to compare an instance to another instance, it is typically prudent to first check the object reference for null to avoid a NullPointerException occurring before the hash code, String representation, or comparison can be made. By moving these common operations out of one of the objects being acted upon into this separate Objects class, the external class can check for null rather than forcing the object itself to be checked explicitly every time.

The second major theme relevant to the methods of the new Objects class is that these methods encapsulate functionality that many developers have written on their own or that other non-standard libraries supply. This new Objects class provides a relatively concise, standardized approach to invoking this common functionality. As will be seen in this post, it makes it easy to clean up commonly implemented methods such as overridden toString, equals, and hashCode methods and to enforce non-null parameter contract constraints.

Unlike the ever-present java.lang.Object class, but like fellow classes in the java.util package, the java.util.Objects class must be explicitly imported in Java classes that make use of it. The Objects class does NOT need to be explicitly imported when used in Groovy because Groovy automatically imports classes in the java.util package. Because all examples in this post are written in Java, the java.util.Objects class will be explicitly imported in each class.

The next code listing is for a simple Person class. I generated this class's hashCode() and equals(Object) methods using NetBeans 6.9's "Insert Code" mechanism and was pleased to see that (assuming I had the Java 1.7 Platform set for my project) the relevant Objects class methods were used in these automatically generated methods. The automatically generated toString() did not make use of either of the overloaded Objects.toString() methods. I assumed that might have been because the Person class has only String attributes and these don't need to be checked for null before the implicit toString() is invoked on them. However, even when I added a more complex data type that would lead to an NPE when its implicit toString() was called, NetBeans did not use the Objects.toString() method to avoid the potential NPE.

Person.java
package dustin.examples;

import java.util.Objects;

/**
 * Simple class to be used in demonstration of JDK 7's java.util.Objects class.
 *
 * @author Dustin
 */
public class Person
{
   private String lastName;

   private String firstName;

   /**
    * Parameterized constructor for instantiating a Person. Both a non-null first
    * name and a non-null last name must be provided (no "Madonna" or "Lindsay"
    * or "Seal" allowed here [unless you pass one name as an empty String]).
    *
    * @param newLastName This Person instance's last name; must not be null.
    * @param newFirstName This Person instance's first name; must not be null.
    * @throws NullPointerException Thrown if either provided name parameter is
    *   null.
    */
   public Person(final String newLastName, final String newFirstName)
   {
      this.lastName = Objects.requireNonNull(newLastName, "Last name cannot be null.");
      this.firstName = Objects.requireNonNull(newFirstName, "First name cannot be null.");
   }

   public String getLastName()
   {
      return this.lastName;
   }

   public String getFirstName()
   {
      return this.firstName;
   }

   /**
    * NetBeans 6.9-generated equals(Object) method. It used
    * Objects.equals(Object, Object) to avoid the need to check for null on any
    * references before comparing them. This can really clean up equals method
    * implementations.
    *
    * @param obj Object to be compared to me for equality.
    * @return {@code true} if the provided object is considered equal to me;
    *    {@code false} otherwise.
    */
   public boolean equals(Object obj)
   {
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      final Person other = (Person) obj;
      if (!Objects.equals(this.lastName, other.lastName))
      {
         return false;
      }
      if (!Objects.equals(this.firstName, other.firstName))
      {
         return false;
      }
      return true;
   }

   /**
    * NetBeans 6.9-generated hashCode(). It used Objects.hashCode(Object)!
    * 
    * @return Hash code for this instance.
    */
   public int hashCode()
   {
      int hash = 5;
      hash = 97 * hash + Objects.hashCode(this.lastName);
      hash = 97 * hash + Objects.hashCode(this.firstName);
      return hash;
   }

   @Override
   public String toString()
   {
      return this.firstName + " " + this.lastName;
   }
}

The NetBeans-generated hashCode() method makes use of Objects.hashCode(Object) to get the individual hash codes of each of its constituent attributes. The advantage of doing this is that each attribute does not need to be checked for null before asking for its hash code. The null checking is still implicitly performed (and zero is returned if the object is null), but the code is much cleaner.

The NetBeans-generated equals(Object) method makes use of Objects.equals(Object, Object) to safely compare the current object's attributes to the provided object's attributes. This method is null-safe and returns true if both attributes being compared are null and returns false if either is null without the other being null. If both compared attributes are non-null, then a standard equality check is made. The Objects.equals(Object,Object) method provides consistent null-safe equality checking with much cleaner code than could be done before this.

The Person class listed above is used by the next code listing, which is the main code listing demonstrating the majority of the methods on the new Objects class.

ObjectsClassDemo.java
package dustin.examples;

import java.util.Objects;   // must be explicitly imported
import java.util.logging.Level;

import java.util.logging.Logger;

/**
 * Simple demonstration of the new java.util.Objects class coming with JDK 7.
 */
public class ObjectsClassDemo
{
   private static final Logger LOGGER = Logger.getLogger(ObjectsClassDemo.class.getName());

   /**
    * Demonstrate usage of Objects.requireNonNull(Object).
    *
    * @param shouldNotBeNull String object be passed to Objects.requireNonNull(Object).
    */
   private static void demoObjectsClassNullness(final String shouldNotBeNull)
   {
      String stringToUse = null;
      try
      {
         stringToUse= Objects.requireNonNull(shouldNotBeNull);
      }
      catch (NullPointerException npe)
      {
         LOGGER.severe(npe.toString());
      }
      LOGGER.log(Level.INFO, "Provided String was: ''{0}''", stringToUse);
   }

   /**
    * Demonstrate usage of Objects.requireNonNull(Object,String). This overloaded
    * version of Objects.requireNonNull is generally preferable because the
    * second (String) parameter is the "message" portion of the NullPointerException
    * that is generated. Without this parameter, the message portion is empty.
    *
    * @param shouldNotBeNull String object to be passed to
    *    Objects.requireNonNull(Object,String) where the first (Object) parameter
    *    is the object that should not be null and the second (String) parameter
    *    is the message to display if the first parameter is null.
    */
   private static void demoObjectsClassNullness(
      final String shouldNotBeNull,
      final String messageIfNull)
   {
      String stringToUse = null;
      try
      {
         stringToUse = Objects.requireNonNull(shouldNotBeNull, messageIfNull);
      }
      catch (NullPointerException npe)
      {
         LOGGER.severe(npe.toString());
      }
      LOGGER.log(Level.INFO, "Provided String was: ''{0}''", stringToUse);
   }

   /**
    * Demonstrate use of Objects.toString(Object) with default message if provided
    * object is null.
    *
    * @param objectToStringify Object to call Objects.toString(Object) on.
    */
   private static void demoNullSafeToStringDefault(
      final Object objectToStringify)
   {
      LOGGER.log(Level.INFO, "toString(): {0}", Objects.toString(objectToStringify));
   }

   /**
    * Demonstrate use of Objects.toString(Object, String) with customized String
    * used to "toString()" when the provided object is null.
    *
    * @param objectToStringify Object to call Objects.toString(Object) on.
    * @param toStringIfObjectIsNull String to be shown as result of "toString()"
    *    on a null reference.
    */
   private static void demoNullSafeToStringCustomized(
      final Object objectToStringify, final String toStringIfObjectIsNull)
   {
      LOGGER.log(Level.INFO, "toString(): {0}", Objects.toString(objectToStringify, toStringIfObjectIsNull));
   }

   /**
    * Demonstrate Objects.hash(). The Objects.hashCode() method is also
    * demonstrated and it is handy to be able to safely get a hash code without
    * explicit null check (0 is returned by Objects.hashCode(Object) if the
    * provided Object reference is null). It is also important to note that
    * calling Objects.hash(Object...) on a single object will NOT result in the
    * same hash code returned from Objects.hashCode(Object) on that same object.
    *
    * @param objectsToHash One or more objects to hash.
    */
   private static void demoHash(final Object firstObjectToHash, final Object ... objectsToHash)
   {
      final int numberObjects =
           objectsToHash.length
         + (firstObjectToHash != null ? 1 : 0);
      final int multipleObjectsHash = Objects.hash(objectsToHash);
      LOGGER.log(Level.INFO, "Hash Code for {0} objects: {1}",
                 new Object[]{numberObjects, multipleObjectsHash});
      LOGGER.log(Level.INFO, "Hash code for first object ({0}) of {1} object(s) is: {2}",
                 new Object[]{Objects.toString(firstObjectToHash), numberObjects, Objects.hashCode(firstObjectToHash)});
   }

   /**
    * Demonstrate Objects.equals(Object, Object) method.
    *
    * @param firstObject First object to be compared by Objects.equals(Object,Object).
    * @param secondObject Second object to be compared by Objects.equals(Object,Object).
    */
   private static void demoEquals(final Object firstObject, final Object secondObject)
   {
      final String aproposPhrase =  Objects.equals(firstObject, secondObject)
                                  ? " is equal to "
                                  : " is NOT equal to ";
      LOGGER.log(Level.INFO, "{0}{1}{2}",
                 new Object[]{Objects.toString(firstObject), aproposPhrase, Objects.toString(secondObject)});
   }

   /**
    * Main demonstration executable.
    *
    * @param arguments Command-line arguments; none anticipated.
    */
   public static void main(final String[] arguments)
   {
      demoObjectsClassNullness("Dustin");
      demoObjectsClassNullness(null);

      demoObjectsClassNullness("Dustin", "The String you passed is null!");
      demoObjectsClassNullness(null, "The String you passed is null!");

      final Person person = new Person("Smith", "William");
      Person nullPerson = null;
      try
      {
         nullPerson = new Person("Dump", null);
      }
      catch (NullPointerException npe)
      {
         LOGGER.severe(npe.toString());
      }

      demoNullSafeToStringDefault(person);
      demoNullSafeToStringDefault(nullPerson);

      demoNullSafeToStringCustomized(person, "No such person");
      demoNullSafeToStringCustomized(nullPerson, "No such person");

      demoHash(person, "Dustin");
      demoHash("Dustin", person);
      demoHash(person);
      demoHash("Dustin");
      demoHash(nullPerson);

      final Person person2 = new Person("Smith", "Barney");
      final Person person3 = new Person("Smith", "Barney");
      demoEquals(person, person2);
      demoEquals(person, nullPerson);
      demoEquals(person2, person3);
      demoEquals(nullPerson, null);
   }
}

The ObjectsClassDemo class contained in the code listing above demonstrates most of the methods on the Objects class. The output from running the above class's main function is shown next.

Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
INFO: Provided String was: 'Dustin'
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
SEVERE: java.lang.NullPointerException
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
INFO: Provided String was: 'null'
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
INFO: Provided String was: 'Dustin'
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
SEVERE: java.lang.NullPointerException: The String you passed is null!
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
INFO: Provided String was: 'null'
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo main
SEVERE: java.lang.NullPointerException: First name cannot be null.
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoNullSafeToStringDefault
INFO: toString(): William Smith
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoNullSafeToStringDefault
INFO: toString(): null
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoNullSafeToStringCustomized
INFO: toString(): William Smith
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoNullSafeToStringCustomized
INFO: toString(): No such person

Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 2 objects: 2,058,375,062
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (William Smith) of 2 object(s) is: -2,111,928,853
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 2 objects: -2,111,928,822
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (Dustin) of 2 object(s) is: 2,058,375,031
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 1 objects: 1
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (William Smith) of 1 object(s) is: -2,111,928,853
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 1 objects: 1
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (Dustin) of 1 object(s) is: 2,058,375,031
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 0 objects: 1
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (null) of 0 object(s) is: 0

Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoEquals
INFO: William Smith is NOT equal to Barney Smith
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoEquals
INFO: William Smith is NOT equal to null
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoEquals
INFO: Barney Smith is equal to Barney Smith
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoEquals
INFO: null is equal to null

The above code and its corresponding output lead to several observations:
  • The overloaded Objects.requireNonNull methods are useful for easily checking parameters to ensure that they are not null and throwing a NullPointerException if any is null.
    • The Objects.requireNonNull(T) method that only accepts a single parameter throws a NullPointerException without a message portion while the Objects.requireNonNull(T, String) method provides the String (second) parameter as the message portion of the NullPointerException. I prefer the latter because one of my personal pet peeves is exception's without a message String.
  • The overloaded Objects.toString(Object) methods are similar to the requireNonNull methods in that uses a "default" while the other allows a customized String to be provided. Both of these methods ensure that a String representation of some sort is provided, even for null references.
  • The Objects.hash(Object...) method and the Objects.hashCode(Object) methods provide null-safe functionality for accessing hash codes.
    • The Objects.hash(Object...) method provides a hash code constructed from multiple objects. Note that this method does NOT return the same hash code for a single provided object as would be returned for the same single object is passed to Objects.hashCode(Object).
    • The Objects.hashCode(Object) method is for acquiring a hash code for a single object in a null-safe way. A zero is returned by this method if the provided object is null.
  • Objects.equals(Object,Object) provides a null-safe way to check two objects for equality. As described above, it returns true if both arguments are null.

I don't specifically focus on the Objects.deepEquals(Object,Object) method or the Objects.compare(T,T,Comparator) method here. They perform as you'd expect from their names. The deepEquals method is used to compare objects that are arrays using Arrays.deepEquals. The compare method returns zero if both provided objects comparison would normally equal zero or if both provided references are null.

Other References

Additional coverage of the java.util.Objects class can be found in Java 7: The New java.util.Objects Class, The Java 7 Features Bound to Make Developers More Productive, and JDK 7: What Frequently Rewritten Methods Should be Included in java.util.Objects?

Conclusion

The java.util.Objects class will provide Java developers with a concise, standardized approach for performing tasks that they've previously had to use non-standard and/or verbose custom code to resolve. Even when developers use IDEs to generate their code, the benefit of more concise and readable code remains. As shown in this blog post, NetBeans 6.9 is already capable of generating code that makes use of the Objects class.

10 comments:

Eric Jablow said...

Is there a method anywhere that would be the equivalent of

public static <T> int min(Comparable<T> a, Comparable<T> b) {
return a.compareTo(b) < 0 ? a : b;
}

That probably isn't the right signature, but super bounds make my head hurt.

Martijn Verburg said...

Another great post! Objects is an area I hadn't even looked at with regards to Java 7. A couple of quick comments.

You'll probably get more joy out of using Netbeans 7.0 Beta 2 with the JDK 7 preview build or later (builds). We've been running our experiments on there successfully, it seems quite stable etc.

The additional NotNull checking is interesting, although I suspect Groovy developers will still stick with their ? syntax. I also wonder how it'll cleanly interoperate with some of the various @NotNull annotation schemes out there.

I'm liking the deepCompare/Equals methods, anything that can help in that area is welcome.

Cheers,
Martijn

Aditya Ch said...

I still don't understand what are we achieving with this utilities !!! Instead of obj !=null we are calling a utility to do the same.
Wouldn't that be a good idea to include this internal to the JDK !!!

Dustin said...

Eric,

I'm not familiar with any standard method available for performing the generic minimum determination between two Comparables. However, the article An Introduction to Generics in Java demonstrates (on page 3) implementation of a max method.

The generics wildcard bounding hurts my head as well, but piecing your suggestion together with the article's suggestion and with the advice given in More Fun with Wildcards, the following implementation of a min method should work:

/**
* Determine the minimum value of the two provided Comparable parameters.
*
* @param a First Comparable parameter to be compared for minimum; should be
* non-null and of same type as other ('b') parameter.
* @param b Second Comparable parameter to be compared for minimum; should be
* non-null and of same type as other ('a') parameter.
* @return Minimum of two provided parameters.
* @throws ClassCastException Exception thrown in the two provided parameters
* are not of the same types (or at least cannot be cast to same type).
* @throws NullPointerException Exception thrown if either provided parameter
* is null.
*/
public static <T extends Comparable<? super T>> T min(T a, T b)
{
return a.compareTo(b) < 0 ? a : b;
}

It seems to me that this would be a nice addition to have in a class like the Objects class. Perhaps it would be a good suggestion for Java 8?

Dustin said...

Martijn,

It's good to hear of positive experiences (especially stability) with NetBeans 7.0 Beta 2. I'll probably download and install that later this week so that I can take advantage of further JDK 7 features.

I agree that it's difficult to argue with the simple elegance of Groovy's safe navigation operator. I wish we had that in Java.

Dustin

Dustin said...

Aditya,

I think most Java developers would prefer more built-in null handling in Java, but I don't see that happening in Java as we know it today given the dramatic effect such a shift would have on backwards compatibility.

Although not quite the same as built-in support for automatic null handling, I think the technique used in Changing Java’s Semantics for Handling Null Pointer Exceptions is interesting.

Dustin

Craig Ringer said...

While not on the new Objects utility class, a closely related new method the JDK needs is a static isEmptyOrNull() on the String class. Pretty much every code base will have its own version of this or will use Apache Commons Lang.

I'd actually love to see much of Apache Commons Lang (and Apache Commons IO) pulled into Java. Certainly StringUtils (as static methods on String), IOUtils for the static stream copying methods, FileUtils, ObjectUtils (which sounds like the conceptual origin of much of the new Objects class), CompareToBuilder, HashCodeBuilder, EqualsBuilder, ToStringBuilder, ExceptionUtils.getRootCause as a method of Throwable, etc. The JDK has been focusing on adding huge new and fancy codebases, and really neglecting the usability of the core library.

Dustin said...

Craig,

I completely agree with you. While I welcomed the addition of String.isEmpty(), I have always wished we had a static method by which we could check for null in conjunction with that check.

I similarly agree with you that it'd be nice to have more common functionality already encapsulated in Apache Commons in the standard development kit.

Adam Rabung said...

@Eric, check out Google's Guava:
http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Ordering.html#min(E, E)

Dustin said...

Another post on the new (to Java 7) Objects class is available here.