Monday, 3 October 2011

JDK 7: The New Objects Class


It was announced approximately 18 months ago that JDK 7 would include a new java.util.Objects classthat would "hold commonly-written utility methods." As part of this announcementJoe 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 equalsmethod, 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 toStringequals, 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. TheObjects 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() andequals(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 theObjects.toString() method to avoid the potential NPE.

Person.java

  1. package dustin.examples;  
  2.   
  3. import java.util.Objects;  
  4.   
  5. /** 
  6.  * Simple class to be used in demonstration of JDK 7's java.util.Objects class. 
  7.  * 
  8.  * @author Dustin 
  9.  */  
  10. public class Person  
  11. {  
  12.    private String lastName;  
  13.   
  14.    private String firstName;  
  15.   
  16.    /** 
  17.     * Parameterized constructor for instantiating a Person. Both a non-null first 
  18.     * name and a non-null last name must be provided (no "Madonna" or "Lindsay" 
  19.     * or "Seal" allowed here [unless you pass one name as an empty String]). 
  20.     * 
  21.     * @param newLastName This Person instance's last name; must not be null. 
  22.     * @param newFirstName This Person instance's first name; must not be null. 
  23.     * @throws NullPointerException Thrown if either provided name parameter is 
  24.     *   null. 
  25.     */  
  26.    public Person(final String newLastName, final String newFirstName)  
  27.    {  
  28.       this.lastName = Objects.requireNonNull(newLastName, "Last name cannot be null.");  
  29.       this.firstName = Objects.requireNonNull(newFirstName, "First name cannot be null.");  
  30.    }  
  31.   
  32.    public String getLastName()  
  33.    {  
  34.       return this.lastName;  
  35.    }  
  36.   
  37.    public String getFirstName()  
  38.    {  
  39.       return this.firstName;  
  40.    }  
  41.   
  42.    /** 
  43.     * NetBeans 6.9-generated equals(Object) method. It used 
  44.     * Objects.equals(Object, Object) to avoid the need to check for null on any 
  45.     * references before comparing them. This can really clean up equals method 
  46.     * implementations. 
  47.     * 
  48.     * @param obj Object to be compared to me for equality. 
  49.     * @return {@code true} if the provided object is considered equal to me; 
  50.     *    {@code false} otherwise. 
  51.     */  
  52.    public boolean equals(Object obj)  
  53.    {  
  54.       if (obj == null)  
  55.       {  
  56.          return false;  
  57.       }  
  58.       if (getClass() != obj.getClass())  
  59.       {  
  60.          return false;  
  61.       }  
  62.       final Person other = (Person) obj;  
  63.       if (!Objects.equals(this.lastName, other.lastName))  
  64.       {  
  65.          return false;  
  66.       }  
  67.       if (!Objects.equals(this.firstName, other.firstName))  
  68.       {  
  69.          return false;  
  70.       }  
  71.       return true;  
  72.    }  
  73.   
  74.    /** 
  75.     * NetBeans 6.9-generated hashCode(). It used Objects.hashCode(Object)! 
  76.     *  
  77.     * @return Hash code for this instance. 
  78.     */  
  79.    public int hashCode()  
  80.    {  
  81.       int hash = 5;  
  82.       hash = 97 * hash + Objects.hashCode(this.lastName);  
  83.       hash = 97 * hash + Objects.hashCode(this.firstName);  
  84.       return hash;  
  85.    }  
  86.   
  87.    @Override  
  88.    public String toString()  
  89.    {  
  90.       return this.firstName + " " + this.lastName;  
  91.    }  
  92. }  

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
  1. package dustin.examples;  
  2.   
  3. import java.util.Objects;   // must be explicitly imported  
  4. import java.util.logging.Level;  
  5.   
  6. import java.util.logging.Logger;  
  7.   
  8. /** 
  9.  * Simple demonstration of the new java.util.Objects class coming with JDK 7. 
  10.  */  
  11. public class ObjectsClassDemo  
  12. {  
  13.    private static final Logger LOGGER = Logger.getLogger(ObjectsClassDemo.class.getName());  
  14.   
  15.    /** 
  16.     * Demonstrate usage of Objects.requireNonNull(Object). 
  17.     * 
  18.     * @param shouldNotBeNull String object be passed to Objects.requireNonNull(Object). 
  19.     */  
  20.    private static void demoObjectsClassNullness(final String shouldNotBeNull)  
  21.    {  
  22.       String stringToUse = null;  
  23.       try  
  24.       {  
  25.          stringToUse= Objects.requireNonNull(shouldNotBeNull);  
  26.       }  
  27.       catch (NullPointerException npe)  
  28.       {  
  29.          LOGGER.severe(npe.toString());  
  30.       }  
  31.       LOGGER.log(Level.INFO, "Provided String was: ''{0}''", stringToUse);  
  32.    }  
  33.   
  34.    /** 
  35.     * Demonstrate usage of Objects.requireNonNull(Object,String). This overloaded 
  36.     * version of Objects.requireNonNull is generally preferable because the 
  37.     * second (String) parameter is the "message" portion of the NullPointerException 
  38.     * that is generated. Without this parameter, the message portion is empty. 
  39.     * 
  40.     * @param shouldNotBeNull String object to be passed to 
  41.     *    Objects.requireNonNull(Object,String) where the first (Object) parameter 
  42.     *    is the object that should not be null and the second (String) parameter 
  43.     *    is the message to display if the first parameter is null. 
  44.     */  
  45.    private static void demoObjectsClassNullness(  
  46.       final String shouldNotBeNull,  
  47.       final String messageIfNull)  
  48.    {  
  49.       String stringToUse = null;  
  50.       try  
  51.       {  
  52.          stringToUse = Objects.requireNonNull(shouldNotBeNull, messageIfNull);  
  53.       }  
  54.       catch (NullPointerException npe)  
  55.       {  
  56.          LOGGER.severe(npe.toString());  
  57.       }  
  58.       LOGGER.log(Level.INFO, "Provided String was: ''{0}''", stringToUse);  
  59.    }  
  60.   
  61.    /** 
  62.     * Demonstrate use of Objects.toString(Object) with default message if provided 
  63.     * object is null. 
  64.     * 
  65.     * @param objectToStringify Object to call Objects.toString(Object) on. 
  66.     */  
  67.    private static void demoNullSafeToStringDefault(  
  68.       final Object objectToStringify)  
  69.    {  
  70.       LOGGER.log(Level.INFO, "toString(): {0}", Objects.toString(objectToStringify));  
  71.    }  
  72.   
  73.    /** 
  74.     * Demonstrate use of Objects.toString(Object, String) with customized String 
  75.     * used to "toString()" when the provided object is null. 
  76.     * 
  77.     * @param objectToStringify Object to call Objects.toString(Object) on. 
  78.     * @param toStringIfObjectIsNull String to be shown as result of "toString()" 
  79.     *    on a null reference. 
  80.     */  
  81.    private static void demoNullSafeToStringCustomized(  
  82.       final Object objectToStringify, final String toStringIfObjectIsNull)  
  83.    {  
  84.       LOGGER.log(Level.INFO, "toString(): {0}", Objects.toString(objectToStringify, toStringIfObjectIsNull));  
  85.    }  
  86.   
  87.    /** 
  88.     * Demonstrate Objects.hash(). The Objects.hashCode() method is also 
  89.     * demonstrated and it is handy to be able to safely get a hash code without 
  90.     * explicit null check (0 is returned by Objects.hashCode(Object) if the 
  91.     * provided Object reference is null). It is also important to note that 
  92.     * calling Objects.hash(Object...) on a single object will NOT result in the 
  93.     * same hash code returned from Objects.hashCode(Object) on that same object. 
  94.     * 
  95.     * @param objectsToHash One or more objects to hash. 
  96.     */  
  97.    private static void demoHash(final Object firstObjectToHash, final Object ... objectsToHash)  
  98.    {  
  99.       final int numberObjects =  
  100.            objectsToHash.length  
  101.          + (firstObjectToHash != null ? 1 : 0);  
  102.       final int multipleObjectsHash = Objects.hash(objectsToHash);  
  103.       LOGGER.log(Level.INFO, "Hash Code for {0} objects: {1}",  
  104.                  new Object[]{numberObjects, multipleObjectsHash});  
  105.       LOGGER.log(Level.INFO, "Hash code for first object ({0}) of {1} object(s) is: {2}",  
  106.                  new Object[]{Objects.toString(firstObjectToHash), numberObjects, Objects.hashCode(firstObjectToHash)});  
  107.    }  
  108.   
  109.    /** 
  110.     * Demonstrate Objects.equals(Object, Object) method. 
  111.     * 
  112.     * @param firstObject First object to be compared by Objects.equals(Object,Object). 
  113.     * @param secondObject Second object to be compared by Objects.equals(Object,Object). 
  114.     */  
  115.    private static void demoEquals(final Object firstObject, final Object secondObject)  
  116.    {  
  117.       final String aproposPhrase =  Objects.equals(firstObject, secondObject)  
  118.                                   ? " is equal to "  
  119.                                   : " is NOT equal to ";  
  120.       LOGGER.log(Level.INFO, "{0}{1}{2}",  
  121.                  new Object[]{Objects.toString(firstObject), aproposPhrase, Objects.toString(secondObject)});  
  122.    }  
  123.   
  124.    /** 
  125.     * Main demonstration executable. 
  126.     * 
  127.     * @param arguments Command-line arguments; none anticipated. 
  128.     */  
  129.    public static void main(final String[] arguments)  
  130.    {  
  131.       demoObjectsClassNullness("Dustin");  
  132.       demoObjectsClassNullness(null);  
  133.   
  134.       demoObjectsClassNullness("Dustin""The String you passed is null!");  
  135.       demoObjectsClassNullness(null"The String you passed is null!");  
  136.   
  137.       final Person person = new Person("Smith""William");  
  138.       Person nullPerson = null;  
  139.       try  
  140.       {  
  141.          nullPerson = new Person("Dump"null);  
  142.       }  
  143.       catch (NullPointerException npe)  
  144.       {  
  145.          LOGGER.severe(npe.toString());  
  146.       }  
  147.   
  148.       demoNullSafeToStringDefault(person);  
  149.       demoNullSafeToStringDefault(nullPerson);  
  150.   
  151.       demoNullSafeToStringCustomized(person, "No such person");  
  152.       demoNullSafeToStringCustomized(nullPerson, "No such person");  
  153.   
  154.       demoHash(person, "Dustin");  
  155.       demoHash("Dustin", person);  
  156.       demoHash(person);  
  157.       demoHash("Dustin");  
  158.       demoHash(nullPerson);  
  159.   
  160.       final Person person2 = new Person("Smith""Barney");  
  161.       final Person person3 = new Person("Smith""Barney");  
  162.       demoEquals(person, person2);  
  163.       demoEquals(person, nullPerson);  
  164.       demoEquals(person2, person3);  
  165.       demoEquals(nullPerson, null);  
  166.    }  
  167. }  

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 theObjects.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 requireNonNullmethods 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 theObjects.compare(T,T,Comparator) method here. They perform as you'd expect from their names. ThedeepEquals method is used to compare objects that are arrays using Arrays.deepEquals. Thecompare 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 ClassThe 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.

No comments:

Post a Comment