350 likes | 360 Views
Java. The Art and Science of. An Introduction. to Computer Science. ERIC S. ROBERTS. C H A P T E R 1 3. Collection Classes. Chapter 13—Collection Classes. I think this is the most extraordinary collection of talent, of human
E N D
Java The Art and Science of An Introduction to Computer Science ERIC S. ROBERTS C H A P T E R 1 3 Collection Classes 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 The ArrayList class revisited 13.2 The HashMap class 13.3 The Java Collections Framework 13.4 Principles of object-oriented design
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 theArrayList 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(ArrayListversion) /* * 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(arrayversion) /* * 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
/* * Creates a string array with twice as many elements as the old array and * then copies the existing elements from the old array to the new one. */ private String[] doubleArrayCapacity(String[] oldArray) { String[] newArray = new String[2 * oldArray.length]; for (int i = 0; i < oldArray.length; i++) { newArray[i] = oldArray[i]; } return newArray; } /* Private constants */ private static final int INITIAL_CAPACITY = 10; readLineArray(arrayversion) /* * 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 2 of 2 skip code
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() Creates a new HashMap object that is initially empty. map.put(key, value) Sets the association for key in the map to value. map.get(key) Returns the value associated with key, or null if none. 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.
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.
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. • To implement this program in Java, you need to perform the following steps, which are illustrated on the following slide: 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.
private void initStateMap(HashMap<String,String> map) { map.put("AL", "Alabama"); map.put("AK", "Alaska"); map.put("AZ", "Arizona"); map.put("FL", "Florida"); map.put("GA", "Georgia"); map.put("HI", "Hawaii"); map.put("WI", "Wisconsin"); map.put("WY", "Wyoming"); } public void run() { HashMap<String,String> stateMap = new HashMap<String,String>(); initStateMap(stateMap); while (true) { String code = readLine("Enter two-letter state abbreviation: "); if (code.length() == 0) break; String state = stateMap.get(code); if (state == null) { println(code + " is not a known state abbreviation"); } else { println(code + " is " + state); } } } . . . . . . map stateMap The PostalLookup Application public void run() { HashMap<String,String> stateMap = new HashMap<String,String>(); initStateMap(stateMap); while (true) { String code = readLine("Enter two-letter state abbreviation: "); if (code.length() == 0) break; String state = stateMap.get(code); if (state == null) { println(code + " is not a known state abbreviation"); } else { println(code + " is " + state); } } } state code stateMap null VE WI HI Hawaii Wisconsin PostalLookup AL=Alabama AK=Alaska HI Enter two-letter state abbreviation: AZ=Arizona HI is Hawaii . . . WI Enter two-letter state abbreviation: FL=Florida WI is Wisconsin GA=Georgia VE Enter two-letter state abbreviation: HI=Hawaii . . . VE is not a known state abbreviation WI=Wisconsin Enter two-letter state abbreviation: WY=Wyoming skip simulation
1. Linear search in parallel arrays. Keep the two-character codes in one arrayand 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 26x26 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. Implementation Strategies for Maps There are several strategies you might choose to implement the map operations get and put. Those strategies include:
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.
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. 2. 3. Every object has a hash code, even if you don’t know what it is. The hash code for any particular object is always the same. If two objects have equal values, they have the same hash code. 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.
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.
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 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. • 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.
The rest of the keys are added similarly. MA MA SD MA NE MO MA HI AZ GA KS LA DE ID KS DE CO NV ME KY MD DE ID DE MT ID CT MT TX RI PA OR IA MO AR OK MO RI NE PA OR GA KY AR CO CT KY DE CO ME NC NV ID VT MT LA ID GA NJ AZ CO CT DE MS KY KS MD CT CO KY VA LA KS WI ID ME NH KS NV CO NJ CO NH MD DE AZ CO TN LA SC MN NY NJ MD NC WY CT PA AZ CT MN AZ ND GA NY AZ GA SC SC RI MN OH NY IA AR ND OR OK MS WV OH WA MN NY TX ND OH MN NY AZ IA MI OK NM UT IA AR OK ND IA AR AR IA OR IA NY MT AR AR OK MI OK WA TX TN RI MN PA NM ND PA OR IA AR MI OK OR MN OH Massachusetts Massachusetts Massachusetts South Dakota Nebraska Missouri Massachusetts Hawaii Idaho New York Missouri Minnesota Tennessee Idaho Minnesota New York Ohio New York Colorado Minnesota New York New York North Dakota Ohio North Dakota Colorado Colorado South Carolina Maryland Michigan Minnesota North Dakota Delaware Ohio South Carolina Tennessee Virginia Wyoming North Carolina New Jersey Montana Kansas Delaware Delaware Missouri Idaho Minnesota Minnesota Oklahoma Colorado Colorado Delaware Kansas Arkansas Vermont Colorado Utah North Carolina New Jersey Mississippi Delaware Kansas Idaho Montana New Mexico Idaho Idaho New Mexico New Jersey New York North Dakota Ohio South Carolina Minnesota Nebraska North Dakota Michigan Montana Kansas Colorado Delaware Maine Delaware Michigan Kansas Montana Colorado New Hampshire Georgia Washington Maine Iowa Iowa Oregon Arkansas Oregon Louisiana Arkansas Kentucky Arkansas Maryland Pennsylvania Connecticut Arizona Rhode Island Texas Arkansas Pennsylvania Louisiana Iowa West Virginia Rhode Island Oregon Oklahoma Kentucky Oklahoma Oklahoma Louisiana Maine Iowa Iowa Arkansas Arkansas Pennsylvania Kentucky Oklahoma Oklahoma Washington Georgia Mississippi Arkansas Kentucky Pennsylvania Rhode Island Texas Wisconsin Oklahoma New Hampshire Nevada Louisiana Iowa Oregon Pennsylvania Connecticut Rhode Island Arizona Arizona Georgia Oregon Kentucky Texas Nevada Iowa Nevada Arizona Georgia Arizona Connecticut Georgia Connecticut Maryland Arizona Connecticut Maryland Oregon Iowa Connecticut Arkansas Arizona null SD NE MO MA HI South Dakota Nebraska Missouri Massachusetts Hawaii null IN IN CA HI IL IN IN AK HI IL FL AK IL CA AK AK FL AK CA FL HI AK IL HI AK AK FL AL FL CA AL CA IL AL AL CA AL AK IL AL AL FL AL IL CA IL CA AK CA Florida Alaska Alabama Florida Illinois Illinois Alabama Alaska Illinois Illinois Florida Alabama Alaska Alaska Alabama Illinois Hawaii Hawaii Illinois California Hawaii Alaska Hawaii California Alabama Illinois California Indiana Indiana Alabama Florida Alaska Indiana California Alabama Indiana Alaska California California Alaska Alaska Florida Alaska Florida Illinois California California Alabama California null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null null Simulating Bucket Hashing stateMap.put("AL", "Alabama") 0 stateMap.put("AK", "Alaska") stateMap.put("AZ", "Arizona") 1 "AL".hashCode() 2091 "AK".hashCode() 2090 "AZ".hashCode() 2105 2 3 Math.abs(2090) % 7 4 Math.abs(2091) % 7 5 Math.abs(2105) % 7 5 4 The key "AL" therefore goes in bucket 5. The key "AK" therefore goes in bucket 4. The key "AZ" therefore goes in bucket 5. 5 6 Because bucket 5 already contains "AL", the "AZ" must be added to the chain. Suppose you call stateMap.get("HI") "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. 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
/** * Retrieves the value associated with key, or null if no such value exists. * @param key The key used to look up the value * @return The value associated with key, or null if no such value exists */ public String get(String key) { int bucket = Math.abs(key.hashCode()) % N_BUCKETS; HashEntry entry = findEntry(bucketArray[bucket], key); if (entry == null) { return null; } else { return entry.getValue(); } } /* * Scans the entry chain looking for an entry that matches the specified key. * If no such entry exists, findEntry returns null. */ private HashEntry findEntry(HashEntry entry, String key) { while (entry != null) { if (entry.getKey().equals(key)) return entry; entry = entry.getLink(); } return null; } 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 2 of 4 skip code
/* Private constants */ private static final int N_BUCKETS = 7; /* Private instance variables */ private HashEntry[] bucketArray; } /* Package-private class: HashEntry */ /* * This class represents 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 { /* Creates a new HashEntry for the specified key/value pair */ public HashEntry(String key, String value) { entryKey = key; entryValue = value; } /* Returns the key component of a HashEntry */ public String getKey() { return entryKey; } The Code for Bucket Hashing /** * Retrieves the value associated with key, or null if no such value exists. * @param key The key used to look up the value * @return The value associated with key, or null if no such value exists */ public String get(String key) { int bucket = Math.abs(key.hashCode()) % N_BUCKETS; HashEntry entry = findEntry(bucketArray[bucket], key); if (entry == null) { return null; } else { return entry.getValue(); } } /* * Scans the entry chain looking for an entry that matches the specified key. * If no such entry exists, findEntry returns null. */ private HashEntry findEntry(HashEntry entry, String key) { while (entry != null) { if (entry.getKey().equals(key)) return entry; entry = entry.getLink(); } return null; } page 3 of 4 skip code
/* Returns the value component of a HashEntry */ public String getValue() { return entryValue; } /* Sets the value component of a HashEntry to a new value */ public void setValue(String value) { entryValue = value; } /* Returns the next link in the entry chain */ public HashEntry getLink() { return entryLink; } /* Sets the link to the next entry in the chain */ public void setLink(HashEntry nextEntry) { entryLink = nextEntry; } /* Private instance variables */ private String entryKey; /* The key component for this HashEntry */ private String entryValue; /* The value component for this HashEntry */ private HashEntry entryLink; /* The next entry in the chain */ } The Code for Bucket Hashing /* Private constants */ private static final int N_BUCKETS = 7; /* Private instance variables */ private HashEntry[] bucketArray; } /* Package-private class: HashEntry */ /* * This class represents 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 { /* Creates a new HashEntry for the specified key/value pair */ public HashEntry(String key, String value) { entryKey = key; entryValue = value; } /* Returns the key component of a HashEntry */ public String getKey() { return entryKey; } page 4 of 4
1. 2. 3. 4. The hashCode method must always return the same code if it is called on the same object. 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. The hashCode implementation should seek to minimize collisions. 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. 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:
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 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 classes in the Java Collections Framework fall into three general categories: 1. 2. 3. 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. Sets. A set is an unordered collection of values in which a particular object can appear at most once. Maps. A map implements an association between keys and values. The HashMap class is in this category. 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 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 AbstractCollection «interface» Set AbstractList AbstractSet «interface» SortedSet ArrayList LinkedList HashSet TreeSet
ArrayListvs.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.
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 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.
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.
If you call this method on a HashMap containing the two-letter state codes, you get: MapIterator Using HashMap, the keys are: SC VA LA GA DC OH MN KY WA IL OR NM MA DE MS WV HI FL KS SD AK TN ID RI NC NY NH MT WI CO OK NE NV MI MD TX VT AZ PR IN AL CA UT WY ND PA AR CT NJ ME MO IA 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 instead this method on a TreeMap containing the same values, you get: MapIterator Using TreeMap, the keys are: AK AL AR AZ CA CO CT DC DE FL GA HI IA ID IL IN KS KY LA MA MD ME MI MN MO MS MT NC ND NE NH NJ NM NV NY OH OK OR PA PR RI SC SD TN TX UT VA VT WA WI WV WY 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(); } }
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 «interface» SortedMap HashMap TreeMap
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. Reverses the order of elements in a list. reverse(list) shuffle(list) Randomly rearranges the elements in a list. Exchanges the elements at index positions p1 and p2. swap(list, p1, p2) replaceAll(list, x1, x2) Replaces all elements matching x1 with x2. 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: • 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.