One of my favorite
standard Java classes is the
Collections class. This is not surprising considering how often I find myself using the
Java Collections Framework. Each Java Collection interface and implementation is useful in its own right, but the
Collections class provides some convenience methods that are highly useful in working with Java collections.
The
Javadoc API documentation for java.util.Collections explains the basics of this class such as the fact that all of its methods are static and that they all either operate on a provided collection or return a collection (here I am using "
collection" more broadly to include
Map as opposed to narrowly focusing on collections implementing the
Collection interface). There are so many highly useful methods in this class that I am going to only focus on a subset of them to keep what is already a lengthy blog posting from becoming too large.
Empty CollectionsSeveral of the methods provided by
java.util.Collections perform similar functionality on different types of collections. For example, the methods
Collections.emptySet(),
Collections.emptyMap(), and
Collections.emptyList() perform the same functionality, but on
Sets,
Maps, and
Lists respectively. In the case of these methods, they each return the appropriate collection type that is empty (no elements in it), typesafe, and immutable. In other words, the provided collection is empty and nothing can be added to it. As I have
blogged about previously, this is useful for implementing the recommendation of
Effective Java to return empty collections rather than null.
The following sample code shows how one of these "empty" methods can be used and the image below the code demonstrates the
UnsupportedOperationException that is thrown when the code execution tries to add an element to this empty collection that is immutable. For this particular example, I am using
Collections.emptySet(), but the principle is the same for the
List and
Map versions.
demonstrateEmptySet()
-
-
-
- public void demonstrateEmptySet()
- {
- log("===== DEMONSTRATING EMPTY SET =====", System.out);
- final Set emptySet = Collections.emptySet();
- log("Size of returned emptySet(): " + emptySet.size(), System.out);
- log("----- Adding String to Collections.emptySet() returned Set -----", System.out);
- emptySet.add("A new String to add.");
- }
Results of Running demonstrateEmptySet()
Single-Element CollectionsAnother functionality provided by
Collections for
Set,
List, and
Map is providing of a single-element collection that, like its empty element sibling, is immutable and typesafe. To illustrate this, the next code sample and output screen snapshot will demonstrate use of
Collections.singletonList(T), though the same principles apply to
Collections.singletonMap(K,V) and
Collections.singleton(T) (no "Set" in method name is
notan accidental omission on my part though that method does apply to the Set).
demonstrateSingletonList()
-
-
-
- public void demonstrateSingletonList()
- {
- log("===== DEMONSTRATING SINGLETON LIST ======", System.out);
- final List singleElementList =
- Collections.singletonList("A single String to add.");
- log( "Size of returned singletonList(): "
- + singleElementList.size()
- + NEW_LINE,
- System.out);
- log(
- "----- Adding String to Collections.singletonList() returned List -----",
- System.out);
- singleElementList.add("Another String to add.");
- }
Results of Running demonstrateSingletonList()
I have found these various "singleton" methods to be useful for passing a single value to an API that requires a collection of that value. Of course, this works best when the code processing the passed-in value does not need to add to the collection.
Unmodifiable CollectionsThe methods already covered for returning empty collections and single-element collections provided these collections as unmodifiable collections. For situations in which an unmodifiable collection is desired with more than one element, appropriate methods are
Collections.unmodifiableList(List),
Collections.unmodifiableMap(Map),
Collections.unmodifiableSet(Set), and the most general
Collections.unmodifiableCollection(Collection). In addition to these, there are also methods for returning a Set or Map that is sorted in addition to being unmodifiable:
Collections.unmodifiableSortedMap and
Collections.unmodifiableSortedSet.
A source code example of using
Collections.unmodifiableMap(Map) and the results of running that example are shown next.
demonstrateUnmodifiableMap()
-
-
-
-
-
- public void demonstrateUnmodifiableMap()
- {
- log("===== DEMONSTRATING UNMODIFIABLE MAP =====", System.out);
- final Map unmodifiableMap =
- Collections.unmodifiableMap(this.favoriteGenreMovies);
- log(
- "Map BEFORE MODIFICATION: " + NEW_LINE + unmodifiableMap.toString(),
- System.out);
-
- log(
- "----- Putting a new value in Map for existing key in underlying Map. -----",
- System.out);
- this.favoriteGenreMovies.put(MovieGenre.JAMES_BOND, "Thunderball");
- log( "The Unmodifiable Map AFTER MODIFICATION: " + NEW_LINE
- + unmodifiableMap.toString(), System.out);
-
- log(
- "----- Putting a completely new key in the underlying map. -----",
- System.out);
- this.favoriteGenreMovies.put(MovieGenre.MYSTERY, "The Usual Suspects");
- log( "The Unmodifiable Map AFTER MODIFICATION: " + NEW_LINE
- + unmodifiableMap.toString(), System.out);
-
- log(
- "----- Now try to 'put' to the unmodifiable wrapper collection -----",
- System.out);
- unmodifiableMap.put(MovieGenre.MYSTERY, "Rear Window");
- }
Results of Running demonstrateUnmodifiableMap()
As a brief side note here, I intentionally added to this example the changing of values of the Map that underlies the unmodifiable Map to demonstrate that the source Collections upon which unmodifiable versions are returned can still be changed as needed. It is only the returned collection that is unmodifiable in the sense that it cannot have elements added to it or elements removed from it.
Checked CollectionsAll of the methods on the Collections class examined in this posting so far have returned unmodifiable collections as either empty, single-element, or multi-element collections. However, the Collections class is capable of much more than simply provide unmodifiable wrappers on collections. The "checked" methods [
Collections.checkedCollection(Collection, Class),
Collections.checkedList(List, Class),
Collections.checkedMap(Map, Class), and
Collections.checkedSet(Set, Class)] are useful for dealing with mixes of collections that use
generic types and collections handling based one
raw collections.
Before the introduction of
generics with
J2SE 5, we were required to find out about type problems associated with collections as we pulled an item out of a collection and cast it to the expected type at runtime. The advent of J2SE 5 generics enabled us to generally move this type mismatch detection from runtime on extraction of the items from a collection to compile time on insertion into the collection. This is highly advantageous because we can find the problem where it really occurs originally (at insertion) and because we can find it sooner (at compile time rather than at runtime).
Unfortunately, there are ways in which this type checking can be circumvented. For example, if a module out of our control accesses our collection as a raw collection, that module will be able to insert non-compliant items in the collection. Of course, we could also do the same ourselves if we're not careful or have legacy code that did not get fully ported. The
Javadoc documentation for the Collections.checkedCollection method explains that these "checked" methods are also useful for debugging problems associated with
ClassCastExceptions and generically typed collections by wrapping a collection instantiation in one of these calls.
To illustrate the problem that can occur when generically typed collections and raw collections are mixed, the following code intentionally does that mixture and the resulting problems are documented in the screen snapshot with its output.
demonstrateProblemWithoutCheckedCollection()
-
-
-
-
- private void demonstrateProblemWithoutCheckedCollection()
- {
- log("1. Demonstrate problem of no checked collection.", System.out);
- final Integer arbitraryInteger = new Integer(4);
- final List rawList = this.favoriteBooks;
- rawList.add(arbitraryInteger);
- final List stringList = rawList;
- for (final String element : stringList)
- {
- log(element.toUpperCase(), System.out);
- }
- }
Results of demonstrateProblemWithoutCheckedCollection
This problem may look easy to address, but it is much more challenging if the mixed use of raw collections with generically typed collections is separated by many lines of code, different methods, or even different classes. In fact, the error upon access of the non-compliant collection element could happen well after its insertion in terms of both time passed and number of lines of code executed.
The use of the "checked" collection method brings some of the advantages of generically typed collections back even when raw collections are mixed. The following code sample and the screen snapshot of the output it generates are shown next.
demonstrateProblemFixedWithCheckedCollection()
-
-
-
- private void demonstrateProblemFixedWithCheckedCollection()
- {
- log("2. Demonstrate problem fixed with checked collection", System.out);
- final Integer arbitraryInteger = new Integer(4);
- final List checkedList = Collections.checkedList(this.favoriteBooks, String.class);
- final List rawList = checkedList;
- rawList.add(arbitraryInteger);
- final List stringList = rawList;
- for (final String element : stringList)
- {
- log(element.toUpperCase(), System.out);
- }
- }
Results of demonstateProblemFixedWithCheckedCollection()
Although use of the "checked" method here still results in the error being detected at runtime, it provides the advantage of detecting the error where it originally occurs (insertion) rather than some unknown amount of time later.
Enumerations and CollectionsThe
Enumeration has been available since JDK 1.0. Because the Enumeration interface is used with several key legacy APIs, it can be useful to be able to easily convert back and forth between an
Enumeration and a collection. Two methods that support this conversion are
Collections.list(Enumeration) [converts the provided Enumeration into a List] and
Collections.enumeration(Collection) [provides an enumeration over a Collection].
The following source code example demonstrates the conversion of an Enumeration to a List and the screen snapshot after it demonstrates its output.
demonstrateEnumerationToList()
-
-
-
- public void demonstrateEnumerationToList()
- {
- log("===== Demonstrate Collections.list(Enumeration) =====", System.out);
- final Enumeration properties = System.getProperties().propertyNames();
- final List propertiesList = Collections.list(properties);
- log(propertiesList.toString(), System.out);
- }
Results of demonstrateEnumerationToList()
The next code listing and its resulting screen snapshot demonstrate converting a Collection to an Enumeration.
demonstrateCollectionToEnumeration()
-
-
-
- public void demonstrateCollectionToEnumeration()
- {
- log("===== Demonstrate Collections.enumeration(Collection) =====", System.out);
- final Enumeration books = Collections.enumeration(this.favoriteBooks);
- while (books.hasMoreElements())
- {
- log(books.nextElement().toString(), System.out);
- }
- }
Results of demonstrateCollectionToEnumeration()
List Order Change-upsThe
Collections class supports randomly reordering a List [two versions of
Collections.shuffle method], reversing the order of a List [
Collections.reverse(List) method], and rotating entries in a list by a prescribed number of entries [
Collections.rotate(List, int) method]. Code samples and the associated screen snapshots for each of these follows.
The "shuffle" method is used to randomly reorder items in a List.
demonstrateShuffle()
-
-
-
- public void demonstrateShuffle()
- {
- log("===== Demonstrate Collections.shuffle(List) =====", System.out);
- log("Books BEFORE shuffle: " + NEW_LINE + this.favoriteBooks, System.out);
- Collections.shuffle(this.favoriteBooks);
- log("Books AFTER shuffle: " + NEW_LINE + this.favoriteBooks, System.out);
- }
Results of demonstrateShuffle()
The "reverse" method simply reverses the order of items in a List.
demonstrateReverseList()
-
-
-
- public void demonstrateReverseList()
- {
- log("===== Demonstrate Collections.reverse(List) =====", System.out);
- log("List BEFORE reverse:" + NEW_LINE + this.favoriteBooks, System.out);
- Collections.reverse(this.favoriteBooks);
- log("List AFTER reverse:" + NEW_LINE + this.favoriteBooks, System.out);
- }
Results of demonstrateReverseList()
The "rotate" method rotates elements in a List by the provided number of spots.
demonstrateRotate()
-
-
-
- public void demonstrateRotate()
- {
- log("===== Demonstrate Collections.rotate(List, int) =====", System.out);
- log("Books BEFORE rotation: " + NEW_LINE + this.favoriteBooks, System.out);
- Collections.rotate(this.favoriteBooks, 3);
- log("Books AFTER rotation: " + NEW_LINE + this.favoriteBooks, System.out);
- }
Results of demonstrateRotate()
So Many MoreThe
Collections class provides significantly more functionality than even that shown here. It includes methods that support collection wrappers that can be used in concurrent environments [such as
Collections.synchronizedCollection(Collection)], support filling a List with a particular item (
Collections.fill), support counting the number of times a particular object exists in a Collection (
Collections.frequency), sorting, searching, swapping, and several more types of functionality.
ConclusionThe
Collections class is one of the most valuable classes in the Java SDK. This blog posting has attempted to demonstrate some of its highly useful methods, but there are many more in addition to those shown here. Use of the
Collections class not only makes working with Java collections easier, but it also provides support for best practices related to Java collection use.