Interfaces
A Collection
represents a group of objects known as its elements. The Collection
interface is used to pass around collections of objects where maximum generality is desired. For example, by convention all general-purpose collection implementations have a constructor that takes a Collection
argument. This constructor, known as a conversion constructor, initializes the new collection to contain all of the elements in the specified collection, whatever the given collection's subinterface or implementation type. In other words, it allows you to convert the collection's type.
Suppose, for example, that you have a Collection<String> c
, which may be a List
, a Set
, or another kind of Collection
. This idiom creates a new ArrayList
(an implementation of the List
interface), initially containing all the elements in c
.
List<String> list = new ArrayList<String>(c);
List<String> list = new ArrayList<>(c);
The Collection
interface contains methods that perform basic operations, such as int size()
, boolean isEmpty()
, boolean contains(Object element)
, boolean add(E element)
, boolean remove(Object element)
, and Iterator<E> iterator()
.
It also contains methods that operate on entire collections, such as boolean containsAll(Collection<?> c)
, boolean addAll(Collection<? extends E> c)
, boolean removeAll(Collection<?> c)
, boolean retainAll(Collection<?> c)
, and void clear()
.
Additional methods for array operations (such as Object[] toArray()
and <T> T[] toArray(T[] a)
exist as well.
In JDK 8 and later, the Collection
interface also exposes methods Stream<E> stream()
and Stream<E> parallelStream()
, for obtaining sequential or parallel streams from the underlying collection. (See the lesson entitled Aggregate Operations for more information about using streams.)
The Collection
interface does about what you'd expect given that a Collection
represents a group of objects. It has methods that tell you how many elements are in the collection (size
, isEmpty
), methods that check whether a given object is in the collection (contains
), methods that add and remove an element from the collection (add
, remove
), and methods that provide an iterator over the collection (iterator
).
The add
method is defined generally enough so that it makes sense for collections that allow duplicates as well as those that don't. It guarantees that the Collection
will contain the specified element after the call completes, and returns true
if the Collection
changes as a result of the call. Similarly, the remove
method is designed to remove a single instance of the specified element from the Collection
, assuming that it contains the element to start with, and to return true
if the Collection
was modified as a result.
Traversing Collections
There are three ways to traverse collections: (1) using aggregate operations (2) with the for-each
construct and (3) by using Iterator
s.
Aggregate Operations
In JDK 8 and later, the preferred method of iterating over a collection is to obtain a stream and perform aggregate operations on it. Aggregate operations are often used in conjunction with lambda expressions to make programming more expressive, using less lines of code. The following code sequentially iterates through a collection of shapes and prints out the red objects:
myShapesCollection.stream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
Likewise, you could easily request a parallel stream, which might make sense if the collection is large enough and your computer has enough cores:
myShapesCollection.parallelStream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
There are many different ways to collect data with this API. For example, you might want to convert the elements of a Collection
to String
objects, then join them, separated by commas:
String joined = elements.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
Or perhaps sum the salaries of all employees:
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
These are but a few examples of what you can do with streams and aggregate operations. For more information and examples, see the lesson entitled Aggregate Operations.
The Collections framework has always provided a number of so-called "bulk operations" as part of its API. These include methods that operate on entire collections, such as containsAll
, addAll
, removeAll
, etc. Do not confuse those methods with the aggregate operations that were introduced in JDK 8. The key difference between the new aggregate operations and the existing bulk operations (containsAll
, addAll
, etc.) is that the old versions are all mutative, meaning that they all modify the underlying collection. In contrast, the new aggregate operations do not modify the underlying collection. When using the new aggregate operations and lambda expressions, you must take care to avoid mutation so as not to introduce problems in the future, should your code be run later from a parallel stream.
for-each Construct
The for-each
construct allows you to concisely traverse a collection or array using a for
loop — see The for Statement. The following code uses the for-each
construct to print out each element of a collection on a separate line.
for (Object o : collection)
System.out.println(o);
Iterator
An Iterator
is an object that enables you to traverse through a collection and to remove elements from the collection selectively, if desired. You get an Iterator
for a collection by calling its iterator
method. The following is the Iterator
interface.
public interface Iterator<E> {
boolean hasNext();
E next();
void remove(); //optional
}
The hasNext
method returns true
if the iteration has more elements, and the next
method returns the next element in the iteration. The remove
method removes the last element that was returned by next
from the underlying Collection
. The remove
method may be called only once per call to next
and throws an exception if this rule is violated.
Note that Iterator.remove
is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress.
Use Iterator
instead of the for-each
construct when you need to:
-
Remove the current element. The
for-each
construct hides the iterator, so you cannot callremove
. Therefore, thefor-each
construct is not usable for filtering. - Iterate over multiple collections in parallel.
The following method shows you how to use an Iterator
to filter an arbitrary Collection
— that is, traverse the collection removing specific elements.
static void filter(Collection<?> c) {
for (Iterator<?> it = c.iterator(); it.hasNext(); )
if (!cond(it.next()))
it.remove();
}
This simple piece of code is polymorphic, which means that it works for any Collection
regardless of implementation. This example demonstrates how easy it is to write a polymorphic algorithm using the Java Collections Framework.
Collection Interface Bulk Operations
Bulk operations perform an operation on an entire Collection
. You could implement these shorthand operations using the basic operations, though in most cases such implementations would be less efficient. The following are the bulk operations:
-
containsAll
— returnstrue
if the targetCollection
contains all of the elements in the specifiedCollection
. -
addAll
— adds all of the elements in the specifiedCollection
to the targetCollection
. -
removeAll
— removes from the targetCollection
all of its elements that are also contained in the specifiedCollection
. -
retainAll
— removes from the targetCollection
all its elements that are not also contained in the specifiedCollection
. That is, it retains only those elements in the targetCollection
that are also contained in the specifiedCollection
. -
clear
— removes all elements from theCollection
.
The addAll
, removeAll
, and retainAll
methods all return true
if the target Collection
was modified in the process of executing the operation.
As a simple example of the power of bulk operations, consider the following idiom to remove all instances of a specified element, e
, from a Collection
, c
.
c.removeAll(Collections.singleton(e));
More specifically, suppose you want to remove all of the null
elements from a Collection
.
c.removeAll(Collections.singleton(null));
This idiom uses Collections.singleton
, which is a static factory method that returns an immutable Set
containing only the specified element.
Collection Interface Array Operations
The toArray
methods are provided as a bridge between collections and older APIs that expect arrays on input. The array operations allow the contents of a Collection
to be translated into an array. The simple form with no arguments creates a new array of Object
. The more complex form allows the caller to provide an array or to choose the runtime type of the output array.
For example, suppose that c
is a Collection
. The following snippet dumps the contents of c
into a newly allocated array of Object
whose length is identical to the number of elements in c
.
Object[] a = c.toArray();
Suppose that c
is known to contain only strings (perhaps because c
is of type Collection<String>
). The following snippet dumps the contents of c
into a newly allocated array of String
whose length is identical to the number of elements in c
.
String[] a = c.toArray(new String[0]);
A Set
is a Collection
that cannot contain duplicate elements. It models the mathematical set abstraction. The Set
interface contains only methods inherited from Collection
and adds the restriction that duplicate elements are prohibited. Set
also adds a stronger contract on the behavior of the equals
and hashCode
operations, allowing Set
instances to be compared meaningfully even if their implementation types differ. Two Set
instances are equal if they contain the same elements.
The Java platform contains three general-purpose Set
implementations: HashSet
, TreeSet
, and LinkedHashSet
. HashSet
, which stores its elements in a hash table, is the best-performing implementation; however it makes no guarantees concerning the order of iteration. TreeSet
, which stores its elements in a red-black tree, orders its elements based on their values; it is substantially slower than HashSet
. LinkedHashSet
, which is implemented as a hash table with a linked list running through it, orders its elements based on the order in which they were inserted into the set (insertion-order). LinkedHashSet
spares its clients from the unspecified, generally chaotic ordering provided by HashSet
at a cost that is only slightly higher.
Here's a simple but useful Set
idiom. Suppose you have a Collection
, c
, and you want to create another Collection
containing the same elements but with all duplicates eliminated. The following one-liner does the trick.
Collection<Type> noDups = new HashSet<Type>(c);
It works by creating a Set
(which, by definition, cannot contain duplicates), initially containing all the elements in c
. It uses the standard conversion constructor described in the The Collection Interface section.
Or, if using JDK 8 or later, you could easily collect into a Set
using aggregate operations:
c.stream()
.collect(Collectors.toSet()); // no duplicates
Here's a slightly longer example that accumulates a Collection
of names into a TreeSet
:
Set<String> set = people.stream()
.map(Person::getName)
.collect(Collectors.toCollection(TreeSet::new));
And the following is a minor variant of the first idiom that preserves the order of the original collection while removing duplicate elements:
Collection<Type> noDups = new LinkedHashSet<Type>(c);
The following is a generic method that encapsulates the preceding idiom, returning a Set
of the same generic type as the one passed.
public static <E> Set<E> removeDups(Collection<E> c) {
return new LinkedHashSet<E>(c);
}
Set Interface Basic Operations
The size
operation returns the number of elements in the Set
(its cardinality). The isEmpty
method does exactly what you think it would. The add
method adds the specified element to the Set
if it is not already present and returns a boolean indicating whether the element was added. Similarly, the remove
method removes the specified element from the Set
if it is present and returns a boolean indicating whether the element was present. The iterator
method returns an Iterator
over the Set
.
The following program
prints out all distinct words in its argument list. Two versions of this program are provided. The first uses JDK 8 aggregate operations. The second uses the for-each construct.
Using JDK 8 Aggregate Operations:
import java.util.*;
import java.util.stream.*;
public class FindDups {
public static void main(String[] args) {
Set<String> distinctWords = Arrays.asList(args).stream()
.collect(Collectors.toSet());
System.out.println(distinctWords.size()+
" distinct words: " +
distinctWords);
}
}
Using the for-each
Construct:
import java.util.*;
public class FindDups {
public static void main(String[] args) {
Set<String> s = new HashSet<String>();
for (String a : args)
s.add(a);
System.out.println(s.size() + " distinct words: " + s);
}
}
Now run either version of the program.
java FindDups i came i saw i left
The following output is produced:
4 distinct words: [left, came, saw, i]
Note that the code always refers to the Collection
by its interface type (Set
) rather than by its implementation type. This is a strongly recommended programming practice because it gives you the flexibility to change implementations merely by changing the constructor. If either of the variables used to store a collection or the parameters used to pass it around are declared to be of the Collection
's implementation type rather than its interface type, all such variables and parameters must be changed in order to change its implementation type.
Furthermore, there's no guarantee that the resulting program will work. If the program uses any nonstandard operations present in the original implementation type but not in the new one, the program will fail. Referring to collections only by their interface prevents you from using any nonstandard operations.
The implementation type of the Set
in the preceding example is HashSet
, which makes no guarantees as to the order of the elements in the Set
. If you want the program to print the word list in alphabetical order, merely change the Set
's implementation type from HashSet
to TreeSet
. Making this trivial one-line change causes the command line in the previous example to generate the following output.
java FindDups i came i saw i left
4 distinct words: [came, i, left, saw]
Set Interface Bulk Operations
Bulk operations are particularly well suited to Set
s; when applied, they perform standard set-algebraic operations. Suppose s1
and s2
are sets. Here's what bulk operations do:
-
s1.containsAll(s2)
— returnstrue
ifs2
is a subset ofs1
. (s2
is a subset ofs1
if sets1
contains all of the elements ins2
.) -
s1.addAll(s2)
— transformss1
into the union ofs1
ands2
. (The union of two sets is the set containing all of the elements contained in either set.) -
s1.retainAll(s2)
— transformss1
into the intersection ofs1
ands2
. (The intersection of two sets is the set containing only the elements common to both sets.) -
s1.removeAll(s2)
— transformss1
into the (asymmetric) set difference ofs1
ands2
. (For example, the set difference ofs1
minuss2
is the set containing all of the elements found ins1
but not ins2
.)
To calculate the union, intersection, or set difference of two sets nondestructively (without modifying either set), the caller must copy one set before calling the appropriate bulk operation. The following are the resulting idioms.
Set<Type> union = new HashSet<Type>(s1);
union.addAll(s2);
Set<Type> intersection = new HashSet<Type>(s1);
intersection.retainAll(s2);
Set<Type> difference = new HashSet<Type>(s1);
difference.removeAll(s2);
The implementation type of the result Set
in the preceding idioms is HashSet
, which is, as already mentioned, the best all-around Set
implementation in the Java platform. However, any general-purpose Set
implementation could be substituted.
Let's revisit the FindDups
program. Suppose you want to know which words in the argument list occur only once and which occur more than once, but you do not want any duplicates printed out repeatedly. This effect can be achieved by generating two sets — one containing every word in the argument list and the other containing only the duplicates. The words that occur only once are the set difference of these two sets, which we know how to compute. Here's how the resulting program
looks.
import java.util.*;
public class FindDups2 {
public static void main(String[] args) {
Set<String> uniques = new HashSet<String>();
Set<String> dups = new HashSet<String>();
for (String a : args)
if (!uniques.add(a))
dups.add(a);
// Destructive set-difference
uniques.removeAll(dups);
System.out.println("Unique words: " + uniques);
System.out.println("Duplicate words: " + dups);
}
}
When run with the same argument list used earlier (i came i saw i left
), the program yields the following output.
Unique words: [left, saw, came]
Duplicate words: [i]
A less common set-algebraic operation is the symmetric set difference — the set of elements contained in either of two specified sets but not in both. The following code calculates the symmetric set difference of two sets nondestructively.
Set<Type> symmetricDiff = new HashSet<Type>(s1);
symmetricDiff.addAll(s2);
Set<Type> tmp = new HashSet<Type>(s1);
tmp.retainAll(s2);
symmetricDiff.removeAll(tmp);
Set Interface Array Operations
The array operations don't do anything special for Set
s beyond what they do for any other Collection
. These operations are described in The Collection Interface section.
A List
is an ordered Collection
(sometimes called a sequence). Lists may contain duplicate elements. In addition to the operations inherited from Collection
, the List
interface includes operations for the following:
-
Positional access
— manipulates elements based on their numerical position in the list. This includes methods such asget
,set
,add
,addAll
, andremove
. -
Search
— searches for a specified object in the list and returns its numerical position. Search methods includeindexOf
andlastIndexOf
. -
Iteration
— extendsIterator
semantics to take advantage of the list's sequential nature. ThelistIterator
methods provide this behavior. -
Range-view
— Thesublist
method performs arbitrary range operations on the list.
The Java platform contains two general-purpose List
implementations. ArrayList
, which is usually the better-performing implementation, and LinkedList
which offers better performance under certain circumstances.
Collection Operations
The operations inherited from Collection
all do about what you'd expect them to do, assuming you're already familiar with them. If you're not familiar with them from Collection
, now would be a good time to read The Collection Interface section. The remove
operation always removes the first occurrence of the specified element from the list. The add
and addAll
operations always append the new element(s) to the end of the list. Thus, the following idiom concatenates one list to another.
list1.addAll(list2);
Here's a nondestructive form of this idiom, which produces a third List
consisting of the second list appended to the first.
List<Type> list3 = new ArrayList<Type>(list1);
list3.addAll(list2);
Note that the idiom, in its nondestructive form, takes advantage of ArrayList
's standard conversion constructor.
And here's an example (JDK 8 and later) that aggregates some names into a List
:
List<String> list = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
Like the Set
interface, List
strengthens the requirements on the equals
and hashCode
methods so that two List
objects can be compared for logical equality without regard to their implementation classes. Two List
objects are equal if they contain the same elements in the same order.
Positional Access and Search Operations
The basic positional access
operations are get
, set
, add
and remove
. (The set
and remove
operations return the old value that is being overwritten or removed.) Other operations (indexOf
and lastIndexOf
) return the first or last index of the specified element in the list.
The addAll
operation inserts all the elements of the specified Collection
starting at the specified position. The elements are inserted in the order they are returned by the specified Collection
's iterator. This call is the positional access analog of Collection
's addAll
operation.
Here's a little method to swap two indexed values in a List
.
public static <E> void swap(List<E> a, int i, int j) {
E tmp = a.get(i);
a.set(i, a.get(j));
a.set(j, tmp);
}
This is a polymorphic algorithm: It swaps two elements in any List
, regardless of its implementation type. Here's another polymorphic algorithm that uses the preceding swap
method.
public static void shuffle(List<?> list, Random rnd) {
for (int i = list.size(); i > 1; i--)
swap(list, i - 1, rnd.nextInt(i));
}
This algorithm, which is included in the Java platform's Collections
class, randomly permutes the specified list using the specified source of randomness. It's a bit subtle: It runs up the list from the bottom, repeatedly swapping a randomly selected element into the current position. Unlike most naive attempts at shuffling, it's fair (all permutations occur with equal likelihood, assuming an unbiased source of randomness) and fast (requiring exactly list.size()-1
swaps). The following program uses this algorithm to print the words in its argument list in random order.
import java.util.*;
public class Shuffle {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (String a : args)
list.add(a);
Collections.shuffle(list, new Random());
System.out.println(list);
}
}
In fact, this program can be made even shorter and faster. The Arrays
class has a static factory method called asList
, which allows an array to be viewed as a List
. This method does not copy the array. Changes in the List
write through to the array and vice versa. The resulting List is not a general-purpose List
implementation, because it doesn't implement the (optional) add
and remove
operations: Arrays are not resizable. Taking advantage of Arrays.asList
and calling the library version of shuffle
, which uses a default source of randomness, you get the following tiny program
whose behavior is identical to the previous program.
import java.util.*;
public class Shuffle {
public static void main(String[] args) {
List<String> list = Arrays.asList(args);
Collections.shuffle(list);
System.out.println(list);
}
}
Iterators
As you'd expect, the Iterator
returned by List
's iterator
operation returns the elements of the list in proper sequence. List
also provides a richer iterator, called a ListIterator
, which allows you to traverse the list in either direction, modify the list during iteration, and obtain the current position of the iterator.
The three methods that ListIterator
inherits from Iterator
(hasNext
, next
, and remove
) do exactly the same thing in both interfaces. The hasPrevious
and the previous
operations are exact analogues of hasNext
and next
. The former operations refer to the element before the (implicit) cursor, whereas the latter refer to the element after the cursor. The previous
operation moves the cursor backward, whereas next
moves it forward.
Here's the standard idiom for iterating backward through a list.
for (ListIterator<Type> it = list.listIterator(list.size()); it.hasPrevious(); ) {
Type t = it.previous();
...
}
Note the argument to listIterator
in the preceding idiom. The List
interface has two forms of the listIterator
method. The form with no arguments returns a ListIterator
positioned at the beginning of the list; the form with an int
argument returns a ListIterator
positioned at the specified index. The index refers to the element that would be returned by an initial call to next
. An initial call to previous
would return the element whose index was index-1
. In a list of length n
, there are n+1
valid values for index
, from 0
to n
, inclusive.
Intuitively speaking, the cursor is always between two elements — the one that would be returned by a call to previous
and the one that would be returned by a call to next
. The n+1
valid index
values correspond to the n+1
gaps between elements, from the gap before the first element to the gap after the last one. The following figure shows the five possible cursor positions in a list containing four elements.
Calls to next
and previous
can be intermixed, but you have to be a bit careful. The first call to previous
returns the same element as the last call to next
. Similarly, the first call to next
after a sequence of calls to previous
returns the same element as the last call to previous
.
It should come as no surprise that the nextIndex
method returns the index of the element that would be returned by a subsequent call to next
, and previousIndex
returns the index of the element that would be returned by a subsequent call to previous
. These calls are typically used either to report the position where something was found or to record the position of the ListIterator
so that another ListIterator
with identical position can be created.
It should also come as no surprise that the number returned by nextIndex
is always one greater than the number returned by previousIndex
. This implies the behavior of the two boundary cases: (1) a call to previousIndex
when the cursor is before the initial element returns -1
and (2) a call to nextIndex
when the cursor is after the final element returns list.size()
. To make all this concrete, the following is a possible implementation of List.indexOf
.
public int indexOf(E e) {
for (ListIterator<E> it = listIterator(); it.hasNext(); )
if (e == null ? it.next() == null : e.equals(it.next()))
return it.previousIndex();
// Element not found
return -1;
}
Note that the indexOf
method returns it.previousIndex()
even though it is traversing the list in the forward direction. The reason is that it.nextIndex()
would return the index of the element we are about to examine, and we want to return the index of the element we just examined.
The Iterator
interface provides the remove
operation to remove the last element returned by next
from the Collection
. For ListIterator
, this operation removes the last element returned by next
or previous
. The ListIterator
interface provides two additional operations to modify the list — set
and add
. The set
method overwrites the last element returned by next
or previous
with the specified element. The following polymorphic algorithm uses set
to replace all occurrences of one specified value with another.
public static <E> void replace(List<E> list, E val, E newVal) {
for (ListIterator<E> it = list.listIterator(); it.hasNext(); )
if (val == null ? it.next() == null : val.equals(it.next()))
it.set(newVal);
}
The only bit of trickiness in this example is the equality test between val
and it.next
. You need to special-case a val
value of null
to prevent a NullPointerException
.
The add
method inserts a new element into the list immediately before the current cursor position. This method is illustrated in the following polymorphic algorithm to replace all occurrences of a specified value with the sequence of values contained in the specified list.
public static <E>
void replace(List<E> list, E val, List<? extends E> newVals) {
for (ListIterator<E> it = list.listIterator(); it.hasNext(); ){
if (val == null ? it.next() == null : val.equals(it.next())) {
it.remove();
for (E e : newVals)
it.add(e);
}
}
}
Range-View Operation
The range-view
operation, subList(int fromIndex, int toIndex)
, returns a List
view of the portion of this list whose indices range from fromIndex
, inclusive, to toIndex
, exclusive. This half-open range mirrors the typical for
loop.
for (int i = fromIndex; i < toIndex; i++) {
...
}
As the term view implies, the returned List
is backed up by the List
on which subList
was called, so changes in the former are reflected in the latter.
This method eliminates the need for explicit range operations (of the sort that commonly exist for arrays). Any operation that expects a List
can be used as a range operation by passing a subList
view instead of a whole List
. For example, the following idiom removes a range of elements from a List
.
list.subList(fromIndex, toIndex).clear();
Similar idioms can be constructed to search for an element in a range.
int i = list.subList(fromIndex, toIndex).indexOf(o);
int j = list.subList(fromIndex, toIndex).lastIndexOf(o);
Note that the preceding idioms return the index of the found element in the subList
, not the index in the backing List
.
Any polymorphic algorithm that operates on a List
, such as the replace
and shuffle
examples, works with the List
returned by subList
.
Here's a polymorphic algorithm whose implementation uses subList
to deal a hand from a deck. That is, it returns a new List
(the "hand") containing the specified number of elements taken from the end of the specified List
(the "deck"). The elements returned in the hand are removed from the deck.
public static <E> List<E> dealHand(List<E> deck, int n) {
int deckSize = deck.size();
List<E> handView = deck.subList(deckSize - n, deckSize);
List<E> hand = new ArrayList<E>(handView);
handView.clear();
return hand;
}
Note that this algorithm removes the hand from the end of the deck. For many common List
implementations, such as ArrayList
, the performance of removing elements from the end of the list is substantially better than that of removing elements from the beginning.
The following is a program
that uses the dealHand
method in combination with Collections.shuffle
to generate hands from a normal 52-card deck. The program takes two command-line arguments: (1) the number of hands to deal and (2) the number of cards in each hand.
import java.util.*;
public class Deal {
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: Deal hands cards");
return;
}
int numHands = Integer.parseInt(args[0]);
int cardsPerHand = Integer.parseInt(args[1]);
// Make a normal 52-card deck.
String[] suit = new String[] {
"spades", "hearts",
"diamonds", "clubs"
};
String[] rank = new String[] {
"ace", "2", "3", "4",
"5", "6", "7", "8", "9", "10",
"jack", "queen", "king"
};
List<String> deck = new ArrayList<String>();
for (int i = 0; i < suit.length; i++)
for (int j = 0; j < rank.length; j++)
deck.add(rank[j] + " of " + suit[i]);
// Shuffle the deck.
Collections.shuffle(deck);
if (numHands * cardsPerHand > deck.size()) {
System.out.println("Not enough cards.");
return;
}
for (int i = 0; i < numHands; i++)
System.out.println(dealHand(deck, cardsPerHand));
}
public static <E> List<E> dealHand(List<E> deck, int n) {
int deckSize = deck.size();
List<E> handView = deck.subList(deckSize - n, deckSize);
List<E> hand = new ArrayList<E>(handView);
handView.clear();
return hand;
}
}
Running the program produces output like the following.
% java Deal 4 5
[8 of hearts, jack of spades, 3 of spades, 4 of spades,
king of diamonds]
[4 of diamonds, ace of clubs, 6 of clubs, jack of hearts,
queen of hearts]
[7 of spades, 5 of spades, 2 of diamonds, queen of diamonds,
9 of clubs]
[8 of spades, 6 of diamonds, ace of spades, 3 of hearts,
ace of hearts]
Although the subList
operation is extremely powerful, some care must be exercised when using it. The semantics of the List
returned by subList
become undefined if elements are added to or removed from the backing List
in any way other than via the returned List
. Thus, it's highly recommended that you use the List
returned by subList
only as a transient object — to perform one or a sequence of range operations on the backing List
. The longer you use the subList
instance, the greater the probability that you'll compromise it by modifying the backing List
directly or through another subList
object. Note that it is legal to modify a sublist of a sublist and to continue using the original sublist (though not concurrently).
List Algorithms
Most polymorphic algorithms in the Collections
class apply specifically to List
. Having all these algorithms at your disposal makes it very easy to manipulate lists. Here's a summary of these algorithms, which are described in more detail in the Algorithms section.
-
sort
— sorts aList
using a merge sort algorithm, which provides a fast, stable sort. (A stable sort is one that does not reorder equal elements.) -
shuffle
— randomly permutes the elements in aList
. -
reverse
— reverses the order of the elements in aList
. -
rotate
— rotates all the elements in aList
by a specified distance. -
swap
— swaps the elements at specified positions in aList
. -
replaceAll
— replaces all occurrences of one specified value with another. -
fill
— overwrites every element in aList
with the specified value. -
copy
— copies the sourceList
into the destinationList
. -
binarySearch
— searches for an element in an orderedList
using the binary search algorithm. -
indexOfSubList
— returns the index of the first sublist of oneList
that is equal to another. -
lastIndexOfSubList
— returns the index of the last sublist of oneList
that is equal to another.
A Queue
is a collection for holding elements prior to processing. Besides basic Collection
operations, queues provide additional insertion, removal, and inspection operations. The Queue
interface follows.
public interface Queue<E> extends Collection<E> {
E element();
boolean offer(E e);
E peek();
E poll();
E remove();
}
Each Queue
method exists in two forms: (1) one throws an exception if the operation fails, and (2) the other returns a special value if the operation fails (either null
or false
, depending on the operation). The regular structure of the interface is illustrated in the following table.
Type of Operation | Throws exception | Returns special value |
---|---|---|
Insert |
add(e) |
offer(e) |
Remove |
remove() |
poll() |
Examine |
element() |
peek() |
Queues typically, but not necessarily, order elements in a FIFO (first-in-first-out) manner. Among the exceptions are priority queues, which order elements according to their values — see the Object Ordering section for details). Whatever ordering is used, the head of the queue is the element that would be removed by a call to remove
or poll
. In a FIFO queue, all new elements are inserted at the tail of the queue. Other kinds of queues may use different placement rules. Every Queue
implementation must specify its ordering properties.
It is possible for a Queue
implementation to restrict the number of elements that it holds; such queues are known as bounded. Some Queue
implementations in java.util.concurrent
are bounded, but the implementations in java.util
are not.
The add
method, which Queue
inherits from Collection
, inserts an element unless it would violate the queue's capacity restrictions, in which case it throws IllegalStateException
. The offer
method, which is intended solely for use on bounded queues, differs from add
only in that it indicates failure to insert an element by returning false
.
The remove
and poll
methods both remove and return the head of the queue. Exactly which element gets removed is a function of the queue's ordering policy. The remove
and poll
methods differ in their behavior only when the queue is empty. Under these circumstances, remove
throws NoSuchElementException
, while poll
returns null
.
The element
and peek
methods return, but do not remove, the head of the queue. They differ from one another in precisely the same fashion as remove
and poll
: If the queue is empty, element
throws NoSuchElementException
, while peek
returns null
.
Queue
implementations generally do not allow insertion of null
elements. The LinkedList
implementation, which was retrofitted to implement Queue
, is an exception. For historical reasons, it permits null
elements, but you should refrain from taking advantage of this, because null
is used as a special return value by the poll
and peek
methods.
Queue implementations generally do not define element-based versions of the equals
and hashCode
methods but instead inherit the identity-based versions from Object
.
The Queue
interface does not define the blocking queue methods, which are common in concurrent programming. These methods, which wait for elements to appear or for space to become available, are defined in the interface java.util.concurrent.BlockingQueue
, which extends Queue
.
In the following example program, a queue is used to implement a countdown timer. The queue is preloaded with all the integer values from a number specified on the command line to zero, in descending order. Then, the values are removed from the queue and printed at one-second intervals. The program is artificial in that it would be more natural to do the same thing without using a queue, but it illustrates the use of a queue to store elements prior to subsequent processing.
import java.util.*;
public class Countdown {
public static void main(String[] args) throws InterruptedException {
int time = Integer.parseInt(args[0]);
Queue<Integer> queue = new LinkedList<Integer>();
for (int i = time; i >= 0; i--)
queue.add(i);
while (!queue.isEmpty()) {
System.out.println(queue.remove());
Thread.sleep(1000);
}
}
}
In the following example, a priority queue is used to sort a collection of elements. Again this program is artificial in that there is no reason to use it in favor of the sort
method provided in Collections
, but it illustrates the behavior of priority queues.
static <E> List<E> heapSort(Collection<E> c) {
Queue<E> queue = new PriorityQueue<E>(c);
List<E> result = new ArrayList<E>();
while (!queue.isEmpty())
result.add(queue.remove());
return result;
}
Usually pronounced as deck
, a deque is a double-ended-queue. A double-ended-queue is a linear collection of elements that supports the insertion and removal of elements at both end points. The Deque
interface is a richer abstract data type than both Stack
and Queue
because it implements both stacks and queues at the same time. The Deque
interface, defines methods to access the elements at both ends of the Deque
instance. Methods are provided to insert, remove, and examine the elements. Predefined classes like ArrayDeque
and LinkedList
implement the Deque
interface.
Note that the Deque
interface can be used both as last-in-first-out stacks and first-in-first-out queues. The methods given in the Deque
interface are divided into three parts:
Insert
The addfirst
and offerFirst
methods insert elements at the beginning of the Deque
instance. The methods addLast
and offerLast
insert elements at the end of the Deque
instance. When the capacity of the Deque
instance is restricted, the preferred methods are offerFirst
and offerLast
because addFirst
might fail to throw an exception if it is full.
Remove
The removeFirst
and pollFirst
methods remove elements from the beginning of the Deque
instance. The removeLast
and pollLast
methods remove elements from the end. The methods pollFirst
and pollLast
return null
if the Deque
is empty whereas the methods removeFirst
and removeLast
throw an exception if the Deque
instance is empty.
Retrieve
The methods getFirst
and peekFirst
retrieve the first element of the Deque
instance. These methods dont remove the value from the Deque
instance. Similarly, the methods getLast
and peekLast
retrieve the last element. The methods getFirst
and getLast
throw an exception if the deque
instance is empty whereas the methods peekFirst
and peekLast
return NULL
.
The 12 methods for insertion, removal and retieval of Deque elements are summarized in the following table:
Type of Operation |
First Element (Beginning of the Deque instance) |
Last Element (End of the Deque instance) |
---|---|---|
Insert |
addFirst(e) offerFirst(e) |
addLast(e) offerLast(e) |
Remove |
removeFirst() pollFirst() |
removeLast() pollLast() |
Examine |
getFirst() peekFirst() |
getLast() peekLast() |
In addition to these basic methods to insert,remove and examine a Deque
instance, the Deque
interface also has some more predefined methods. One of these is removeFirstOccurence
, this method removes the first occurence of the specified element if it exists in the Deque
instance. If the element does not exist then the Deque
instance remains unchanged. Another similar method is removeLastOccurence
; this method removes the last occurence of the specified element in the Deque
instance. The return type of these methods is boolean
, and they return true
if the element exists in the Deque
instance.
A Map
is an object that maps keys to values. A map cannot contain duplicate keys: Each key can map to at most one value. It models the mathematical function abstraction. The Map
interface includes methods for basic operation