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