Friday, May 16, 2008

Using Collections Methods emptyList(), emptyMap(), and emptySet()

Early this year (2008), a reasonable question was asked on one of the Sun forums (the Core APIs - Collections: Lists, Sets, and Maps forum): When should I use emptyList(), emptySet() or emptyMap()?

Some responders replied with obvious answers that probably provided no new information to the person asking the question. What the person was really asking is why would one ever want a method that returns a Java List, a Java Set, or a Java Map that is empty and immutable. The Javadoc API documentation for the three methods Collections.emptyList(), Collections.emptyMap(), and Collections.emptySet() make it clear that these methods are for returning immutable, empty List, Map, and Set respectively. So, the question really is why would one want an empty immutable List, Map, or Set?

Item #27 in Joshua Bloch's first edition of Effective Java is called "Return zero-length arrays, not nulls" and this item describes some of the advantages of returning an empty array rather than a null from a method call. Although I have not gotten my hands on a copy of the recently released second edition of Effective Java, I suspect that its altered item on not returning null (now Item #43 - "Return empty arrays or collections, not null") demonstrates returning an empty collection and perhaps even demonstrates using these emptyList, emptySet, and emptyMap methods on the Collections class to get an empty collection that also has the additional benefit of being immutable (the new edition's Item #15 is "Minimize Mutability").

Why is returning an empty and immutable Collection often preferable to returning a null or even to returning a mutable collection? The most obvious disadvantage of returning a null is forcing the client of the method to deal with that null. The most obvious advantage to an immutable collection is the advantages associated with immutable objects and collections in concurrent programming. Discussion of why immutable objects and collections are highly desirable in multi-threaded environments can be found in Effective Concurrency for the Java Platform and in the Java Tutorials Concurrency Trail.

The example code below provides an example of using one of these methods:


import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class FunWithEmptyImmutableCollections
{
private Set<String> states;

/**
* Prepare the states data member with some sample names of states.
*/
private void prepareStates()
{
states = new HashSet<String>();
states.add("Alabama");
states.add("Alaska");
states.add("Arizona");
states.add("Arkansas");
states.add("California");
states.add("Colorado");
states.add("Connecticut");
states.add("Delaware");
states.add("Florida");
}

/**
* Provide names of all states that begin with provided alphabet letter.
*
* @param firstLetter Letter for which matching state names are desired.
* @return Set of names of states that begin with provided firstLetter.
*/
private Set<String> getStatesStartingWithDesignatedLetter(
final String firstLetter)
{
if ( (firstLetter == null)
|| (firstLetter.isEmpty())
|| (firstLetter.length() > 1) )
{
//return null;
return Collections.emptySet();
}

final Set<String> matchingStates = new HashSet<String>();
for ( final String stateName : states )
{
if ( stateName.startsWith(firstLetter.toUpperCase()) )
{
matchingStates.add(stateName);
}
}

return Collections.unmodifiableSet(matchingStates);
//return matchingStates;
}

/**
* Print out contents of provided Set.
*
* @param printTitle Title to print with set contents.
* @param setToPrint Set whose contents should be printed.
*/
public static void printSetContents(
final String printTitle,
final Set<String> setToPrint)
{
System.out.println("----- " + printTitle + "-----");
for ( final String setItem : setToPrint )
{
System.out.println( setItem );
}
System.out.println("--------------------");
}

/**
* Add provided stateName to provided Set of states.
*
* @param states Set of states to which state name should be added.
* @param stateName Name of state to be added.
*/
public static void addArbitraryState(
final Set<String> states,
final String stateName )
{
states.add(stateName);
}

/**
* Main executable method for running example.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
final FunWithEmptyImmutableCollections me =
new FunWithEmptyImmutableCollections();
me.prepareStates();
Set<String> states = me.getStatesStartingWithDesignatedLetter("C");
printSetContents( "Happy Path: Designated Letter Matches", states);
addArbitraryState(states, "Georgia");
states = me.getStatesStartingWithDesignatedLetter("B");
printSetContents( "Not-So-Happy Path: Designated Letter Not Found",
states );
addArbitraryState(states, "Georgia");
states = me.getStatesStartingWithDesignatedLetter("");
printSetContents( "Unhappy Path: null Returned", states);
addArbitraryState(states, "Georgia");
}
}


When used, these methods do not allow the client calling these methods to change the returned set. The commented-out lines show alternatives to these methods. Instead of returning an empty set, a null could be returned. Also, instead of returning an unmodifiable set, a normal, modifiable set could be returned. The following screen snapshots show the differences that occur when different combinations of these methods are used.

Trying to Modify Results of Collections.emptySet

The next screen snapshot demonstrates what happens when the code tries to modify a returned empty set. There is no NullPointerException in this case because the returned empty set is NOT null, but there is an UnsupportedOperationException.



Trying to Access Null

The next screen snapshot shows the all-too-familiar error message (NullPointerException) when null is returned rather than an empty Set and code tries to do something on that null.



Trying to Modify Collections.unmodifiableSet Returned Set

In this last screen snapshot, the results of trying to modify a Set returned using the Collections.unmodifiableSet method is demonstrated. Like when trying to modify the Set returned by Collections.emptySet(), an UnsupportedOperationException is encountered when the code attempts to modify the Set. However, there is slightly more detail in this exception stack trace, including reference to the Collections.unmodifibleSet.



The Collections class provides many useful static methods for working with Java Collections. In this blog entry, I covered the value of returning an empty, immutable Collection and demonstrated how easy this is to do with the appropriate Collections methods. As part of doing this, I also demonstrated the use of the similar methods for turning a regular collection into an unmodifiable collection.

One thing to keep in mind is the principle of Fail Fast. There may be times where it is simply better to fail than to return empty collections or null.

No comments: