Wrapper Implementations
Wrapper implementations delegate all their real work to a specified collection but add extra functionality on top of what this collection offers. For design pattern fans, this is an example of thedecorator pattern. Although it may seem a bit exotic, it's really pretty straightforward.
These implementations are anonymous; rather than providing a public class, the library provides a static factory method.All these implementations are found in theCollections
class, which consists solely of static methods.
Synchronization Wrappers
The synchronization wrappers add automatic synchronization (thread-safety) to an arbitrary collection. Each of the six core collection interfaces —Collection
,Set
, List
, Map
, SortedSet
, and SortedMap
— has one static factory method.
public static <T> Collection<T> synchronizedCollection(Collection<T> c);
public static <T> Set<T> synchronizedSet(Set<T> s);
public static <T> List<T> synchronizedList(List<T> list);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s);
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m);
Each of these methods returns a synchronized (thread-safe)
Collection
backed up by the specified collection. To guarantee serial access,
all access to the backing collection must be accomplished through the returned collection. The easy way to guarantee this is not to keep a reference to the backing collection. Create the synchronized collection with the following trick.
List<Type> list = Collections.synchronizedList(new ArrayList<Type>());
A collection created in this fashion is every bit as thread-safe as a normally synchronized collection, such as aVector
.
In the face of concurrent access, it is imperative that the user manually synchronize on the returned collection when iterating over it. The reason is that iteration is accomplished via multiple calls into the collection, which must be composed into a single atomic operation. The following is the idiom to iterate over a wrapper-synchronized collection.
Collection<Type> c = Collections.synchronizedCollection(myCollection);
synchronized(c) {
for (Type e : c)
foo(e);
}
If an explicit iterator is used, the
iterator
method must be called from within the
synchronized
block. Failure to follow this advice may result in nondeterministic behavior. The idiom for iterating over a
Collection
view of a synchronized
Map
is similar.
It is imperative that the user synchronize on the synchronizedMap
when iterating over any of itsCollection
views rather than synchronizing on theCollection
view itself, as shown in the following example.
Map<KeyType, ValType> m = Collections.synchronizedMap(new HashMap<KeyType, ValType>());
...
Set<KeyType> s = m.keySet();
...
// Synchronizing on m, not s!
synchronized(m) {
while (KeyType k : s)
foo(k);
}
One minor downside of using wrapper implementations is that you do not have the ability to execute anynoninterface operations of a wrapped implementation. So, for instance, in the precedingList
example, you cannot callArrayList
's ensureCapacity
operation on the wrapped ArrayList
.
Unmodifiable Wrappers
Unlike synchronization wrappers, which add functionality to the wrapped collection, the unmodifiable wrappers take functionality away. In particular, they take away the ability to modify the collection by intercepting all the operations that would modify the collection and throwing an UnsupportedOperationException
. Unmodifiable wrappers have two main uses, as follows:
- To make a collection immutable once it has been built. In this case, it's good practice not to maintain a reference to the backing collection. This absolutely guarantees immutability.
- To allow certain clients read-only access to your data structures. You keep a reference to the backing collection but hand out a reference to the wrapper. In this way, clients can look but not modify, while you maintain full access.
Like synchronization wrappers, each of the six core Collection
interfaces has one static factory method.
public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c); public static <T> Set<T> unmodifiableSet(Set<? extends T> s); public static <T> List<T> unmodifiableList(List<? extends T> list); public static <K,V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m); public static <T> SortedSet<T> unmodifiableSortedSet(SortedSet<? extends T> s); public static <K,V> SortedMap<K, V> unmodifiableSortedMap(SortedMap<K, ? extends V> m);
Checked Interface Wrappers
The Collections.checked
interface wrappers are provided for use with generic collections. These implementations return adynamically type-safe view of the specified collection, which throws aClassCastException
if a client attempts to add an element of the wrong type. The generics mechanism in the language provides compile-time (static) type-checking, but it is possible to defeat this mechanism. Dynamically type-safe views eliminate this possibility entirely.
Convenience Implementations
This section describes several mini-implementations that can be more convenient and more efficient than general-purpose implementations when you don't need their full power.All the implementations in this section are made available via static factory methods rather thanpublic
classes.
List View of an Array
The Arrays.asList
method returns a List
view of its array argument. Changes to theList
write through to the array and vice versa. The size of the collection is that of the array and cannot be changed. If theadd
or the remove
method is called on the List
, anUnsupportedOperationException
will result.
The normal use of this implementation is as a bridge between array-based and collection-based APIs. It allows you to pass an array to a method expecting aCollection
or a List
. However, this implementation also has another use. If you need a fixed-sizeList
, it's more efficient than any general-purpose List
implementation. This is the idiom.
List<String> list = Arrays.asList(new String[size]);
Note that a reference to the backing array is not retained.
Immutable Multiple-Copy List
Occasionally you'll need an immutable List
consisting of multiple copies of the same element. TheCollections.nCopies
method returns such a list. This implementation has two main uses.The first is to initialize a newly created List
; for example, suppose you want an ArrayList
initially consisting of 1,000null
elements. The following incantation does the trick.
List<Type> list = new ArrayList<Type>(Collections.nCopies(1000, (Type)null);
Of course, the initial value of each element need not be
null
.
The second main use is to grow an existingList
. For example, suppose you want to add 69 copies of the string
"fruit bat"
to the end of a
List<String>
. It's not clear why you'd want to do such a thing, but let's just suppose you did. The following is how you'd do it.
lovablePets.addAll(Collections.nCopies(69, "fruit bat"));
By using the form of addAll
that takes both an index and a Collection
, you can add the new elements to the middle of a List
instead of to the end of it.
Immutable Singleton Set
Sometimes you'll need an immutable singleton Set
, which consists of a single, specified element. TheCollections.singleton
method returns such a Set
. One use of this implementation is to remove all occurrences of a specified element from aCollection
.
c.removeAll(Collections.singleton(e));
A related idiom removes all elements that map to a specified value from a
Map
. For example, suppose you have a
Map
—
job
— that maps people to their line of work and suppose you want to eliminate all the lawyers. The following one-liner will do the deed.
job.values().removeAll(Collections.singleton(LAWYER));
One more use of this implementation is to provide a single input value to a method that is written to accept a collection of values.
Empty Set, List, and Map Constants
The Collections
class provides methods to return the empty Set
,List
, and Map
— emptySet
, emptyList
, and emptyMap
. The main use of these constants is as input to methods that take aCollection
of values when you don't want to provide any values at all, as in this example.
tourist.declarePurchases(Collections.emptySet());
Summary of Implementations
Implementations are the data objects used to store collections, which implement the interfaces described in theInterfaces lesson.
The Java Collections Framework provides several general-purpose implementations of the core interfaces:
- For the
Set
interface,HashSet
is the most commonly used implementation. - For the
List
interface,ArrayList
is the most commonly used implementation. - For the
Map
interface,HashMap
is the most commonly used implementation. - For the
Queue
interface,LinkedList
is the most commonly used implementation. - For the
Deque
interface,ArrayDeque
is the most commonly used implementation.
Each of the general-purpose implementations provides all optional operations contained in its interface.
The Java Collections Framework also provides several special-purpose implementations for situations that require nonstandard performance, usage restrictions, or other unusual behavior.
The java.util.concurrent
package contains several collections implementations, which are thread-safe but not governed by a single exclusion lock.
The Collections
class (as opposed to the Collection
interface), provides static methods that operate on or return collections, which are known as Wrapper implementations.
Finally, there are several Convenience implementations, which can be more efficient than general-purpose implementations when you don't need their full power. The Convenience implementations are made available through static factory methods.
Answers to Questions and Exercises:
Questions
- Question: You plan to write a program that uses several basic collection interfaces:
Set
,List
,Queue
, andMap
. You're not sure which implementations will work best, so you decide to use general-purpose implementations until you get a better idea how your program will work in the real world. Which implementations are these?
Answer:
Set
:HashSet
List
:ArrayList
Queue
:LinkedList
Map
:HashMap
- Question: If you need a
Set
implementation that provides value-ordered iteration, which class should you use?
Answer:
TreeSet
guarantees that the sorted set is in ascending element order, sorted according to the natural order of the elements or by theComparator
provided. - Question: Which class do you use to access wrapper implementations?
Answer:
You use theCollections
class, which provides static methods that operate on or return collections.
Exercises
Exercise: Write a program that reads a text file, specified by the first command line argument, into aList
. The program should then print random lines from the file, the number of lines printed to be specified by the second command line argument. Write the program so that a correctly-sized collection is allocated all at once, instead of being gradually expanded as the file is read in. Hint: To determine the number of lines in the file, use
java.io.File.length
to obtain the size of the file, then divide by an assumed size of an average line.
Answer:
Since we are accessing the
List
randomly, we will use
ArrayList
. We estimate the number of lines by taking the file size and dividing by 50. We then double that figure, since it is more efficient to overestimate than to underestmate.
import java.util.*;
import java.io.*;
public class FileList {
public static void main(String[] args) {
final int assumedLineLength = 50;
File file = new File(args[0]);
List<String> fileList =
new ArrayList<String>((int)(file.length() / assumedLineLength) * 2);
BufferedReader reader = null;
int lineCount = 0;
try {
reader = new BufferedReader(new FileReader(file));
for (String line = reader.readLine(); line != null;
line = reader.readLine()) {
fileList.add(line);
lineCount++;
}
} catch (IOException e) {
System.err.format("Could not read %s: %s%n", file, e);
System.exit(1);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {}
}
}
int repeats = Integer.parseInt(args[1]);
Random random = new Random();
for (int i = 0; i < repeats; i++) {
System.out.format("%d: %s%n", i,
fileList.get(random.nextInt(lineCount - 1)));
}
}
}
This program actually spends most of its time reading in the file, so pre-allocating the
ArrayList
has little affect on its performance. Specifying an initial capacity in advance is more likely to be useful when your program repeatly creates large
ArrayList
objects without intervening I/O.
Original: http://docs.oracle.com/javase/tutorial/collections/implementations/wrapper.html