Chapter 4—Statement Forms - Stanford Computer Science

advertisement
The Art and Science of
CHAPTER 13
Collection Classes
I think this is the most extraordinary collection of talent, of human
knowledge, that has ever been gathered at the White House—with
the possible exception of when Thomas Jefferson dined alone.
—John F. Kennedy, dinner for Nobel laureates, 1962
13.1
13.2
13.3
13.4
The ArrayList class revisited
The HashMap class
The Java Collections Framework
Principles of object-oriented design
ERIC S. ROBERTS
Java
An Introduction
to Computer Science
The ArrayList Class Revisited
• You have already seen the ArrayList class in section
Chapter 11.8, which included examples showing how to use
ArrayLists in both the pre- and post-Java 5.0 worlds. The
purpose of section 13.1 is to look at the idea behind the
ArrayList class from a more general perspective that paves
the way for a discussion of the Java Collection Framework.
• The most obvious difference between the ArrayList class
and Java’s array facility is that ArrayList is a full-fledged
Java class. As such, the ArrayList class can support more
sophisticated operations than arrays can. All of the operations
that pertain to arrays must be built into the language; the
operations that apply to the ArrayList class, by contrast,
can be provided by extension.
The Power of Dynamic Allocation
• Much of the added power of the ArrayList class comes
from the fact that ArrayLists allow you to expand the list of
values dynamically. The class also contains methods that add
and remove elements from any position in the list; no such
operations are available for arrays.
• The extra flexibility offered by the ArrayList class can
reduce the complexity of programs substantially. As an
example, the next few slides show two versions of the code
for the readLineArray method, which reads and returns an
array of lines from a reader.
– The first version is the one that appears in section 12.4 and uses
the ArrayList class, adding each line as it appears.
– The second version uses only arrays in the implementation and
therefore has to allocate space as the program reads additional
lines from the reader. In this implementation, the code doubles
the size of the internal array each time it runs out of space.
readLineArray (ArrayList version)
/*
* Reads all available lines from the specified reader and returns an array
* containing those lines. This method closes the reader at the end of the
* file.
*/
private String[] readLineArray(BufferedReader rd) {
ArrayList<String> lineList = new ArrayList<String>();
try {
while (true) {
String line = rd.readLine();
if (line == null) break;
lineList.add(line);
}
rd.close();
} catch (IOException ex) {
throw new ErrorException(ex);
}
String[] result = new String[lineList.size()];
for (int i = 0; i < result.length; i++) {
result[i] = lineList.get(i);
}
return result;
}
readLineArray (array version)
/*
* Reads all available lines from the specified reader and returns an array
* containing those lines. This method closes the reader at the end of the
* file.
*/
private String[] readLineArray(BufferedReader rd) {
String[] lineArray = new String[INITIAL_CAPACITY];
int nLines = 0;
try {
while (true) {
String line = rd.readLine();
if (line == null) break;
if (nLines + 1>= lineArray.length) {
lineArray = doubleArrayCapacity(lineArray);
}
lineArray[nLines++] = line;
}
rd.close();
} catch (IOException ex) {
throw new ErrorException(ex);
}
String[] result = new String[nLines];
for (int i = 0; i < nLines; i++) {
result[i] = lineArray[i];
}
return result;
}
page 1 of 2
skip code
readLineArray (array version)
/*
/*
** Creates
Reads all
a string
available
array
lines
withfrom
twice
theasspecified
many elements
readerasand
thereturns
old array
an array
and
** then
containing
copies those
the existing
lines. elements
This method
fromcloses
the old
thearray
reader
to at
thethe
newend
one.
of the
*/
* file.
*/private String[] doubleArrayCapacity(String[] oldArray) {
private
String[]
String[]
newArray
readLineArray(BufferedReader
= new String[2 * oldArray.length];
rd) {
for
String[]
(int ilineArray
= 0; i < =oldArray.length;
new String[INITIAL_CAPACITY];
i++) {
intnewArray[i]
nLines = 0;= oldArray[i];
}try {
return
while
newArray;
(true) {
}
String line = rd.readLine();
if (line == null) break;
/* Private constants
if (nLines
*/ + 1>= lineArray.length) {
private static
lineArray
final int
= doubleArrayCapacity(lineArray);
INITIAL_CAPACITY = 10;
}
lineArray[nLines++] = line;
}
rd.close();
} catch (IOException ex) {
throw new ErrorException(ex);
}
String[] result = new String[nLines];
for (int i = 0; i < nLines; i++) {
result[i] = lineArray[i];
}
return result;
}
page 2 of 2
skip code
The HashMap Class
• The HashMap class is one of the most valuable tools exported
by the java.util package and comes up in a surprising
number of applications.
• The HashMap class implements the abstract idea of a map,
which is an associative relationship between keys and values.
A key is an object that never appears more than once in a map
and can therefore be used to identify a value, which is the
object associated with a particular key.
• Although the HashMap class exports other methods as well,
the essential operations on a HashMap are the ones listed in
the following table:
new HashMap( )
map.put(key, value)
map.get(key)
Creates a new HashMap object that is initially empty.
Sets the association for key in the map to value.
Returns the value associated with key, or null if none.
Generic Types for Keys and Values
• Beginning with Java Standard Edition 5.0, the HashMap class
is a generic type that allows clients to specify the types of the
keys and values.
• As with the ArrayList class introduced in Chapter 11, the
type information is written in angle brackets after the class
name. The only difference is that a HashMap requires two
type parameters: one for the key and one for the value. For
example, the type designation HashMap<String,Integer>
indicates a HashMap that uses strings as keys to obtain
integer values.
• In versions that predate Java 5.0, the HashMap class uses
Object as the type for both keys and values. This definition
makes it possible to use any object type as a key or value, but
often means that you need a type cast to convert the result of a
HashMap method to the intended type.
A Simple HashMap Application
• Suppose that you want to write a program that displays the
name of a state given its two-letter postal abbreviation.
• This program is an ideal application for the HashMap class
because what you need is a map between two-letter codes and
state names. Each two-letter code uniquely identifies a
particular state and therefore serves as a key for the
HashMap; the state names are the corresponding values.
• To implement this program in Java, you need to perform the
following steps, which are illustrated on the following slide:
1.
2.
3.
4.
Create a HashMap containing all 50 key/value pairs.
Read in the two-letter abbreviation to translate.
Call get on the HashMap to find the state name.
Print out the name of the state.
The PostalLookup Application
public void run() {
HashMap<String,String> stateMap = new HashMap<String,String>();
private void initStateMap(HashMap<String,String> map) {
initStateMap(stateMap);
map.put("AL", "Alabama");
while (true) {
map.put("AK", "Alaska");
String code = readLine("Enter two-letter state abbreviation: ");
map.put("AZ", "Arizona");
if (code.length()
== 0) break;
...
String state = stateMap.get(code);
map.put("FL", "Florida");
if (state == null) {
map.put("GA", "Georgia");
println(code + " is not a known state abbreviation");
map.put("HI", "Hawaii");
} else { . . .
println(code + " is " + state);
map.put("WI", "Wisconsin");
state
code
stateMap
}
map.put("WY", "Wyoming");
map
}
Hawaii
null
VE
WI
HI
Wisconsin
}
}
PostalLookup
Enter
HI is
Enter
WI is
Enter
VE is
Enter
two-letter state abbreviation: HI
Hawaii
two-letter state abbreviation: WI
Wisconsin
two-letter state abbreviation: VE
not a known state abbreviation
two-letter state abbreviation:
AL=Alabama
AK=Alaska
AZ=Arizona
...
FL=Florida
GA=Georgia
HI=Hawaii
...
WI=Wisconsin
WY=Wyoming
skip simulation
Implementation Strategies for Maps
There are several strategies you might choose to implement the
map operations get and put. Those strategies include:
1. Linear search in parallel arrays. Keep the two-character codes in
one array and the state names in a second, making sure that the
index numbers of the code and its corresponding state name always
match. Such structures are called parallel arrays. You can use
linear search to find the two-letter code and then take the state name
from that position in the other array. This strategy takes O(N) time.
2. Binary search in parallel arrays. If you keep the arrays sorted by
the two-character code, you can use binary search to find the key.
Using this strategy improves the performance to O(log N).
3. Table lookup in a two-dimensional array. In this specific example,
you could store the state names in a 26 x 26 string array in which the
first and second indices correspond to the two letters in the code.
Because you can now find any code in a single step, this strategy is
O(1), although this performance comes at a cost in memory space.
The Idea of Hashing
• The third strategy on the preceding slide shows that one can
make the get and put operations run very quickly, even to
the point that the cost of finding a key is independent of the
number of keys in the table. This O(1) performance is
possible only if you know where to look for a particular key.
• To get a sense of how you might achieve this goal in practice,
it helps to think about how you find a word in a dictionary.
You certainly don’t start at the beginning at look at every
word, but you probably don’t use binary search either. Most
dictionaries have thumb tabs that indicate where each letter
appear. Words starting with A are in the A section, and so on.
• The HashMap class uses a strategy called hashing, which is
conceptually similar to the thumb tabs in a dictionary. The
critical idea is that you can improve performance enormously
if you use the key to figure out where to look.
Hash Codes
• To make it possible for the HashMap class to know where to
look for a particular key, every object defines a method called
hashCode that returns an integer associated with that object.
As you will see in a subsequent slide, this hash code value
tells the HashMap implementation where it should look for a
particular key, dramatically reducing the search time.
• In general, clients of the HashMap class have no reason to
know the actual value of the integer returned as a hash code
for some key. The important things to remember are:
1. Every object has a hash code, even if you don’t know what it is.
2. The hash code for any particular object is always the same.
3. If two objects have equal values, they have the same hash code.
Hash Codes and Collisions
• For any Java object, the hashCode method returns an int
that can by any one of the 4,294,967,296 (232) possible values
for that type.
• While 4,294,967,296 seems huge, it is insignificant compared
to the total number of objects that can be represented inside a
machine, which would be infinite if there were no limits on
the size of memory.
• The fact that there are more possible objects than hash codes
means that there must be some distinct objects that have the
same hash codes. For example, the strings "hierarch" and
"crinolines" have the same hash code, which happens to
be -1732884796.
• Because different keys can generate the same hash codes, any
strategy for implementing a map using hash codes must take
that possibility into account, even though it happens rarely.
The Bucket Hashing Strategy
• One common strategy for implementing a map is to use the
hash code for an object to select an index into an array that
will contain all the keys with that hash code. Each element of
that array is conventionally called a bucket.
• In practice, the array of buckets is smaller than the number of
hash codes, making it necessary to convert the hash code into
a bucket index, typically by executing a statement like
int bucket = Math.abs(key.hashCode()) % N_BUCKETS;
• The value in each element of the bucket array cannot be a
single key/value pair given the chance that different keys fall
into the same bucket. Such situations are called collisions.
• To take account of the possibility of collisions, each elements
of the bucket array is usually a linked list of the keys that fall
into that bucket, as shown in the simulation on the next slide.
Simulating Bucket Hashing
0
1
2
3
4
5
CO
CA
DE
KS
ID rest CO
CA
DE
ID the keys
CO
CA are added
DE
CO
CA
CA
stateMap.put("AL",
"Alabama")
stateMap.put("AK",
"Alaska")
stateMap.put("AZ",
"Arizona")
The
of
similarly.
WY
CO
MT
NC
CA
DE
KS
NJ
ID
CO
MT
NC
CA
DE
KS
NJ
ID
CO
MT
CA
DE
KS
NJ
ID
CO
MT
CA
DE
KS
ID
North
New
California
Wyoming
Delaware
Colorado
Montana
Kansas
Idaho
Carolina
Jersey
North
New
California
Delaware
Colorado
Montana
Kansas
Idaho
Carolina
Jersey
New
California
Delaware
Colorado
Montana
Kansas
Idaho
Jersey
California
Delaware
Colorado
Montana
Kansas
Idaho
null
null
null
null
"AL".hashCode()
2091
"AK".hashCode()
2090
"AZ".hashCode()
2105
null
null
null  null
MN
OH
ND
SC
NY
TN
VA
IL
MN
OH
ND
SC
NY
TN
IL
MN
OH
ND
SC
NY
IL
MN
OH
ND
NY
IL
Math.abs(2090)
Math.abs(2091)
5
Math.abs(2105)
MN
ND
NY
IL
MN
NY
IL
MN
IL % 7 
IL 4
North
South
North
South
North
South
North
Minnesota
New
Tennessee
Virginia
Illinois
Ohio
York
Carolina
Dakota
Minnesota
New
Tennessee
Illinois
Ohio
York
Carolina
Dakota
Minnesota
New
Illinois
Ohio
York
Carolina
Dakota
Minnesota
New
Illinois
Ohio
York
Dakota
null
null
null
null
MO
MA
NE
SD
HI
MO
MA
NE
HI
MO
MA
HI
MA
HI
South
Massachusetts
Nebraska
Missouri
Hawaii
Dakota
Massachusetts
Nebraska
Missouri
Hawaii
Massachusetts
Missouri
Hawaii
Massachusetts
Hawaii
null
null
null
null
NM
UT
MI
IN
NM
MI
IN
MI
IN
IN
6
New
New
Michigan
Indiana
Utah
Mexico
Michigan
Indiana
Mexico
Michigan
Indiana
Indiana
null
null
null
null
California
Delaware
Colorado
Kansas
Idaho
North
Minnesota
New
Illinois
York
Dakota
California
Delaware
Colorado
Idaho
Minnesota
New
Illinois
York
California
Delaware
Colorado
Minnesota
Illinois
California
Colorado
California
null
Illinois
The
key null
"AL"
goes in bucket 5.
"AK" therefore
4.
"AZ"
null
null
null
Because
bucket you
5 already
contains "AL",
HI
Suppose
call stateMap.get("HI")
the
Hawaii"AZ" must be added to the chain.
null
"HI".hashCode()  2305
Math.abs(2305) % 7  2
The key "HI" must therefore be in bucket 2
and can be located by searching the chain.
WV
WA
OR
OK
AR
AK
PA
TX
RI
IA
WA
OR
OK
AR
AK
PA
TX
RI
IA
OR
OK
AR
AK
PA
TX
RI
IA
OR
OK
AR
AK
PA
RI
IA
OR
OK
AR
AK
PA
IA
OR
OK
AR
AK
IA
OK
AR
AK
IA
AR
AK
IA
AR
AK
AK
Rhode
West
Pennsylvania
Washington
Oklahoma
Arkansas
Oregon
Alaska
Texas
Iowa
Virginia
Island
Rhode
Pennsylvania
Washington
Oklahoma
Arkansas
Oregon
Alaska
Texas
Iowa
Island
Rhode
Pennsylvania
Oklahoma
Arkansas
Oregon
Alaska
Texas
Iowa
Island
Rhode
Pennsylvania
Oklahoma
Arkansas
Oregon
Alaska
Iowa
Island
Pennsylvania
Oklahoma
Arkansas
Oregon
Alaska
Iowa
Oklahoma
Arkansas
Oregon
Alaska
Iowa
Oklahoma
Arkansas
Alaska
Iowa
Arkansas
Alaska
Iowa
Arkansas
Alaska
Alaska
null
null
null
null
null
null
null
null
null
null
MD
GA
NH
NV
CT
AZ
WI
AL
MD
GA
NH
NV
CT
AZ
AL
MD
GA
NV
CT
AZ
AL
MD
GA
CT
AZ
AL
GA
CT
AZ
AL
CT
AZ
AL
AZ
AL
AL
New
Connecticut
Wisconsin
Maryland
Alabama
Arizona
Georgia
Nevada
Hampshire
New
Connecticut
Maryland
Alabama
Arizona
Georgia
Nevada
Hampshire
Connecticut
Maryland
Alabama
Arizona
Georgia
Nevada
Connecticut
Maryland
Alabama
Arizona
Georgia
Connecticut
Alabama
Arizona
Georgia
Connecticut
Alabama
Arizona
Alabama
Arizona
Alabama
null
null
null
null
null
null
null
null
ME
MS
KY
VT
LA
FL
ME
MS
KY
LA
FL
ME
KY
LA
FL
KY
LA
FL
KY
FL
FL
Mississippi
Louisiana
Kentucky
Vermont
Florida
Maine
Mississippi
Louisiana
Kentucky
Florida
Maine
Louisiana
Kentucky
Florida
Maine
Louisiana
Kentucky
Florida
Kentucky
Florida
Florida
null
null
null
null
null
null
skip simulation
Achieving O(1) Performance
• The simulation on the previous side uses only seven buckets
to emphasize what happens when collisions occur: the smaller
the number of buckets, the more likely collisions become.
• In practice, the real implementation of HashMap uses a much
larger value for N_BUCKETS to minimize the opportunity for
collisions. If the number of buckets is considerably larger
than the number of keys, most of the bucket chains will either
be empty or contain exactly one key/value pair.
• The ratio of the number of keys to the number of buckets is
called the load factor of the HashMap. Because a HashMap
achieves O(1) performance only if the load factor is small, the
library implementation of HashMap automatically increases
the number of buckets when the table becomes too full.
• The next few slides show an implementation of HashMap that
uses this approach.
The Code for Bucket Hashing
public class SimpleStringMap {
/** Creates a new SimpleStringMap with no key/value pairs */
public SimpleStringMap() {
bucketArray = new HashEntry[N_BUCKETS];
}
/**
* Sets the value associated with key. Any previous value for key is lost.
* @param key The key used to refer to this value
* @param value The new value to be associated with key
*/
public void put(String key, String value) {
int bucket = Math.abs(key.hashCode()) % N_BUCKETS;
HashEntry entry = findEntry(bucketArray[bucket], key);
if (entry == null) {
entry = new HashEntry(key, value);
entry.setLink(bucketArray[bucket]);
bucketArray[bucket] = entry;
} else {
entry.setValue(value);
}
}
page 1 of 4
skip code
The Code for Bucket Hashing
/**
public class SimpleStringMap {
* Retrieves the value associated with key, or null if no such value exists.
* @param
keya The
used to look with
up the
/**
Creates
new key
SimpleStringMap
no value
key/value pairs */
* @return
The
value
associated
with
key,
or
null if no such value exists
public SimpleStringMap() {
*/
bucketArray = new HashEntry[N_BUCKETS];
public String get(String key) {
} int bucket = Math.abs(key.hashCode()) % N_BUCKETS;
HashEntry entry = findEntry(bucketArray[bucket], key);
/**
if (entry == null) {
* Sets the
value
associated with key. Any previous value for key is lost.
return
null;
} else
* @param
key{ The key used to refer to this value
* @paramreturn
value entry.getValue();
The new value to be associated with key
}
*/
}public void put(String key, String value) {
int bucket = Math.abs(key.hashCode()) % N_BUCKETS;
/*
HashEntry
entry
= findEntry(bucketArray[bucket],
key);
* Scans
the entry
chain
looking for an entry that matches
the specified key.
if
(entry
==
null)
{
* If no such entry exists, findEntry returns null.
entry = new HashEntry(key, value);
*/
privateentry.setLink(bucketArray[bucket]);
HashEntry findEntry(HashEntry entry, String key) {
while
(entry != null) { = entry;
bucketArray[bucket]
if (entry.getKey().equals(key))
return entry;
} else
{
entry
= entry.getLink();
entry.setValue(value);
}}
return null;
}
}
page 2 of 4
skip code
The Code for Bucket Hashing
/**
/* Private constants */
* Retrieves
private static
the value
finalassociated
int N_BUCKETS
with=key,
7; or null if no such value exists.
* @param key The key used to look up the value
/*
* @return
Private The
instance
valuevariables
associated
*/with key, or null if no such value exists
*/private HashEntry[] bucketArray;
public String get(String key) {
}
int bucket = Math.abs(key.hashCode()) % N_BUCKETS;
HashEntry entry = findEntry(bucketArray[bucket], key);
if (entry == null) {
/* Package-private
return null;
class: HashEntry */
/*
} else {
* This class
return
represents
entry.getValue();
a pair of a key and a value, along with a reference
* to }
the next HashEntry in the chain. The methods exported by the class
* }
consist only of getters and setters.
*/
/*
class HashEntry {
* Scans the entry chain looking for an entry that matches the specified key.
/*
* If
Creates
no such
a new
entry
HashEntry
exists,for
findEntry
the specified
returnskey/value
null.
pair */
*/public HashEntry(String key, String value) {
private
entryKey
HashEntry
= key;findEntry(HashEntry entry, String key) {
while (entry
entryValue
= value;
!= null) {
}
if (entry.getKey().equals(key)) return entry;
entry = entry.getLink();
/* Returns
}
the key component of a HashEntry */
public
return
String
null;
getKey() {
} return entryKey;
}
page 3 of 4
skip code
The Code for Bucket Hashing
/* Private
Returns constants
the value */
component of a HashEntry */
private
public String
staticgetValue()
final int N_BUCKETS
{
= 7;
return entryValue;
/* }
Private instance variables */
private HashEntry[] bucketArray;
/* Sets the value component of a HashEntry to a new value */
} public void setValue(String value) {
entryValue = value;
}
/* Package-private class: HashEntry */
/* Returns the next link in the entry chain */
* This
public
class
HashEntry
represents
getLink()
a pair
{ of a key and a value, along with a reference
* to return
the next
entryLink;
HashEntry in the chain. The methods exported by the class
* consist
}
only of getters and setters.
*/
class
/* Sets
HashEntry
the link{to the next entry in the chain */
public void setLink(HashEntry nextEntry) {
/* Creates
entryLink
a new=HashEntry
nextEntry;
for the specified key/value pair */
public
}
HashEntry(String key, String value) {
entryKey = key;
/* Private
entryValue
instance
= value;
variables */
private String entryKey;
/* The key component for this HashEntry
*/
}
private String entryValue;
/* The value component for this HashEntry */
HashEntry
entryLink;
The next entry
in the chain
*/
/* private
Returns the
key component
of /*
a HashEntry
*/
} public String getKey() {
return entryKey;
}
page 4 of 4
Writing hashCode Methods
If you want to use one of your own classes as a HashMap key,
you will usually need to override its hashCode method. When
you do, it is useful to keep the following principles in mind:
1. The hashCode method must always return the same code if it is
called on the same object.
2. The implementation of hashCode must be consistent with the
implementation of the equals method, because hashing uses the
equals method to compare keys. This condition is stronger than
the first one, which says only that the hash code for a specific object
should not change arbitrarily.
3. The hashCode implementation should seek to minimize collisions.
4. The hashCode method should be relatively simple to compute. If
you write a hashCode method that takes a long time to evaluate,
you give up the primary advantage of hashing, which is that the
basic algorithm runs very quickly.
The hashCode and equals Methods
• As noted on the preceding slide, hashing can work only if the
definitions of hashCode and equals are compatible. The
practical implication of this principle is that you should never
override one without overriding the other.
• The following code, for example, makes it possible to use
objects of the Rational class from Chapter 6 as hashMap keys:
public int hashCode() {
return new Integer(num).hashCode()
^ (37 * new Integer(den).hashCode());
}
public boolean equals(Object obj) {
if (obj instanceof Rational) {
Rational r = (Rational) obj;
return this.num == r.num && this.den == r.den;
} else {
return false;
}
}
The Java Collections Framework
• The ArrayList and HashMap classes are part of a larger set
of classes called the Java Collections Framework, which is
part of the java.util package.
• The classes in the Java Collections Framework fall into three
general categories:
1. Lists. A list is an ordered collection of values that allows the
client to add and remove elements. As you would expect, the
ArrayList class falls into this category.
2. Sets. A set is an unordered collection of values in which a
particular object can appear at most once.
3. Maps. A map implements an association between keys and
values. The HashMap class is in this category.
• The next slide shows the Java class hierarchy for the first two
categories, which together are called collections.
The Collection Hierarchy
The following diagram shows the portion of the Java Collections
Framework that implements the Collection interface. The
dotted lines specify that a class implements a particular interface.
«interface»
Collection
«interface»
List
AbstractList
ArrayList
«interface»
Set
AbstractCollection
LinkedList
AbstractSet
HashSet
TreeSet
«interface»
SortedSet
ArrayList vs. LinkedList
• If you look at the left side of the collections hierarchy on the
preceding slide, you will discover that there are two classes in
the Java Collections Framework that implement the List
interface: ArrayList and LinkedList.
• Because these classes implement the same interface, it is
generally possible to substitute one for the other.
• The fact that these classes have the same effect, however,
does not imply that they have the same performance
characteristics.
– The ArrayList class is more efficient if you are selecting a
particular element or searching for an element in a sorted array.
– The LinkedList class is more efficient if you are adding or
removing elements from a large list.
• Choosing which list implementation to use is therefore a
matter of evaluating the performance tradeoffs.
The Set Interface
• The right side of the collections hierarchy diagram contains
classes that implement the Set interface, which is used to
represent an unordered collection of objects. The two
concrete classes in this category are HashSet and TreeSet.
• A set is in some ways a stripped-down version of a list. Both
structures allow you to add and remove elements, but the set
form does not offer any notion of index positions. All you
can know is whether an object is present or absent from a set.
• The difference between the HashSet and TreeSet classes
reflects a difference in the underlying implementation. The
HashSet class is built on the idea of hashing; the TreeSet
class is based on a structure called a binary tree, the details of
which are beyond the scope of the text. In practice, the main
difference arises when you iterate over the elements of a set,
which is described on the next slide.
Iteration in Collections
• One of the most useful operations for any collection is the
ability to run through each of the elements in a loop. This
process is called iteration.
• The java.util package includes a class called Iterator
that supports iteration over the elements of a collection. In
older versions of Java, the programming pattern for using an
iterator looks like this:
Iterator iterator = collection.elements();
while (iterator.hasNext()) {
type element = (type) iterator.next();
. . . statements that process this particular element . . .
}
• Java Standard Edition 5.0 allows you to simplify this code to
for (type element : collection) {
. . . statements that process this particular element . . .
}
Iteration Order
• For a collection that implements the List interface, the order
in which iteration proceeds through the elements of the list is
defined by the underlying ordering of the list. The element at
index 0 comes first, followed by the other elements in order.
• The ordering of iteration in a Set is more difficult to specify
because a set is, by definition, an unordered collection. A set
that implements only the Set interface, for example, is free to
deliver up elements in any order, typically choosing an order
that is convenient for the implementation.
• If, however, a Set also implements the SortedSet interface
(as the TreeSet class does), the iterator is forced to deliver
elements that appear in ascending order according to the
compareTo method for that class. An iterator for a TreeSet
of strings is therefore required to deliver its elements in
lexicographic order, as illustrated on the next slide.
Iteration Order in a HashMap
The following method iterates through the keys in a map:
private void listKeys(Map<String,String> map, int nPerLine) {
String className = map.getClass().getName();
int lastDot = className.lastIndexOf(".");
String shortName = className.substring(lastDot + 1);
println("Using " + shortName + ", the keys are:");
Iterator<String> iterator = map.keySet().iterator();
for (int i = 1; iterator.hasNext(); i++) {
print(" " + iterator.next());
if (i % nPerLine == 0) println();
}
}
If you call this method on a HashMap containing the two-letter
state codes, you get:
MapIterator
Using HashMap, the
SC VA LA GA DC OH
DE MS WV HI FL KS
NH MT WI CO OK NE
IN AL CA UT WY ND
keys are:
MN KY WA IL
SD AK TN ID
NV MI MD TX
PA AR CT NJ
OR
RI
VT
ME
NM
NC
AZ
MO
MA
NY
PR
IA
Iteration Order in a TreeMap
The following method iterates through the keys in a map:
private void listKeys(Map<String,String> map, int nPerLine) {
String className = map.getClass().getName();
int lastDot = className.lastIndexOf(".");
String shortName = className.substring(lastDot + 1);
println("Using " + shortName + ", the keys are:");
Iterator<String> iterator = map.keySet().iterator();
for (int i = 1; iterator.hasNext(); i++) {
print(" " + iterator.next());
if (i % nPerLine == 0) println();
}
}
If you call instead this method on a TreeMap containing the
same values, you get:
MapIterator
Using TreeMap, the
AK AL AR AZ CA CO
ID IL IN KS KY LA
MT NC ND NE NH NJ
PR RI SC SD TN TX
keys are:
CT DC DE FL
MA MD ME MI
NM NV NY OH
UT VA VT WA
GA
MN
OK
WI
HI
MO
OR
WV
IA
MS
PA
WY
The Map Hierarchy
The following diagram shows the portion of the Java Collections
Framework that implements the Map interface. The structure
matches that of the Set interface in the Collection hierarchy.
The distinction between HashMap and TreeMap is the same as
that between HashSet and TreeSet.
«interface»
Map
AbstractMap
HashMap
TreeMap
«interface»
SortedMap
The Collections Toolbox
• The Collections class (not the same as the Collection
interface) exports several static methods that operate on lists,
the most important of which appear in the following table:
binarySearch(list, key) Finds key in a sorted list using binary search.
sort(list)
Sorts a list into ascending order.
min(list)
Returns the smallest value in a list.
max(list)
Returns the largest value in a list.
reverse(list)
Reverses the order of elements in a list.
shuffle(list)
Randomly rearranges the elements in a list.
swap(list, p1, p2)
Exchanges the elements at index positions p1 and p2.
replaceAll(list, x1, x2) Replaces all elements matching x1 with x2.
• The java.util package exports a similar Arrays class that
provides the same basic operations for any array.
Principles of Object-oriented Design
Section 13.4 offers the following guidelines for package design:
• Unified. Each package should define a consistent abstraction with a clear
unifying theme. If a class does not fit within that theme, it should not be
part of the package.
• Simple. The package design should simplify things for the client. To the
extent that the underlying implementation is itself complex, the package
must seek to hide that complexity.
• Sufficient. For clients to adopt a package, it must provide classes and
methods that meet their needs. If some critical operation is missing from a
package, clients may decide to abandon it and develop their own tools.
• Flexible. A well-designed package should be general enough to meet the
needs of many different clients. A package that offers narrowly defined
operations for one client is not nearly as useful as one that can be used in
many different situations.
• Stable. The methods defined in a class exported as part of a package
should continue to have precisely the same structure and effect, even as
the package evolves. Making changes in the behavior of a class forces
clients to change their programs, which reduces its utility.
The End
Download