java.util.Objects and friends

A small project I worked on during JDK 7 milestones 05 and 06 was the introduction of a java.util.Objects class to serve as a home for static utility methods operating on general objects (6797535, 6889858, 6891113). Those utilities include null-safe or null-tolerant methods for comparing two objects, computing the hash code of an object, and returning a string for an object, operations generally relating to the methods defined on java.lang.Object.

The code to implement each of these methods is very short, so short it is tempting to not write tests when adding such methods to a code base. But the methods they aren't so simple that mistakes cannot be made; replacing such helper methods with a common, tested version from the JDK would be a fine refactoring.

The current set of public methods in java.util.Objects is:

  • static boolean equals(Object a, Object b)

  • static boolean deepEquals(Object a, Object b)

  • static <T> int compare(T a, T b, Comparator<? super T> c)

  • static int hashCode(Object o)

  • static int hash(Object... values)

  • static String toString(Object o)

  • static String toString(Object o, String nullDefault)

  • static <T> T nonNull(T obj)

  • static <T> T nonNull(T obj, String message)

The first two methods define two equivalence relations over object references. Unlike the equals methods on Object, the equals(Object a, Object b) method handles null values. That is, true is returned if both arguments are null or if the first argument is non-null and a.equals(b) returns true. A method with this functionality is an especially common utility method to write, there are several versions of it in the JDK, so I expect the two-argument equals will be one of the most heavily used methods in the Objects class.

The second equivalence relation is defined by the deepEquals method. The equals and deepEquals relations can differ for object arrays; see for the javadoc for details. Equality implies deep equality, but the converse is not true. For example, in the program below arrays c and d are deep-equals but not equals.

public class Test {
   public static void main(String... args) {
       Object common = "A string in common.";
       Object[] a = {common};
       Object[] b = {common};
       Object[] c = {a};
       Object[] d = {b};
       // c and d are deepEquals, but not equals
   }
}

A third equivalence relation is the object identity relation defined by the == operator on references, but since that is already built into the language, no library support is needed. Identity equality implies equals equality and deepEquals equality.

Next, Objects includes a null-tolerant Comparator-style method which first compares for object identity using == before calling the provided Comparator. While Comparable classes aren't as widely available as the methods inherited from java.lang.Object, Comparable is a very useful and frequently implemented interface.

Objects has two hash-related methods. The first is a null-handling hash method which assigns null a zero hash code and the second is a utility method for implementing a reasonable hash function for a class just by passing in the right list of values.

The toString methods provide null handling support, in case of a null argument either returning "null" or the provided default string.

Finally, there are two methods to more conveniently handle null checks, intended to be useful when validating method and constructor parameters.

Taken together, the methods in Objects should lessen the pain and tedium of null handling until more systematic approaches are used.

The Objects API was shaped by discussion in various threads on core-libs-dev in September and October 2009. Several other bugs were also fixed as a result of those discussions, one adding a set of compare methods for primitive types (6582946) and another to consistently define the hash codes of the wrapper classes (4245470).

Comments:

something which might be useful is a toString(Object) which doesn't throw an exception.

This is particularly handy for error messages as it is often the case that the object toString'ed is not in a good state which can itself result in an exception. (Meaning the original exception can be obscured by an attempt to give more information)

e.g.
} catch (Exception e) {
logger.log(Level.WARNING, "Unable to process " + task, e); // task is not in a good state and task.toString() throws its own exception. :(
}

One way of handling this is to return a toString of the second exception thrown instead.

Posted by Peter Lawrey on February 04, 2010 at 05:11 AM PST #

Don't we have commons-lang already? Who would need this?

Posted by Nickolay Martinov on March 03, 2010 at 06:59 PM PST #

A little late in the game here. I think it would be useful to have a general NullObject factory (or proxy). This would be in line with these methods to handle nulls. This method would construct a NullObject (as in the NullObject pattern). Class<? extends T> createNullObject(Class<T> objectClass). This seems to be a possible implementation using dynamic proxies:http://www.codeproject.com/KB/java/NullObjectPattern.aspx. From what I gather dynamic proxies are only useful for Interfaces though. While I am trying to learn; this is over my current knowledge. I don't know how common it is for this pattern to be used; however, it would simplify the creation and maintainability of code which uses it. At the moment I am work with an interface that can return nulls. In the absence of a null safe deference, NullObjects seem like the next best thing in lieu of checking everything for != null. Unfortunately, I do not maintain these interfaces and do not want the tedium of creating a NullObject for each class from scratch. Plus don't you feel such a method would be awesome?

Posted by Jon H on March 10, 2010 at 06:57 AM PST #

Post a Comment:
Comments are closed for this entry.
About

darcy

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
News

No bookmarks in folder

Blogroll