您的位置:首页 > 编程语言 > Java开发

Java集合框架官方教程(5):集合类的同步包装器/不可变包装器

2016-07-29 00:00 579 查看

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 the
Collections
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 a
Vector
.

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 synchronized
Map
when iterating over any of its
Collection
views rather than synchronizing on the
Collection
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 preceding
List
example, you cannot call
ArrayList
'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 a
ClassCastException
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 than
public
classes.


List View of an Array

The
Arrays.asList
method returns a
List
view of its array argument. Changes to the
List
write through to the array and vice versa.The size of the collection is that of the array and cannot be changed. If the
add
or the
remove
method is called on the
List
, an
UnsupportedOperationException
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 a
Collection
or a
List
. However, this implementation also has another use. If you need a fixed-size
List
, 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. The
Collections.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,000
null
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 existing
List
.
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. The
Collections.singleton
method returns such a
Set
. One use of this implementation is to remove all occurrences of a specified element from a
Collection
.


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 a
Collection
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 JavaCollections 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 JavaCollections 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
, and
Map
. 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 the
Comparator
provided.


Question: Which class do you use to access wrapper implementations?
Answer:
You use the
Collections
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 a
List
. 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: