Скачать презентацию The Art and Science of CHAPTER 13 Collection Скачать презентацию The Art and Science of CHAPTER 13 Collection

7124311ddb43f47caf8afe5b7bf4a76d.ppt

  • Количество слайдов: 35

The Art and Science of CHAPTER 13 Collection Classes I think this is the 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 Array. List class revisited The Hash. Map class The Java Collections Framework Principles of object-oriented design ERIC S. ROBERTS Java An Introduction to Computer Science

The Array. List Class Revisited • You have already seen the Array. List class The Array. List Class Revisited • You have already seen the Array. List class in section Chapter 11. 8, which included examples showing how to use Array. Lists in both the pre- and post-Java 5. 0 worlds. The purpose of section 13. 1 is to look at the idea behind the Array. List class from a more general perspective that paves the way for a discussion of the Java Collection Framework. • The most obvious difference between the Array. List class and Java’s array facility is that Array. List is a full-fledged Java class. As such, the Array. List 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 Array. List class, by contrast, can be provided by extension.

The Power of Dynamic Allocation • Much of the added power of the Array. The Power of Dynamic Allocation • Much of the added power of the Array. List class comes from the fact that Array. Lists 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 Array. List class can reduce the complexity of programs substantially. As an example, the next few slides show two versions of the code for the read. Line. Array 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 Array. List 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.

read. Line. Array (Array. List version) /* * Reads all available lines from the read. Line. Array (Array. List 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[] read. Line. Array(Buffered. Reader rd) { Array. List line. List = new Array. List(); try { while (true) { String line = rd. read. Line(); if (line == null) break; line. List. add(line); } rd. close(); } catch (IOException ex) { throw new Error. Exception(ex); } String[] result = new String[line. List. size()]; for (int i = 0; i < result. length; i++) { result[i] = line. List. get(i); } return result; }

read. Line. Array (array version) /* * Reads all available lines from the specified read. Line. Array (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[] read. Line. Array(Buffered. Reader rd) { String[] line. Array = new String[INITIAL_CAPACITY]; int n. Lines = 0; try { while (true) { String line = rd. read. Line(); if (line == null) break; if (n. Lines + 1>= line. Array. length) { line. Array = double. Array. Capacity(line. Array); } line. Array[n. Lines++] = line; } rd. close(); } catch (IOException ex) { throw new Error. Exception(ex); } String[] result = new String[n. Lines]; for (int i = 0; i < n. Lines; i++) { result[i] = line. Array[i]; } return result; } page 1 of 2 skip code

read. Line. Array (array version) /* /* * Reads all available lines from the read. Line. Array (array version) /* /* * Reads all available lines from the specified reader and returns an and * Creates a string array with twice as many elements as the old array * containing the lines. This method closes the reader the new one. * then copies thoseexisting elements from the old array to at the end of the * */ file. */private String[] double. Array. Capacity(String[] old. Array) { private String[] read. Line. Array(Buffered. Reader rd) { String[] new. Array = new String[2 * old. Array. length]; String[] line. Array new String[INITIAL_CAPACITY]; for (int i = 0; i < =old. Array. length; i++) { intnew. Array[i] = old. Array[i]; n. Lines = 0; try { } while (true) return new. Array; { String line = rd. read. Line(); } if (line == null) break; if (n. Lines /* Private constants */ + 1>= line. Array. length) { line. Array = INITIAL_CAPACITY = 10; private static final int double. Array. Capacity(line. Array); } line. Array[n. Lines++] = line; } rd. close(); } catch (IOException ex) { throw new Error. Exception(ex); } String[] result = new String[n. Lines]; for (int i = 0; i < n. Lines; i++) { result[i] = line. Array[i]; } return result; } page 2 of 2 skip code

The Hash. Map Class • The Hash. Map class is one of the most The Hash. Map Class • The Hash. Map class is one of the most valuable tools exported by the java. util package and comes up in a surprising number of applications. • The Hash. Map 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 Hash. Map class exports other methods as well, the essential operations on a Hash. Map are the ones listed in the following table: new Hash. Map( ) map. put(key, value) map. get(key) Creates a new Hash. Map 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, Generic Types for Keys and Values • Beginning with Java Standard Edition 5. 0, the Hash. Map class is a generic type that allows clients to specify the types of the keys and values. • As with the Array. List class introduced in Chapter 11, the type information is written in angle brackets after the class name. The only difference is that a Hash. Map requires two type parameters: one for the key and one for the value. For example, the type designation Hash. Map indicates a Hash. Map that uses strings as keys to obtain integer values. • In versions that predate Java 5. 0, the Hash. Map 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 Hash. Map method to the intended type.

A Simple Hash. Map Application • Suppose that you want to write a program A Simple Hash. Map 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 Hash. Map 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 Hash. Map; 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 Hash. Map containing all 50 key/value pairs. Read in the two-letter abbreviation to translate. Call get on the Hash. Map to find the state name. Print out the name of the state.

The Postal. Lookup Application public void run() { Hash. Map<String, String> state. Map = The Postal. Lookup Application public void run() { Hash. Map state. Map = new Hash. Map(); private void init. State. Map(Hash. Map map) { init. State. Map(state. Map); map. put("AL", "Alabama"); while (true) { map. put("AK", "Alaska"); String code = read. Line("Enter two-letter state abbreviation: "); map. put("AZ", "Arizona"); if (code. length() == 0) break; . . . String state = state. Map. 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 state. Map } map. put("WY", "Wyoming"); map } Hawaii null VE WI HI Wisconsin } } Postal. Lookup 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 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 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 Hash. Map 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 Hash. Map class to know Hash Codes • To make it possible for the Hash. Map class to know where to look for a particular key, every object defines a method called hash. Code that returns an integer associated with that object. As you will see in a subsequent slide, this hash code value tells the Hash. Map implementation where it should look for a particular key, dramatically reducing the search time. • In general, clients of the Hash. Map 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 hash. Code method returns Hash Codes and Collisions • For any Java object, the hash. Code 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 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. hash. Code()) % 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 6 CO DE KS ID Simulating Bucket Hashing 0 1 2 3 4 5 6 CO DE KS ID DE ID CO DE CA CA state. Map. put("AL", added similarly. state. Map. put("AK", CO "Alaska") state. Map. put("AZ", CA "Arizona") The rest CO the keys are "Alabama") of WY CO MT NC DE KS NJ ID CA CO MT DE KS ID CA 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 "AL". hash. Code() null 2091 "AK". hash. Code() 2090 "AZ". hash. Code() 2105 null MN OH ND SC NY VA TN IL MN OH ND SC NY IL MN OH ND NY IL Math. abs(2090) Math. abs(2091) % 7 5 Math. abs(2105) MN ND NY MN IL IL 4 South North Minnesota New York Tennessee Virginia Illinois Ohio Carolina Dakota South North Minnesota New York Tennessee Illinois Ohio Carolina Dakota South North Minnesota New York Illinois Ohio Carolina Dakota North Minnesota New York Illinois Ohio Dakota null MO MA NE SD HI MO MA NE HI MA MO HI MA HI South Massachusetts Nebraska Missouri Hawaii Dakota Massachusetts Missouri Nebraska Hawaii Massachusetts Missouri Hawaii Massachusetts Hawaii null NM UT MI IN NM MI IN IN New Michigan Indiana Utah Mexico New Michigan Indiana Mexico Michigan Indiana null California Delaware Colorado Kansas Idaho North Minnesota New York Illinois Dakota California Delaware Colorado Idaho Minnesota New York Illinois California Delaware Colorado Minnesota Illinois California Colorado California null Illinois The key null therefore null in bucket 5. "AL" null goes "AK" 4. "AZ" null Because. Suppose you call state. Map. get("HI") bucket 5 already contains "AL", HI the "AZ" must be added to 2305 the chain. Hawaii "HI". hash. Code() null 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 PA TX RI IA AK OR OK AR PA IA AK OR OK AR IA AK 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 Iowa Island Pennsylvania Oklahoma Arkansas Oregon Alaska Iowa Oklahoma Arkansas Alaska Iowa Arkansas Alaska null null null MD GA NH NV CT AZ WI AL MD GA NH NV CT AZ AL MD GA CT AZ AL AZ AL AL New Connecticut Alabama Wisconsin Maryland Georgia Arizona Nevada Hampshire New Connecticut Alabama Maryland Georgia Arizona Nevada Hampshire Connecticut Alabama Maryland Georgia Arizona Nevada Connecticut Alabama Maryland Georgia Arizona Connecticut Alabama Arizona Alabama null null MS ME KY VT LA FL MS ME KY LA FL KY FL FL Mississippi Louisiana Kentucky Vermont Florida Maine Mississippi Louisiana Kentucky Florida Maine Louisiana Kentucky Florida null null skip simulation

Achieving O(1) Performance • The simulation on the previous side uses only seven buckets 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 Hash. Map 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 Hash. Map. Because a Hash. Map achieves O(1) performance only if the load factor is small, the library implementation of Hash. Map automatically increases the number of buckets when the table becomes too full. • The next few slides show an implementation of Hash. Map that uses this approach.

The Code for Bucket Hashing public class Simple. String. Map { /** Creates a The Code for Bucket Hashing public class Simple. String. Map { /** Creates a new Simple. String. Map with no key/value pairs */ public Simple. String. Map() { bucket. Array = new Hash. Entry[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. hash. Code()) % N_BUCKETS; Hash. Entry entry = find. Entry(bucket. Array[bucket], key); if (entry == null) { entry = new Hash. Entry(key, value); entry. set. Link(bucket. Array[bucket]); bucket. Array[bucket] = entry; } else { entry. set. Value(value); } } page 1 of 4 skip code

The Code for Bucket Hashing /** public class Simple. String. Map { * Retrieves The Code for Bucket Hashing /** public class Simple. String. Map { * Retrieves the value associated with key, or null if no such value exists. * Creates a new Simple. String. Map up no key/value pairs */ /**@param key The key used to look withthe value * @return Simple. String. Map() { public The value associated with key, or null if no such value exists */ bucket. Array = new Hash. Entry[N_BUCKETS]; public String get(String key) { } int bucket = Math. abs(key. hash. Code()) % N_BUCKETS; Hash. Entry entry = find. Entry(bucket. Array[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 entry. get. Value(); be associated with key value The new value to } */ } public void put(String key, String value) { int bucket = Math. abs(key. hash. Code()) % N_BUCKETS; /* Hash. Entry entry = looking for an entry that matches the specified key. * Scans the entry chain find. Entry(bucket. Array[bucket], key); if (entry == exists, * If no such entry null) { find. Entry returns null. entry = new Hash. Entry(key, value); */ entry. set. Link(bucket. Array[bucket]); private Hash. Entry find. Entry(Hash. Entry entry, String key) { while (entry != null) { = entry; bucket. Array[bucket] if { } else(entry. get. Key(). equals(key)) return entry; entry = entry. get. Link(); entry. set. Value(value); } } return null; } } page 2 of 4 skip code

The Code for Bucket Hashing /**Private constants */ /* * Retrieves the value associated The Code for Bucket Hashing /**Private constants */ /* * Retrieves the value associated with=key, or null if no such value exists. private static final int N_BUCKETS 7; * @param key The key used to look up the value * Private instance associated /* @return The valuevariables */with key, or null if no such value exists */private Hash. Entry[] bucket. Array; public String get(String key) { int bucket = Math. abs(key. hash. Code()) % N_BUCKETS; } Hash. Entry entry = find. Entry(bucket. Array[bucket], key); if (entry == null) { return null; /* Package-private class: Hash. Entry */ } else { /* return entry. get. Value(); * This class represents a pair of a key and a value, along with a reference * to } the next Hash. Entry in the chain. The methods exported by the class * } consist only of getters and setters. */ /* class Hash. Entry { * Scans the entry chain looking for an entry that matches the specified key. * Creates a entry exists, find. Entry returns null. /* If no suchnew Hash. Entry for the specified key/value pair */ */public Hash. Entry(String key, String value) { private Hash. Entry find. Entry(Hash. Entry entry, String key) { entry. Key = key; while (entry != null) { entry. Value = value; if (entry. get. Key(). equals(key)) return entry; } entry = entry. get. Link(); } /* Returns the key component of a Hash. Entry */ return null; public String get. Key() { } return entry. Key; } page 3 of 4 skip code

The Code for Bucket Hashing /* Private constants */ Returns the value component of The Code for Bucket Hashing /* Private constants */ Returns the value component of a Hash. Entry */ private. String get. Value() N_BUCKETS = 7; public static final int { return entry. Value; /* Private instance variables */ } private Hash. Entry[] bucket. Array; /* Sets the value component of a Hash. Entry to a new value */ } public void set. Value(String value) { entry. Value = value; } /* Package-private class: Hash. Entry */ /* Returns the next link in the entry chain */ * This class represents a pair of a key and a value, along with a reference public Hash. Entry get. Link() { * to the next Hash. Entry in the chain. The methods exported by the class return entry. Link; * consist only of getters and setters. } */ class Hash. Entry {to the next entry in the chain */ /* Sets the link public void set. Link(Hash. Entry next. Entry) { /* Creates a new=Hash. Entry for the specified key/value pair */ entry. Link next. Entry; public Hash. Entry(String key, String value) { } entry. Key = key; /* Private instancevalue; entry. Value = variables */ private String entry. Key; /* The key component for this Hash. Entry */ } private String entry. Value; /* The value component for this Hash. Entry */ private Hash. Entry entry. Link; /* The next entry in the chain */ /* Returns the key component of a Hash. Entry */ } public String get. Key() { return entry. Key; } page 4 of 4

Writing hash. Code Methods If you want to use one of your own classes Writing hash. Code Methods If you want to use one of your own classes as a Hash. Map key, you will usually need to override its hash. Code method. When you do, it is useful to keep the following principles in mind: 1. The hash. Code method must always return the same code if it is called on the same object. 2. The implementation of hash. Code 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 hash. Code implementation should seek to minimize collisions. 4. The hash. Code method should be relatively simple to compute. If you write a hash. Code 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 hash. Code and equals Methods • As noted on the preceding slide, hashing The hash. Code and equals Methods • As noted on the preceding slide, hashing can work only if the definitions of hash. Code 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 hash. Map keys: public int hash. Code() { return new Integer(num). hash. Code() ^ (37 * new Integer(den). hash. Code()); } 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 Array. List and Hash. Map classes are part The Java Collections Framework • The Array. List and Hash. Map 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 Array. List 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 Hash. Map 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 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 Abstract. List Array. List «interface» Set Abstract. Collection Linked. List Abstract. Set Hash. Set Tree. Set «interface» Sorted. Set

Array. List vs. Linked. List • If you look at the left side of Array. List vs. Linked. List • 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: Array. List and Linked. List. • 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 Array. List class is more efficient if you are selecting a particular element or searching for an element in a sorted array. – The Linked. List 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 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 Hash. Set and Tree. Set. • 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 Hash. Set and Tree. Set classes reflects a difference in the underlying implementation. The Hash. Set class is built on the idea of hashing; the Tree. Set 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 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. has. Next()) { 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 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 Sorted. Set interface (as the Tree. Set class does), the iterator is forced to deliver elements that appear in ascending order according to the compare. To method for that class. An iterator for a Tree. Set of strings is therefore required to deliver its elements in lexicographic order, as illustrated on the next slide.

Iteration Order in a Hash. Map The following method iterates through the keys in Iteration Order in a Hash. Map The following method iterates through the keys in a map: private void list. Keys(Map map, int n. Per. Line) { String class. Name = map. get. Class(). get. Name(); int last. Dot = class. Name. last. Index. Of(". "); String short. Name = class. Name. substring(last. Dot + 1); println("Using " + short. Name + ", the keys are: "); Iterator iterator = map. key. Set(). iterator(); for (int i = 1; iterator. has. Next(); i++) { print(" " + iterator. next()); if (i % n. Per. Line == 0) println(); } } If you call this method on a Hash. Map containing the two-letter state codes, you get: Map. Iterator Using Hash. Map, 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 Tree. Map The following method iterates through the keys in Iteration Order in a Tree. Map The following method iterates through the keys in a map: private void list. Keys(Map map, int n. Per. Line) { String class. Name = map. get. Class(). get. Name(); int last. Dot = class. Name. last. Index. Of(". "); String short. Name = class. Name. substring(last. Dot + 1); println("Using " + short. Name + ", the keys are: "); Iterator iterator = map. key. Set(). iterator(); for (int i = 1; iterator. has. Next(); i++) { print(" " + iterator. next()); if (i % n. Per. Line == 0) println(); } } If you call instead this method on a Tree. Map containing the same values, you get: Map. Iterator Using Tree. Map, 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 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 Hash. Map and Tree. Map is the same as that between Hash. Set and Tree. Set. «interface» Map Abstract. Map Hash. Map Tree. Map «interface» Sorted. Map

The Collections Toolbox • The Collections class (not the same as the Collection interface) 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: binary. Search(list, key) Finds key in a sorted list using binary search. 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, p 1, p 2) Exchanges the elements at index positions p 1 and p 2. replace. All(list, x 1, x 2) Replaces all elements matching x 1 with x 2. sort(list) • 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: 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 The End