The Queue Interface
A Queue
is a collection for holding elements prior to processing. Besides basic Collection
operations, queues provide additional insertion, removal, and inspection operations. TheQueue
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 (eithernull
orfalse
, depending on the operation). The regular structure of the interface is illustrated inthe 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 theObject 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
orpoll
. In a FIFO queue, all new elements are inserted at the tail of the queue. Other kinds of queues may use different placement rules. EveryQueue
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 asbounded. SomeQueue
implementations in java.util.concurrent
are bounded, but the implementations injava.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 throwsIllegalStateException
.The offer
method, which is intended solely for use on bounded queues, differs fromadd
only in that it indicates failure to insert an element by returningfalse
.
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. Theremove
andpoll
methods differ in their behavior only when the queue is empty. Under these circumstances,remove
throwsNoSuchElementException
, while poll
returnsnull
.
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 asremove
andpoll
: If the queue is empty, element
throwsNoSuchElementException
, whilepeek
returns null
.
Queue
implementations generally do not allow insertion ofnull
elements. TheLinkedList
implementation, which was retrofitted to implementQueue
, is an exception. For historical reasons, it permitsnull
elements, but you should refrain from taking advantage of this, becausenull
is used as a special return value by the poll
and peek
methods.
Queue implementations generally do not define element-based versions of theequals
andhashCode
methods but instead inherit the identity-based versions fromObject
.
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 interfacejava.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;
}
The Deque Interface
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.TheDeque
interface is a richer abstract data type than bothStack
andQueue
because it implements both stacks and queues at the same time. TheDeque
interface, defines methods to access the elements at both ends of theDeque
instance. Methods are provided to insert, remove, and examine the elements. Predefined classes likeArrayDeque
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 theDeque
interface are divided into three parts:
Insert
The addfirst
and offerFirst
methods insert elements at the beginning of theDeque
instance. The methodsaddLast
and offerLast
insert elements at the end of theDeque
instance. When the capacity of theDeque
instance is restricted, the preferred methods areofferFirst
andofferLast
because addFirst
might fail to throw an exception if it is full.
Remove
The removeFirst
and pollFirst
methods remove elements from the beginning of theDeque
instance. TheremoveLast
and pollLast
methods remove elements from the end. The methodspollFirst
andpollLast
return null
if the Deque
is empty whereas the methodsremoveFirst
and removeLast
throw an exception if theDeque
instance is empty.
Retrieve
The methods getFirst
and peekFirst
retrieve the first element of theDeque
instance.These methods dont remove the value from theDeque
instance. Similarly, the methodsgetLast
and peekLast
retrieve the last element.The methodsgetFirst
and getLast
throw an exception if thedeque
instance is empty whereas the methodspeekFirst
andpeekLast
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, theDeque
interface also hassome more predefined methods. One of these isremoveFirstOccurence
, this method removes the first occurence of the specified element if it exists in theDeque
instance. If the element does not exist then theDeque
instance remains unchanged. Another similar method isremoveLastOccurence
; this method removes the last occurence of the specified element in theDeque
instance.The return type of these methods isboolean
, and they returntrue
if the element exists in theDeque
instance.
The Map Interface
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 mathematicalfunction abstraction. TheMap
interface includes methods for basic operations (such asput
,get
, remove
, containsKey
,containsValue
,size
, and empty
),bulk operations (such asputAll
andclear
), andcollection views (such as keySet
,entrySet
, andvalues
).
The Java platform contains three general-purposeMap
implementations:HashMap
, TreeMap
, and LinkedHashMap
. Their behavior and performance are precisely analogous toHashSet
,TreeSet
, and LinkedHashSet
, as described inThe Set Interface section.
The remainder of this page discusses the Map
interface in detail. But first, here are some more examples of collecting toMap
s using JDK 8 aggregate operations. Modeling real-world objects is a common task in object-oriented programming, so it is reasonable to think that some programs might, for example, group employees by department:
// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
Or compute the sum of all salaries by department:
// Compute sum of salaries by department
Map<Department, Integer> totalByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.summingInt(Employee::getSalary)));
Or perhaps group students by passing or failing grades:
// Partition students into passing and failing
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade()>= PASS_THRESHOLD));
You could also group people by city:
// Classify Person objects by city
Map<String, List<Person>> peopleByCity
= personStream.collect(Collectors.groupingBy(Person::getCity));
Or even cascade two collectors to classify people by state and city:
// Cascade Collectors
Map<String, Map<String, List<Person>>> peopleByStateAndCity
= personStream.collect(Collectors.groupingBy(Person::getState,
Collectors.groupingBy(Person::getCity)))
Again, these are but a few examples of how to use the new JDK 8 APIs. For in-depthcoverage of lambda expressions and aggregate operations see the lesson entitledAggregate Operations.
Map Interface Basic Operations
The basic operations of Map
(put
, get
,containsKey
,containsValue
, size
, and isEmpty
) behave exactly like their counterparts inHashtable
. Thefollowing program
generates a frequency table of the words found in its argument list. The frequency table maps each word to the number of times it occurs in the argument list.
import java.util.*;
public class Freq {
public static void main(String[] args) {
Map<String, Integer> m = new HashMap<String, Integer>();
// Initialize frequency table from command line
for (String a : args) {
Integer freq = m.get(a);
m.put(a, (freq == null) ? 1 : freq + 1);
}
System.out.println(m.size() + " distinct words:");
System.out.println(m);
}
}
The only tricky thing about this program is the second argument of the put
statement. That argument is a conditional expression that has the effect of setting the frequency to one if the word has never been seen before or one more than its current value if the word has already been seen. Try running this program with the command:
java Freq if it is to be it is up to me to delegate
The program yields the following output.
8 distinct words:
{to=3, delegate=1, be=1, it=2, up=1, if=1, me=1, is=2}
Suppose you'd prefer to see the frequency table in alphabetical order. All you have to do is change the implementation type of the
Map
from
HashMap
to
TreeMap
. Making this four-character change causes the program to generate the following output from the same command line.
8 distinct words:
{be=1, delegate=1, if=1, is=2, it=2, me=1, to=3, up=1}
Similarly, you could make the program print the frequency table in the order the words first appear on the command line simply by changing the implementation type of the map to
LinkedHashMap
. Doing so results in the following output.
8 distinct words:
{if=1, it=2, is=2, to=3, be=1, up=1, me=1, delegate=1}
This flexibility provides a potent illustration of the power of an interface-based framework.
Like the Set
and List
interfaces, Map
strengthens the requirements on theequals
andhashCode
methods so that two Map
objects can be compared for logical equality without regard to their implementation types.TwoMap
instances are equal if they represent the same key-value mappings.
By convention, all general-purpose Map
implementations provide constructors that take aMap
object and initialize the newMap
to contain all the key-value mappings in the specifiedMap
. This standardMap
conversion constructor is entirely analogous to the standardCollection
constructor: It allows the caller to create aMap
of a desired implementation type that initially contains all of the mappings in anotherMap
, regardless of the otherMap
's implementation type. For example, suppose you have aMap
, namedm
. The following one-liner creates a new HashMap
initially containing all of the same key-value mappings asm
.
Map<K, V> copy = new HashMap<K, V>(m);
Map Interface Bulk Operations
The clear
operation does exactly what you would think it could do: It removes all the mappings from theMap
.The putAll
operation is theMap
analogue of theCollection
interface's addAll
operation. In addition to its obvious use of dumping oneMap
into another, it has a second, more subtle use. Suppose aMap
is used to represent a collection of attribute-value pairs; the putAll
operation, in combination with the Map
conversion constructor, provides a neat way to implement attribute map creation with default values. The following is a static factory method that demonstrates this technique.
static <K, V> Map<K, V> newAttributeMap(Map<K, V>defaults, Map<K, V> overrides) {
Map<K, V> result = new HashMap<K, V>(defaults);
result.putAll(overrides);
return result;
}
Collection Views
The Collection
view methods allow a Map
to be viewed as aCollection
in these three ways:
keySet
— theSet
of keys contained in theMap
.values
— TheCollection
of values contained in theMap
. ThisCollection
is not aSet
, because multiple keys can map to the same value.entrySet
— theSet
of key-value pairs contained in theMap
. TheMap
interface provides a small nested interface calledMap.Entry
, the type of the elements in thisSet
.
The Collection
views provide the only means to iterate over aMap
. This example illustrates the standard idiom for iterating over the keys in aMap
with afor-each
construct:
for (KeyType key : m.keySet())
System.out.println(key);
and with an
iterator
:
// Filter a map based on some
// property of its keys.
for (Iterator<Type> it = m.keySet().iterator(); it.hasNext(); )
if (it.next().isBogus())
it.remove();
The idiom for iterating over values is analogous. Following is the idiom for iterating over key-value pairs.
for (Map.Entry<KeyType, ValType> e : m.entrySet())
System.out.println(e.getKey() + ": " + e.getValue());
At first, many people worry that these idioms may be slow because the Map
has to create a new Collection
instance each time a Collection
view operation is called. Rest easy: There's no reason that aMap
cannot always return the same object each time it is asked for a givenCollection
view. This is precisely what all the Map
implementations injava.util
do.
With all three Collection
views, calling an Iterator
'sremove
operation removes the associated entry from the backingMap
, assuming that the backing Map
supports element removal to begin with. This is illustrated by the preceding filtering idiom.
With the entrySet
view, it is also possible to change the value associated with a key by calling aMap.Entry
'ssetValue
method during iteration (again, assuming theMap
supports value modification to begin with).Note that these are the only safe ways to modify aMap
during iteration; the behavior is unspecified if the underlyingMap
is modified in any other way while the iteration is in progress.
The Collection
views support element removal in all its many forms —remove
,removeAll
, retainAll
, and clear
operations, as well as theIterator.remove
operation. (Yet again, this assumes that the backingMap
supports element removal.)
The Collection
views do not support element addition under any circumstances. It would make no sense for thekeySet
andvalues
views, and it's unnecessary for the entrySet
view, because the backingMap
's put
andputAll
methods provide the same functionality.
Fancy Uses of Collection Views: Map Algebra
When applied to the Collection
views, bulk operations (containsAll
,removeAll
, andretainAll
) are surprisingly potent tools. For starters, supposeyou want to know whether oneMap
is a submap of another — that is, whether the firstMap
contains all the key-value mappings in the second. The following idiom does the trick.
if (m1.entrySet().containsAll(m2.entrySet())) {
...
}
Along similar lines, suppose you want to know whether two
Map
objects contain mappings for all of the same keys.
if (m1.keySet().equals(m2.keySet())) {
...
}
Suppose you have a
Map
that represents a collection of attribute-value pairs, and two
Set
s representing required attributes and permissible attributes. (The permissible attributes include the required attributes.) The following snippet determines whether the attribute map conforms to these constraints and prints a detailed error message if it doesn't.
static <K, V> boolean validate(Map<K, V> attrMap, Set<K> requiredAttrs, Set<K>permittedAttrs) {
boolean valid = true;
Set<K> attrs = attrMap.keySet();
if (! attrs.containsAll(requiredAttrs)) {
Set<K> missing = new HashSet<K>(requiredAttrs);
missing.removeAll(attrs);
System.out.println("Missing attributes: " + missing);
valid = false;
}
if (! permittedAttrs.containsAll(attrs)) {
Set<K> illegal = new HashSet<K>(attrs);
illegal.removeAll(permittedAttrs);
System.out.println("Illegal attributes: " + illegal);
valid = false;
}
return valid;
}
Suppose you want to know all the keys common to two
Map
objects.
Set<KeyType> commonKeys = new HashSet<KeyType>(m1.keySet());
commonKeys.retainAll(m2.keySet());
A similar idiom gets you the common values.
All the idioms presented thus far have been nondestructive; that is, they don't modify the backingMap
. Here are a few that do. Suppose you want to remove all of the key-value pairs that oneMap
has in common with another.
m1.entrySet().removeAll(m2.entrySet());
Suppose you want to remove from one
Map
all of the keys that have mappings in another.
m1.keySet().removeAll(m2.keySet());
What happens when you start mixing keys and values in the same bulk operation? Suppose you have a
Map
,
managers
, that maps each employee in a company to the employee's manager. We'll be deliberately vague about the types of the key and the value objects. It doesn't matter, as long as they're the same. Now suppose you want to know who all the "individual contributors" (or nonmanagers) are. The following snippet tells you exactly what you want to know.
Set<Employee> individualContributors = new HashSet<Employee>(managers.keySet());
individualContributors.removeAll(managers.values());
Suppose you want to fire all the employees who report directly to some manager, Simon.
Employee simon = ... ;
managers.values().removeAll(Collections.singleton(simon));
Note that this idiom makes use of Collections.singleton
, a static factory method that returns an immutable Set
with the single, specified element.
Once you've done this, you may have a bunch of employees whose managers no longer work for the company (if any of Simon's direct-reports were themselves managers). The following code will tell you which employees have managers who no longer works for the company.
Map<Employee, Employee> m = new HashMap<Employee, Employee>(managers);
m.values().removeAll(managers.keySet());
Set<Employee> slackers = m.keySet();
This example is a bit tricky. First, it makes a temporary copy of the Map
, and it removes from the temporary copy all entries whose (manager) value is a key in the originalMap
. Remember that the originalMap
has an entry for each employee. Thus, the remaining entries in the temporaryMap
comprise all the entries from the originalMap
whose (manager) values are no longer employees. The keys in the temporary copy, then, represent precisely the employees that we're looking for.
There are many more idioms like the ones contained in this section, but it would be impractical and tedious to list them all. Once you get the hang of it, it's not that difficult to come up with the right one when you need it.
Multimaps
A multimap is like a Map
but it can map each key to multiple values. The Java Collections Framework doesn't include an interface for multimaps because they aren't used all that commonly.It's a fairly simple matter to use a Map
whose values areList
instances as a multimap. This technique is demonstrated in the next code example, which reads a word list containing one word per line (all lowercase) and prints out all the anagram groups that meet a size criterion. Ananagram group is a bunch of words, all of which contain exactly the same letters but in a different order. The program takes two arguments on the command line: (1) the name of the dictionary file and (2) the minimum size of anagram group to print out. Anagram groups containing fewer words than the specified minimum are not printed.
There is a standard trick for finding anagram groups: For each word in the dictionary, alphabetize the letters in the word (that is, reorder the word's letters into alphabetical order) and put an entry into a multimap, mapping the alphabetized word to the original word. For example, the word bad causes an entry mapping abd into bad to be put into the multimap. A moment's reflection will show that all the words to which any given key maps form an anagram group. It's a simple matter to iterate over the keys in the multimap, printing out each anagram group that meets the size constraint.
The following program
is a straightforward implementation of this technique.
import java.util.*;
import java.io.*;
public class Anagrams {
public static void main(String[] args) {
int minGroupSize = Integer.parseInt(args[1]);
// Read words from file and put into a simulated multimap
Map<String, List<String>> m = new HashMap<String, List<String>>();
try {
Scanner s = new Scanner(new File(args[0]));
while (s.hasNext()) {
String word = s.next();
String alpha = alphabetize(word);
List<String> l = m.get(alpha);
if (l == null)
m.put(alpha, l=new ArrayList<String>());
l.add(word);
}
} catch (IOException e) {
System.err.println(e);
System.exit(1);
}
// Print all permutation groups above size threshold
for (List<String> l : m.values())
if (l.size() >= minGroupSize)
System.out.println(l.size() + ": " + l);
}
private static String alphabetize(String s) {
char[] a = s.toCharArray();
Arrays.sort(a);
return new String(a);
}
}
Running this program on a 173,000-word dictionary file with a minimum anagram group size of eight produces the following output.
9: [estrin, inerts, insert, inters, niters, nitres, sinter,
triens, trines]
8: [lapse, leaps, pales, peals, pleas, salep, sepal, spale]
8: [aspers, parses, passer, prases, repass, spares, sparse,
spears]
10: [least, setal, slate, stale, steal, stela, taels, tales,
teals, tesla]
8: [enters, nester, renest, rentes, resent, tenser, ternes,
treens]
8: [arles, earls, lares, laser, lears, rales, reals, seral]
8: [earings, erasing, gainers, reagins, regains, reginas,
searing, seringa]
8: [peris, piers, pries, prise, ripes, speir, spier, spire]
12: [apers, apres, asper, pares, parse, pears, prase, presa,
rapes, reaps, spare, spear]
11: [alerts, alters, artels, estral, laster, ratels, salter,
slater, staler, stelar, talers]
9: [capers, crapes, escarp, pacers, parsec, recaps, scrape,
secpar, spacer]
9: [palest, palets, pastel, petals, plates, pleats, septal,
staple, tepals]
9: [anestri, antsier, nastier, ratines, retains, retinas,
retsina, stainer, stearin]
8: [ates, east, eats, etas, sate, seat, seta, teas]
8: [carets, cartes, caster, caters, crates, reacts, recast,
traces]
Many of these words seem a bit bogus, but that's not the program's fault; they're in the dictionary file. Here's the
dictionary file
we used.It was derived from the Public Domain ENABLE benchmark reference word list.
Orginal: http://docs.oracle.com/javase/tutorial/collections/interfaces/queue.html