CS 1302 – Ch 22, Java Collections Framework – Part 1 Sections 1-11 Pages 728-756 Review Questions Programming Exercises Some code accompanies these notes and are found on the Schedule. Sections 22.1 – Introduction 1. Frequently, we find it useful to group related objects together, to form a collection of objects/elements. A collection is implemented by a data structure. A data structure provides a way to add items to the collection and to access items. Sometimes we refer to a collection as a collection class or a container class. The JCF defines a root interface, Collection. There are three major types of collections in Java. Each of these is an interface and each is a sub-interface of Collection. a. List – Most commonly used. A sequentially ordered set of objects. Can access values based on their position. Examples: A list of orders or items in a warehouse, a mailing list. b. Set – No duplicates allowed. No order on elements is imposed. No random access. Examples: a student’s set of courses for a semester, a set of passwords for registered users. c. Queue – Stores objects that are processed in a first-in, first-out (FIFO) fashion. 2. There are four common implementations of Lists: a. ArrayList – Backed by arrays. When size gets to large, array size is doubled by creating a new array and copying. Arrays in Java occupy contiguous memory. Not synchronized. Faster than Vector for single-threaded applications. Add, remove, contains are O(n). Positional access is constant. Example: Books checked out at a library by a patron. b. Vector – Backed by an array. Synchronized, for use with multithreaded applications. c. Stack – A subclass of Vector used to implement a stack. d. LinkedList – Uses dynamic memory to allocate space for each object. Constant time add and remove. Contains is O(n). Positional access is O(n). 3. There are three common implementations of Sets: a. HashSet – uses a hash table to store values. Add, remove, contains, all are constant time. b. LinkedHashSet – Somewhere in between. c. TreeSet – uses a tree to store values. Add, remove, contains are O(log(n)). When to use: All objects unique. Don’t need random access. If you need sorted order, use TreeSet. If you need to use the input order, use LinkedHashSet. Use HashSet when order is not important. 4. There is one common implementation of Queue – the PriorityQueue. 5. Another type of collection is a Map – A map stores values which are accessed by keys. Each value is associated with a key. Thus, we say a Map stores key/value pairs. We can use a key to quickly search for an object. No duplicate keys are allowed because keys are stored in a set. Values are stored in a list. 1 Example: a dictionary – the key is the word and the value is the definition; the health records for patients in a doctor’s office – the key is the patient and the value is the associated health records. A Map is an interface, but is not a sub-interface of Collection. 6. There are three common implementations of Maps: a. HashMap b. LinkedHashMap c. TreeMap When to use: A unique key identifies each object. Need random access. The difference in the three is in the ordering of their keys. Sections 22.2 – Collections 1. Java groups all collection classes into the Java Collections Framework (JCF). You can learn more about this at: http://java.sun.com/docs/books/tutorial/collections/index.html http://java.sun.com/docs/books/tutorial/collections/implementations/index.html 2. Consider the Collection interface which is the root interface for all collection classes. It specifies the minimum services any collection class must supply. «interface» java.util.Collection<E> +add(o: E): boolean Adds a new element o to this collection. +addAll(c: Collection<? extends E>): boolean Adds all the elements in the collection c to this collection. +clear(): void Removes all the elements from this collection. +contains(o: Object): boolean Returns true if this collection contains the element o. +containsAll(c: Collection<?>):boolean Returns true if this collection contains all the elements in c. +equals(o: Object): boolean Returns true if this collection is equal to another collection o. +hashCode(): int Returns the hash code for this collection. +isEmpty(): boolean Returns true if this collection contains no elements. +iterator(): Iterator Returns an iterator for the elements in this collection. +remove(o: Object): boolean Removes the element o from this collection. +removeAll(c: Collection<?>): boolean Removes all the elements in c from this collection. +retainAll(c: Collection<?>): boolean Retains the elements that are both in c and in this collection. +size(): int Returns the number of elements in this collection. +toArray(): Object[] Returns an array of Object for the elements in this collection. «interface» java.util.Iterator<E> +hasNext(): boolean Returns true if this iterator has more elements to traverse. +next(): E Returns the next element from this iterator. +remove(): void Removes the last element obtained using the next method. 2 3. Now, consider the Java Collections Framework (JCF) hierarchy. We will consider each of the 11 concrete classes shown. SortedSet TreeSet Set AbstractSet Collection HashSet LinkedHashSet AbstractCollection Vector Stack AbstractList List ArrayList AbstractSequentialList LinkedList Deque AbstractQueue Queue Concrete Classes Abstract Classes Interfaces PriorityQueue Note that the Map interface (below) is not a collection. However, the keys are stored in a Set and the values are stored in a List and is a part of the JCF. SortedMap Map TreeMap AbstractMap Interfaces Abstract Classes HashMap LinkedHashMap Concrete Classes 4. Below, we shown another view of the hierarchy. Here, we just show the three major sub interfaces (List, Set, Queue) and some of the concrete implementations. The abstract classes are not shown. This view shows how the behaviors are propagated down the hierarchy. The abstract classes implement so of the behaviors. 3 4 5. Here, we show the Iterator interfaces and the Map interfaces: 5 Sections 22.3 – The Collections Interface and the AbstractCollection Class 1. See item 2 in the previous section. Sections 22.4 – Sets 1. The Set interface extends the Collection interface. It does not introduce new methods or constants, but it stipulates that an instance of Set contains no duplicate elements. The concrete classes that implement Set must ensure that no duplicate elements can be added to the set. That is, no two elements e1 and e2 can be in the set such that e1.equals(e2) is true. 2. The Set interface hierarchy «interface» java.util.Collection<E> «interface» java.util.Set<E> java.util.AbstractSet<E> «interface» java.util.SortedSet<E> +first(): E java.util.HashSet<E> +HashSet() +last(): E +HashSet(c: Collection<? extends E>) +headSet(toElement: E): SortedSet<E> +HashSet(initialCapacity: int) +tailSet(fromElement: E): SortedSet<E> +HashSet(initialCapacity: int, loadFactor: float) «interface» java.util.NavigableSet<E> java.util.LinkedHashSet<E> +pollFirst(): E +LinkedHashSet() +pollLast(): E +LinkedHashSet(c: Collection<? extends E>) +lower(e: E): E +LinkedHashSet(initialCapacity: int) +higher(e: E):E +LinkedHashSet(initialCapacity: int, loadFactor: float) +floor(e: E): E +ceiling(e: E): E java.util.TreeSet<E> +TreeSet() +TreeSet(c: Collection<? extends E>) +TreeSet(comparator: Comparator<? super E>) +TreeSet(s: SortedSet<E>) 6 3. The AbstractSet class is a convenience class that extends AbstractCollection and implements Set. The AbstractSet class provides concrete implementations for the equals method and the hashCode method. The hash code of a set is the sum of the hash code of all the elements in the set. We will consider this later. Since the size method and iterator method are not implemented in the AbstractSet class, AbstractSet is an abstract class. Sections 22.4.1 – The HashSet Class 1. The HashSet class is a concrete class that implements Set. It can be used to store duplicate-free elements. For efficiency, objects added to a hash set need to implement the hashCode method in a manner that properly disperses the hash code. 2. Create a HashSet of Strings. Notice that we use Set as the reference type. We could have used HashSet but we prefer to be as general as possible. // Create a hash set Set<String> set = new HashSet<String>(); 3. Add some strings: Code System.out.print( System.out.print( System.out.print( System.out.print( System.out.print( Output true true true true true set.add("London") ); set.add("Paris") ); set.add("New York") ); set.add("San Francisco") ); set.add("Beijing") ); Try to add a duplicate: Code System.out.println( set.add("New York") ); Output false 4. Print the set. Set has a nice toString method. Notice that the order is not the order the items were added. We cannot guarantee the order with HashSet. Code System.out.println( set ); Output [San Francisco, New York, Paris, Beijing, London] 5. Remove elements from set: Code // Try removing; System.out.print( set.remove("Paris") ); System.out.println( set.remove("Chicago") ); // Print set System.out.println( set ); 7 Output true false [San Francisco, New York, Beijing, London] 6. Search for an item in set. Notice that search doesn’t return the item, it just tells whether it is in the set or not. Code System.out.print( set.contains( "Beijing" ) + ", " ); System.out.println( set.contains( "Moab" ) ); Output true false 7. Try to change the elements in a set using the enhanced for loop Code for( String c : set ) { c += "-a"; System.out.print( c + ", " ); } System.out.println(); // Print set System.out.println( set ); Output San Francisco-a, New York-a, Beijing-a, London-a, [San Francisco, New York, Beijing, London] 8. Use the iterator method. Can remove items from set, but can’t modify them. Code Iterator<String> iterator = set.iterator(); while ( iterator.hasNext() ) { String s = iterator.next(); // Can remove things if( s.equals("New York") ) iterator.remove(); // but can't modify s += "-b"; } System.out.println( set ); Output [San Francisco, Beijing, London] 9. Turn set into an array. We modify the items in the array, but the set is untouched. A copy is made. But, remember that String behaves like a primitive type. If we had custom objects, we’d need to be more careful. Code String[] ary = new String[ set.size() ]; ary = set.toArray( ary ); Output // Modify elements. for( int i=0; i<ary.length; i++ ) ary[i] += "-b"; // Print array System.out.println( Arrays.toString(ary) ); // Print set System.out.println( set); [San Francisco-b, Beijing-b, London-b] [San Francisco, Beijing, London] 8 10. Create a new set from the array. First, we convert the array to a list using the Arrays class utility method, asList. Then, the HashSet (and in fact any Collection) has an overloaded constructor that takes a Collection to initialize the set. If there were duplicates, they would be removed. Code List<String> list = Arrays.asList( ary ); Set<String> set2 = new HashSet<String>( list ); // Print set System.out.println( set2 ); Output [Beijing-b, San Francisco-b, London-b] Sections 22.Extra – HashSet & HashMap with Custom Objects 1. If you want to use HashSet with custom objects then the custom class must override both the equals and hashCode methods. The Java API says that if two objects are to be considered equal (a.equals(b)==true) then their hashcodes must be equal (a.hashCode()==b.hashCode()). Note, this does not say that two objects that have the same hashcode are equal. The signature for the two methods are: public boolean equals( Object o ) public int hashCode() We already know what the equals method is used to enforce logical equality. For instance, two Employees are equal if and only if they have the same first name, last name, and SSN. 2. The hashCode method is a bit more involved to explain. You will study hashing in detail in the next course (CS 3410). For now, we say that the hashCode method returns an integer that represents the location of the object. It is a short-cut way to find an object in a set, however, its use is encapsulated (hidden) inside the HashSet class. The operation works something like this: in the course of doing certain operations (add, contains) if the HashSet class determines that two objects have the same hashcode, then it calls equals to see if the objects are really the same. 3. For instance, suppose we are using a HashSet to hold Employee objects. When we add an Employee to the HashSet, the Employee’s hashcode is used to store the object, like an index in an array. For example, an Employee object may have a hashcode of 48, so the HashSet add method would put this object in the 48th position in an array. Later, when you want to call the contains or remove method with that Employee object, the code retrieves the hashcode (48) for the object and then finds the object in position 48. The situation is identical with HashMap if we want to use custom objects for the keys or if we want to use the containsValue method when the values are custom objects. 4. So, how do we override the hashcode method? We will show a simple approach here and again in the HashMap section. You will consider this in more detail in CS 3410. 9 5. Example – The class below only implements the equals method, but not the hashCode method. Code – Incorrect, only overrides the equals method. class EmployeeIncorrect { private String lName; private String fName; public EmployeeIncorrect( String lName, String fName ) { this.lName = lName; this.fName = fName; } public String getLastName() { return lName; } public String getFirstName() { return fName; } public boolean equals(Object o) { EmployeeIncorrect e = (EmployeeIncorrect)o; return ( this.getLastName().equals(e.getLastName()) ) && ( this.getFirstName().equals(e.getFirstName()) ); } } So, we create two Employees that are equal, but they both get added to the set. This is because the add method calls the hashCode method on each object and they are both different, so equals never gets called. The hashCode method that is called is the one implemented in the Object class which usually uses the address of the object to compute its hashcode. Usually this unique. Code Set<EmployeeIncorrect> set = new HashSet<EmployeeIncorrect>(); Output EmployeeIncorrect e1 = new EmployeeIncorrect("Jones", "Hugh" ); EmployeeIncorrect e2 = new EmployeeIncorrect("Jones", "Hugh" ); System.out.println( set.add( e1 ) ); System.out.println( set.add( e2 ) ); true true System.out.println( set ); [Hugh Jones, Hugh Jones] 10 6. Example – Here, we also override the hashCode method in the Employee class. Code class Employee { private String lName; private String fName; public Employee( String lName, String fName ) { this.lName = lName; this.fName = fName; } public String getLastName() { return lName; } public String getFirstName() { return fName; } public String toString() { return String.format("%s %s", getFirstName(), getLastName() ); } public boolean equals(Object o) { Employee e = (Employee)o; return ( this.getLastName().equals(e.getLastName()) ) && ( this.getFirstName().equals(e.getFirstName()) ); } public int hashCode() { int x = lName.hashCode(); int y = fName.hashCode(); int z = 23*x + 7*y; return z; } } Some test code now shows that the duplicate object will not be added Code Set<Employee> set2 = new HashSet<Employee>(); Output Employee e3 = new Employee("Jones", "Hugh" ); Employee e4 = new Employee("Jones", "Hugh" ); System.out.println( set2.add( e3 ) ); System.out.println( set2.add( e4 ) ); true false System.out.println( set2 ); [Hugh Jones] 11 Sections 22.4.2 – The LinkedHashSet Class 1. The LinkedHashSet class is virtually identical to HashSet except that the order is preserved. Code Set<String> set = new LinkedHashSet<String>(); Output // Add some cities System.out.print( set.add("London") ); System.out.print( set.add("Paris") ); System.out.print( set.add("New York") ); System.out.print( set.add("San Francisco") ); System.out.print( set.add("Beijing") ); System.out.println( set.add("New York") ); true true true true true false // Print set System.out.println( set ); [London, Paris, New York, San Francisco, Beijing] Sections 22.4.3 – The TreeSet Class 1. SortedSet is a subinterface of Set, which guarantees that the elements in the set are sorted. TreeSet is a concrete class that implements the SortedSet interface. You can use an iterator to traverse the elements in the sorted order. The elements can be sorted in two ways. a. One way is to use the Comparable interface. Thus, to create a TreeSet of custom objects, the class must implement Comparable. b. The other way is to specify a Comparator for the elements in the set. Instead of putting the comparison in the class itself, as with implementing Comparable, put the comparison code in a separate class, a Comparator. This approach is referred to as order by comparator. 2. Example – Create a HashSet, add some items and then create a TreeSet from HashSet. Code // Create a hash set Set<String> set = new HashSet<String>(); Output // Add strings to the set set.add("London"); set.add("Paris"); set.add("New York"); set.add("New Brunswick"); set.add("San Francisco"); set.add("Beijing"); set.add("New York"); // Create TreeSet from HashSet TreeSet<String> treeSet = new TreeSet<String>(set); // Print TreeSet System.out.println( treeSet ); [Beijing, London, New Brunswick, New York, Paris, San Francisco] 12 3. Example (continued) – Use SortedSet methods. Initial TreeSet (from above): [Beijing, London, New Brunswick, New York, Paris, San Francisco] Code // Returns the first (lowest) element currently in this set. System.out.println(treeSet.first()); // Returns the last (highest) element currently in this set. System.out.println(treeSet.last()); Code // Returns a view of the portion of this set // whose elements // are strictly less than // toElement, {x|x<e} System.out.println(treeSet.headSet("New York")); // Returns a view of the portion of this set // whose elements // are greater than or equal // to fromElement, {x|x>=e} System.out.println(treeSet.tailSet("New York")); System.out.println(treeSet.tailSet("New")); // Returns a view of the portion of this set // whose elements // range from fromElement, // inclusive, to toElement, exclusive. x|e1<=x<e2} System.out.println(treeSet.subSet("London", "New York")); Output Beijing San Francisco Output [Beijing, London, New Brunswick] [New York, Paris, San Francisco] [New Brunswick, New York, Paris, San Francisco] [London, New Brunswick] 4. Example (continued) – Use NavigableSet methods. Initial TreeSet: [Beijing, London, New Brunswick, New York, Paris, San Francisco] Code // Returns the greatest element in this set strictly less // than the given element, or null if there is no such // element, x<e System.out.println(treeSet.lower("P")); // Returns the least element in this set strictly greater // than the given element, or null if there is no such // element, x>e System.out.println(treeSet.higher("P")); System.out.println(treeSet.higher("Paris")); Code // Returns the greatest element in this set less than or // equal to the given element, or null if there is no such // element, x<=e System.out.println(treeSet.floor("P")); System.out.println(treeSet.floor("Paris")); // Returns the least element in this set greater than or // equal to the given element, or null if there is no such // element, x>=e System.out.println(treeSet.ceiling("P")); System.out.println(treeSet.ceiling("Paris")); 13 Output New York Paris San Francisco Output New York Paris Paris Paris Code // Retrieves and removes the first (lowest) element, or // returns null if this set is empty. System.out.println(treeSet.pollFirst()); // Retrieves and removes the last (highest) element, or // returns null if this set is empty. System.out.println(treeSet.pollLast()); Output System.out.println(treeSet); [London, New Brunswick, New York, Paris] Code Iterator<String> i = treeSet.iterator(); while( i.hasNext() ) System.out.print( i.next()); // Reverse Iterator i = treeSet.descendingIterator(); while( i.hasNext() ) System.out.print( i.next()); Beijing San Francisco Output London, New Brunswick, New York, Paris, Paris, New York, New Brunswick, London, 5. Example 2 – Custom class that implements Comparable. First, consider the Employee class that implements Comparable by comparing Employees based on last name, and then first name. class Employee implements Comparable<Employee> { private String lName; private String fName; private int ssNum; private double salary; public Employee(String lName, String fName, int ssNum, double salary) { this.lName = lName; this.fName = fName; this.ssNum = ssNum; this.salary = salary; } public public public public public String getLastName() { return lName; } String getFirstName() { return fName; } int getSSNum() { return ssNum; } double getSalary() { return salary; } void setSalary( double salary ) { this.salary = salary; } public String toString() { ... } public int compareTo( Employee e ) { int lName = this.getLastName().compareTo(e.getLastName()); if( lName != 0 ) return lName; else return this.getFirstName().compareTo(e.getFirstName()); } } 14 6. Example 2 (continued) – Some driver code. Note that e7 is not added because the compareTo method says it is the same as e4. The set is of course ordered on last name, first name. Code // Create a treeset with no comparator. TreeSet<Employee> tsEmployees = new TreeSet<Employee>(); Output Employee e1, e2, e3, e4, e5, e6, e7; e1 = new Employee("Lyton", "Xavier", 243558673, 77.88); e2 = new Employee("Dern", "Donald", 476227851, 23.44); e3 = new Employee("Lyton", "Nelly", 338290448, 82.45); e4 = new Employee("Reilly", "Junita", 338290448, 82.45); e5 = new Employee("Colter", "Ben", 883456321, 12.89); e6 = new Employee("Mixton", "Zerby", 422789012, 22.76); e7 = new Employee("Reilly", "Junita", 126270942, 82.45); // Put students in treeset tsEmployees.add(e1); tsEmployees.add(e2); tsEmployees.add(e3); tsEmployees.add(e4); tsEmployees.add(e5); tsEmployees.add(e6); false System.out.println( tsEmployees.add(e7) ); [(Colter, Ben - 883456321, 12.89), (Dern, Donald - 476227851, 23.44), (Lyton, Nelly - 338290448, 82.45), (Lyton, Xavier - 243558673, 77.88), (Mixton, Zerby - 422789012, 22.76), (Reilly, Junita - 338290448, 82.45)] System.out.println( tsEmployees ); Sections 22.5 – The Comparator Interface 1. You can use a Comparator to create a TreeSet based on some other ordering of the objects. For instance, the Employee class above implements Comparable to provide a way to sort objects based on last name and first name. Suppose we want to create a TreeSet of Employees sorted on SSN. We could create a Comparator like this: class EmployeeSSNumComparator implements Comparator<Employee> { public int compare( Employee e1, Employee e2 ) { return e1.getSSNum() - e2.getSSNum(); } } Notice the Comparator interface requires a compare method and uses generics to specify the objects to compare. It also requires an equals method, but one is inherited, so it is not necessary, here, to override. 15 2. Next, we can use a TreeSet constructor that takes a Comparator. EmployeeSSNumComparator comp = new EmployeeSSNumComparator(); TreeSet<Employee> tsEmployees = new TreeSet<Employee>(comp); This TreeSet will then use this Comparator to order elements, not the compareTo method in the Employee class. 3. Some Driver code: Code e1 = new Employee("Dern", "Donald", 476227851, 23.44); e2 = new Employee("Lyton", "Xavier", 243558673, 77.88); e3 = new Employee("Lyton", "Nelly", 338290448, 82.45); Output tsEmployees.add(e1); tsEmployees.add(e2); tsEmployees.add(e3); [(Lyton, Xavier - 243558673, 77.88), (Lyton, Nelly - 338290448, 82.45), (Dern, Donald - 476227851, 23.44)] System.out.println( tsEmployees ); 4. Suppose we want to remove an Employee and we only know the SSN. We can create a dummy employee object with the SSN and made-up values for name and salary. Code Employee dummy = new Employee( "Doo", "Daa", 338290448, 0.00); Output System.out.println( tsEmployees.remove(dummy)); true System.out.println( tsEmployees ); [(Lyton, Xavier - 243558673, 77.88), (Dern, Donald - 476227851, 23.44)] 16 5. The text provides an example where you can use a Comparator to compare “different” types of objects. There, they consider the GeometricObject class, and two subclasses, Circle and Rectangle. There, they define a GeometricObjectComparator that compares two GeometricObjects based on their area. import java.util.Comparator; public class GeometricObjectComparator implements Comparator<GeometricObject> { public int compare(GeometricObject o1, GeometricObject o2) { double area1 = o1.getArea(); double area2 = o2.getArea(); if (area1 < area2) return -1; else if (area1 == area2) return 0; else return 1; } } and then some driver code: Set<GeometricObject> set = new TreeSet<GeometricObject>(new GeometricObjectComparator()); set.add(new Rectangle(4, 5)); set.add(new Circle(40)); set.add(new Circle(40)); set.add(new Rectangle(4, 1)); Sections 22.6 – Lists 1. A set stores non-duplicate elements. To allow duplicate elements to be stored in a collection, you need to use a list. A list can not only store duplicate elements, but can also allow the user to specify where the element is stored. The user can access the element by index. 17 2. The List interface hierarchy «interface» java.util.Collection<E> «interface» java.util.List<E> +add(index: int, element:E): boolean Adds a new element at the specified index. +addAll(index: int, c: Collection<? extends E>) : boolean Adds all the elements in c to this list at the specified index. +get(index: int): E Returns the element in this list at the specified index. +indexOf(element: Object): int Returns the index of the first matching element. +lastIndexOf(element: Object): int Returns the index of the last matching element. +listIterator(): ListIterator<E> Returns the list iterator for the elements in this list. +listIterator(startIndex: int): ListIterator<E> Returns the iterator for the elements from startIndex. +remove(index: int): E Removes the element at the specified index. +set(index: int, element: E): E Sets the element at the specified index. +subList(fromIndex: int, toIndex: int): List<E> Returns a sublist from fromIndex to toIndex. 3. The ListIterator Interface extends the Iterator interface. Thus, in addition to the Iterator methods, a ListIterator also provides these methods. «interface» java.util.Iterator<E> «interface» java.util.ListIterator<E> +add(o: E): void Adds the specified object to the list. +hasPrevious(): boolean Returns true if this list iterator has more elements when traversing backward. +nextIndex(): int Returns the index of the next element. +previous(): E Returns the previous element in this list iterator. +previousIndex(): int Returns the index of the previous element. +set(o: E): void Replaces the last element returned by the previous or next method with the specified element. 18 Sections 22.6.1 – The ArrayList and LinkedList Classes 1. The ArrayList class and the LinkedList class are concrete implementations of the List interface. Which of the two classes you use depends on your specific needs. If you need to support random access through an index without inserting or removing elements from any place other than the end, ArrayList offers the most efficient collection. If, however, your application requires the insertion or deletion of elements from any place in the list, you should choose LinkedList. A list can grow or shrink dynamically. An array is fixed once it is created. If your application does not require insertion or deletion of elements, the most efficient data structure is the array. 2. The ArrayList & LinkedList classes. «interface» java.util.Collection<E> «interface» java.util.List<E> java.util.ArrayList<E> +ArrayList() Creates an empty list with the default initial capacity. +ArrayList(c: Collection<? extends E>) Creates an array list from an existing collection. +ArrayList(initialCapacity: int) Creates an empty list with the specified initial capacity. +trimToSize(): void Trims the capacity of this ArrayList instance to be the list's current size. 19 «interface» java.util.Collection<E> «interface» java.util.List<E> java.util.LinkedList<E> +LinkedList() Creates a default empty linked list. +LinkedList(c: Collection<? extends E>) Creates a linked list from an existing collection. +addFirst(o: E): void Adds the object to the head of this list. +addLast(o: E): void Adds the object to the tail of this list. +getFirst(): E Returns the first element from this list. +getLast(): E Returns the last element from this list. +removeFirst(): E Returns and removes the first element from this list. +removeLast(): E Returns and removes the last element from this list. 20 Sections 22.7 – Static Methods for Lists & Collections 1. The Collections class contains various static methods for operating on collections and maps, for creating synchronized collection classes, and for creating read-only collection classes. java.util.Collections List +sort(list: List): void Sorts the specified list. +sort(list: List, c: Comparator): void Sorts the specified list with the comparator. +binarySearch(list: List, key: Object): int Searches the key in the sorted list using binary search. +binarySearch(list: List, key: Object, c: Comparator): int Searches the key in the sorted list using binary search with the comparator. +reverse(list: List): void Reverses the specified list. +reverseOrder(): Comparator Returns a comparator with the reverse ordering. +shuffle(list: List): void Shuffles the specified list randomly. +shuffle(list: List): void Shuffles the specified list with a random object. +copy(des: List, src: List): void Copies from the source list to the destination list. +nCopies(n: int, o: Object): List Returns a list consisting of n copies of the object. +fill(list: List, o: Object): void Fills the list with the object. +max(c: Collection): Object Returns the max object in the collection. +max(c: Collection, c: Comparator): Object Returns the max object using the comparator. +min(c: Collection): Object Collection Returns the min object in the collection. +min(c: Collection, c: Comparator): Object Returns the min object using the comparator. +disjoint(c1: Collection, c2: Collection): boolean Returns true if c1 and c2 have no elements in common. +frequency(c: Collection, o: Object): int Returns the number of occurrences of the specified element in the collection. Sections 22.8 – Performance of Sets & Lists 1. The author did a comparison of sets and lists doing a number of random adds (Integers) and removes. I’ve modified his example to differentiate between adds and removes. In each case, we generate 500,000 unique integers in random order and then add them. In the case of remove, we add them as before and then randomly remove them. For the starred items (*), only 250,000 removes were performed. All time is in seconds. HashSet LinkedHashSet TreeSet ArrayList LinkedList add() remove() Remove last item 0.17 0.17 0.11 0.15 0.36 0.48 0.03 47.10* 0.03 0.04 202.63* 0.03 21 Sections 22.9 – The Vector & Stack Classes 1. The Java Collections Framework was introduced with Java 2. Several data structures were supported prior to Java 2. Among them are the Vector class and the Stack class. These classes were redesigned to fit into the Java Collections Framework, but their old-style methods are retained for compatibility. This section introduces the Vector class and the Stack class. 2. In Java 2, Vector is the same as ArrayList, except that Vector contains the synchronized methods for accessing and modifying the vector. None of the new collection data structures introduced so far are synchronized. If synchronization is required, you can use the synchronized versions of the collection classes. These classes are introduced later in the section, “The Collections Class.” 3. The Vector class «interface» java.util.List<E> java.util.Vector<E> +Vector() Creates a default empty vector with initial capacity 10. +Vector(c: Collection<? extends E>) Creates a vector from an existing collection. +Vector(initialCapacity: int) Creates a vector with the specified initial capacity. +Vector(initCapacity:int, capacityIncr: int) Creates a vector with the specified initial capacity and increment. +addElement(o: E): void Appends the element to the end of this vector. +capacity(): int Returns the current capacity of this vector. +copyInto(anArray: Object[]): void Copies the elements in this vector to the array. +elementAt(index: int): E Returns the object at the specified index. +elements(): Enumeration<E> Returns an enumeration of this vector. +ensureCapacity(): void Increases the capacity of this vector. +firstElement(): E Returns the first element in this vector. +insertElementAt(o: E, index: int): void Inserts o to this vector at the specified index. +lastElement(): E Returns the last element in this vector. +removeAllElements(): void Removes all the elements in this vector. +removeElement(o: Object): boolean Removes the first matching element in this vector. +removeElementAt(index: int): void Removes the element at the specified index. +setElementAt(o: E, index: int): void Sets a new element at the specified index. +setSize(newSize: int): void Sets a new size in this vector. +trimToSize(): void Trims the capacity of this vector to its size. 22 4. The Stack class java.util.Vector<E> java.util.Stack<E> +Stack() Creates an empty stack. +empty(): boolean Returns true if this stack is empty. +peek(): E Returns the top element in this stack. +pop(): E Returns and removes the top element in this stack. +push(o: E) : E Adds a new element to the top of this stack. +search(o: Object) : int Returns the position of the specified element in this stack. Sections 22.10.2 – Queues & Priority Queues 1. A queue is a first-in/first-out data structure. Elements are appended to the end of the queue and are removed from the beginning of the queue. In a priority queue, elements are assigned priorities. When accessing elements, the element with the highest priority is removed first. 2. The Queue class «interface» java.util.Collection<E> «interface» java.util.Queue<E> +offer(element: E): boolean Inserts an element to the queue. +poll(): E Retrieves and removes the head of this queue, or null if this queue is empty. +remove(): E Retrieves and removes the head of this queue and throws an exception if this queue is empty. +peek(): E Retrieves, but does not remove, the head of this queue, returning null if this queue is empty. +element(): E Retrieves, but does not remove, the head of this queue, throwing an exception if this queue is empty. 23 3. The PriorityQueue class «interface» java.util.Queue<E> java.util.PriorityQueue<E> +PriorityQueue() Creates a default priority queue with initial capacity 11. +PriorityQueue(initialCapacity: int) Creates a default priority queue with the specified initial capacity. +PriorityQueue(c: Collection<? extends E>) Creates a priority queue with the specified collection. +PriorityQueue(initialCapacity: int, comparator: Comparator<? super E>) Creates a priority queue with the specified initial capacity and the comparator. 4. Example – Priority Queue. We create a priority queue of Employee objects and use a Comparator that sorts on salary, ascending. In other words, the lower the salary, the higher the priority. First, the Comparator: class EmployeeSalaryComparator implements Comparator<Employee> { public int compare( Employee e1, Employee e2 ) { double salDif = e1.getSalary() - e2.getSalary(); if( salDif < 0 ) return -1; else if( salDif > 0 ) return 1; else return 0; } } 24 5. Example (continued). Code // Create a PriorityQueue with a comparator. EmployeeSalaryComparator comp = new EmployeeSalaryComparator(); PriorityQueue<Employee> qEmps = new PriorityQueue<Employee>(10,comp); // Create some employees. Employee e1 = new Employee("Lyton", "Xavier", Employee e2 = new Employee("Dern", "Donald", Employee e3 = new Employee("Lyton", "Nelly", Employee e4 = new Employee("Reilly", "Junita", Employee e5 = new Employee("Colter", "Ben", Employee e6 = new Employee("Mixton", "Zerby", Employee e7 = new Employee("Reilly", "Junita", 243558673, 476227851, 338290448, 338290448, 883456321, 422789012, 126270942, 77.88); 23.44); 82.45); 82.45); 12.89); 22.76); 23.44); // Put employees in queue qEmps.offer(e1); qEmps.offer(e2); qEmps.offer(e3); qEmps.offer(e4); qEmps.offer(e5); qEmps.offer(e6); qEmps.offer(e7); 6. Example (continued). Use the iterator (or enhanced for loop) to access elements. Notice, there is no order! Or is there? You’ll find out in CS 3410. Code Iterator<Employee> iter = qEmps.iterator(); while( iter.hasNext() ) System.out.println( iter.next() ); Output (Colter, Ben - 883456321, 12.89) (Dern, Donald - 476227851, 23.44) (Mixton, Zerby - 422789012, 22.76) (Reilly, Junita - 338290448, 82.45) (Lyton, Xavier - 243558673, 77.88) (Lyton, Nelly - 338290448, 82.45) (Reilly, Junita - 126270942, 23.44) 7. Example (continued). Remove all the elements from the queue and print. Removes in the correct order (by salary) and appears to preserve the order for ties. In other words, if two Employees have the same salary, then the one that arrived first, leaves first. You would like a queue to have such a priority, but the text says ties are broken arbitrarily. Code Output (Colter, Ben - 883456321, 12.89) (Mixton, Zerby - 422789012, 22.76) (Dern, Donald - 476227851, 23.44) (Reilly, Junita - 126270942, 23.44) (Lyton, Xavier - 243558673, 77.88) (Lyton, Nelly - 338290448, 82.45) (Reilly, Junita - 338290448, 82.45) while (qEmps.size() > 0) System.out.println(qEmps.remove()); 25 8. Note, you cannot change the priority of an item once it is in the PriorityQueue. If you do, you will destroy the ordering. The Comparator is only used when inserting into the PriorityQueue. If you did need to change a priority, you would need to remove the item, change the priority, and then re-insert it. Sections 22.10.1 – The LinkedList Class & Deque Interface 1. The Deque interface is a sub-interface of Queue. It defines methods to: addFirst, removeFirst, getFirst, addLast, removeLast, getLast. “Deque” is short for “double-ended queue” and is pronounced, “deck”. 2. The LinkedList class implements the List interface and the Deque interface. Thus, a LinkedList can be used to model a queue. 3. Example – modeling a first-in, first-out (FIFO) queue. Code // Create Queue from LinkedList LinkedList<Employee> qEmps = new LinkedList<Employee>(); Output // Create some new employees. Employee e1 = new Employee("Lyton", "Xavier", 243558673, 77.88); Employee e2 = new Employee("Dern", "Donald", 476227851, 23.44); Employee e3 = new Employee("Lyton", "Nelly", 338290448, 82.45); // Put employees in Queue qEmps.offer(e1); qEmps.offer(e2); qEmps.offer(e3); // Use Iterator to visit employees, order is FIFO. Iterator<Employee> iter = qEmps.iterator(); while( iter.hasNext() ) System.out.println( iter.next() ); (Lyton, Xavier - 243558673, 77.88) (Dern, Donald - 476227851, 23.44) (Lyton, Nelly - 338290448, 82.45) // Remove elements from the queue, Order is // FIFO while (qEmps.size() > 0) System.out.println(qEmps.remove()); 26 (Lyton, Xavier - 243558673, 77.88) (Dern, Donald - 476227851, 23.44) (Lyton, Nelly - 338290448, 82.45) Sections 22.11 – Maps 1. The Map interface maps keys to the elements. The keys are like indexes. In List, the indexes are integer. In Map, the keys can be any objects. Search keys Corresponding values Entry … . . 2. The Map Interface: java.util.Map<K, V> +clear(): void Removes all mappings from this map. +containsKey(key: Object): boolean Returns true if this map contains a mapping for the specified key. +containsValue(value: Object): boolean Returns true if this map maps one or more keys to the specified value. +entrySet(): Set Returns a set consisting of the entries in this map. +get(key: Object): V Returns the value for the specified key in this map. +isEmpty(): boolean Returns true if this map contains no mappings. +keySet(): Set<K> Returns a set consisting of the keys in this map. +put(key: K, value: V): V Puts a mapping in this map. +putAll(m: Map): void Adds all the mappings from m to this map. +remove(key: Object): V Removes the mapping for the specified key. +size(): int Returns the number of mappings in this map. +values(): Collection<V> Returns a collection consisting of the values in this map. 27 3. Concrete Map Classes «interface» java.util.Map<K, V> «interface» java.util.AbstractMap<K, V> java.util.SortedMap<K, V> +firstKey(): K java.util.HashMap<K, V> +lastKey(): K +comparator(): Comparator<? super K>) +HashMap() +headMap(toKey: K): SortedMap +HashMap(m: Map) +tailMap(fromKey: K): SortedMap java.util.LinkedHashMap<K, V> java.util.TreeMap<K, V> +LinkedHashMap() +LinkedHashMap(m: Map) +TreeMap() +LinkedHashMap(initialCapacity: int, loadFactor: float, accessOrder: boolean) +TreeMap(m: Map) +TreeMap(c: Comparator<? super K>) 4. HashMap and TreeMap – The HashMap and TreeMap classes are two concrete implementations of the Map interface. The HashMap class is efficient for locating a value, inserting a mapping, and deleting a mapping. The TreeMap class, implementing SortedMap, is efficient for traversing the keys in a sorted order. 5. LinkedHashMap – LinkedHashMap was introduced in JDK 1.4. It extends HashMap with a linked list implementation that supports an ordering of the entries in the map. The entries in a HashMap are not ordered, but the entries in a LinkedHashMap can be retrieved in the order in which they were inserted into the map (known as the insertion order), or the order in which they were last accessed, from least recently accessed to most recently (access order). The no-arg constructor constructs a LinkedHashMap with the insertion order. To construct a LinkedHashMap with the access order, use the LinkedHashMap(initialCapacity, loadFactor, true). 28 HashMap Code Example 1. The put method and looping through the values. Code // Create an empty hashmap. Map<Integer,Employee> hmEmployees = new HashMap<Integer,Employee>(); Output // Create some new employees. Employee e1 = new Employee("Lyton", "Xavier", 243558673, 77.88); Employee e2 = new Employee("Dern", "Donald", 476227851, 23.44); // Put students in hashmap, use SSN as key. // A successful put returns a "null" System.out.println( hmEmployees.put( e1.getSSNum(), e1) ); // If the key exists, then a replace occurs // and the old value is returned System.out.println( hmEmployees.put( e1.getSSNum(), e2) ); // Display values for( Employee e : hmEmployees.values() ) System.out.println( e ); null Lyton, Xavier - 243558673, 77.88 Dern, Donald - 476227851, 23.44 2. The get method and looping through the values. Code // Display values in hashmap for( Employee e : hmEmployees.values() ) System.out.println( e ); // Get a value by key // Successful System.out.println( hmEmployees.get( 243558673 ) ); // Unsuccessful System.out.println( hmEmployees.get( 123456789 ) ); Output Dern, Donald - 476227851, 23.44 Lyton, Xavier - 243558673, 77.88 Lyton, Nelly - 338290448, 82.45 Lyton, Xavier - 243558673, 77.88 null 29 3. Removing values. Code // Display values for( Employee e : hmEmployees.values() ) System.out.println( e ); // Successful System.out.println( hmEmployees.remove( 243558673 ) ); // Not successful System.out.println( hmEmployees.remove( 123456789 ) ); // Display values for( Employee e : hmEmployees.values() ) System.out.println( e ); 30 Output Dern, Donald - 476227851, 23.44 Lyton, Xavier - 243558673, 77.88 Lyton, Nelly - 338290448, 82.45 Lyton, Xavier - 243558673, 77.88 null Dern, Donald - 476227851, 23.44 Lyton, Nelly - 338290448, 82.45 4. The putAll method. Code // Create an empty hashmap. Map<Integer,Employee> hmEmployees = new HashMap<Integer,Employee>(); Output // Create some new employees. Employee e1 = new Employee("Lyton", "Xavier", 243558673, 77.88); Employee e2 = new Employee("Dern", "Donald", 476227851, 23.44); Employee e3 = new Employee("Lyton", "Nelly", 338290448, 82.45); Employee e4 = new Employee("Reilly", "Junita", 178890463, 82.45); Employee e5 = new Employee("Colter", "Ben", 883456321, 12.89); Employee e6 = new Employee("Mixton", "Zerby", 422789012, 22.76); // Clear hashmap and add some employees. hmEmployees.clear(); hmEmployees.put(e1.getSSNum(), e1); hmEmployees.put(e2.getSSNum(), e2); hmEmployees.put(e3.getSSNum(), e3); // Display hashmap 1 values. for( Employee e : hmEmployees.values() ) System.out.println( e ); Dern, Donald - 476227851, 23.44 Lyton, Xavier - 243558673, 77.88 Lyton, Nelly - 338290448, 82.45 // Create a 2nd hashmap and add some emps. Map<Integer,Employee> hmEmployees2 = new HashMap<Integer,Employee>(); hmEmployees2.put(e4.getSSNum(), e4); hmEmployees2.put(e5.getSSNum(), e5); hmEmployees2.put(e6.getSSNum(), e6); // Display hashmap 2 values. for( Employee e : hmEmployees2.values() ) System.out.println( e ); Colter, Ben - 883456321, 12.89 Reilly, Junita - 178890463, 82.45 Mixton, Zerby - 422789012, 22.76 // Put 2nd map into first. hmEmployees.putAll( hmEmployees2 ); // Display hashmap 1 values. for( Employee e : hmEmployees.values() ) System.out.println( e ); 31 Dern, Donald - 476227851, 23.44 Colter, Ben - 883456321, 12.89 Lyton, Xavier - 243558673, 77.88 Reilly, Junita - 178890463, 82.45 Lyton, Nelly - 338290448, 82.45 Mixton, Zerby - 422789012, 22.76 5. The containsKey and containsValue methods. Must override equals and hashCode. Overriding hashCode is beyond the scope of this class. We show both methods below. Code // See if a key is present. System.out.println( hmEmployees.containsKey( e5.getSSNum()) ); // Create a dummy employee. Use to search for by value. // Note how equals is defined in Employee class. Employee eDummy = new Employee("Colter", "Ben", 0,0 ); // See if a value is present. System.out.println( hmEmployees.containsValue( eDummy ) ); Output true true public boolean equals(Object o) { Employee e = (Employee)o; int lName = this.getLastName().compareTo(e.getLastName()); if( lName == 0 ) { int fName = this.getFirstName().compareTo(e.getFirstName()); if( fName == 0 ) return true; } return false; } public int hashCode() { int x = lName.hashCode(); int y = fName.hashCode(); int z = 23*x + 7*y; return z; } 32 6. The entrySet method, MapEntry class Code // Display hashmap values for( Employee e : hmEmployees.values() ) System.out.println( e ); Output Dern, Donald - 476227851, 23.44 Lyton, Xavier - 243558673, 77.88 Lyton, Nelly - 338290448, 82.45 // Create EntrySet Set<Map.Entry<Integer,Employee>> eSet = hmEmployees.entrySet(); // Create Iterator for EntrySet. Iterator<Map.Entry<Integer,Employee>> iter = eSet.iterator(); // Loop over EntrySet. while( iter.hasNext() ) { // Grab the map entry. Map.Entry<Integer,Employee> mapEntry = iter.next(); // Extract key and value. int key = mapEntry.getKey(); Employee value = mapEntry.getValue(); // Possibly change value. if( key == 338290448 ) { Employee eChanged = new Employee("Fudd", "Elmer", 338290448, 7.14); mapEntry.setValue( eChanged ); } } // Display hashmap values for( Employee e : hmEmployees.values() ) System.out.println( e ); 33 Dern, Donald - 476227851, 23.44 Lyton, Xavier - 243558673, 77.88 Fudd, Elmer - 338290448, 7.14 7. The keySet method, ordering the keys, using keys to access Employees Code // Display hashmap values for( Employee e : hmEmployees.values() ) System.out.println( e ); Output Dern, Donald - 476227851, 23.44 Lyton, Xavier - 243558673, 77.88 Fudd, Elmer - 338290448, 7.14 // Get set of keys. Set<Integer> keys = hmEmployees.keySet(); // Display unordered keys for( int key : keys ) System.out.println( key ); 476227851 243558673 338290448 // Order the keys. TreeSet<Integer> orderedKeys = new TreeSet<Integer>( keys ); // Display employees ordered by key. for( int key : orderedKeys ) System.out.println( hmEmployees.get(key) ); Lyton, Xavier - 243558673, 77.88 Fudd, Elmer - 338290448, 7.14 Dern, Donald - 476227851, 23.44 8. Manipulating the values Code // Display hashmap values for( Employee e : hmEmployees.values() ) System.out.println( e ); Output Dern, Donald - 476227851, 23.44 Lyton, Xavier - 243558673, 77.88 Fudd, Elmer - 338290448, 7.14 // Get values collection. Collection<Employee> cEmployees = hmEmployees.values(); // Turn collection into list List<Employee> lEmployees = new ArrayList<Employee>( cEmployees ); // Sort the list Collections.sort( lEmployees, new EmployeeNameComparator() ); // Display hashmap values for( Employee e : lEmployees ) System.out.println( e ); Dern, Donald - 476227851, 23.44 Fudd, Elmer - 338290448, 7.14 Lyton, Xavier - 243558673, 77.88 34 TreeMap Code Example Code // Create an empty treemap. SortedMap<Integer,Employee> tmEmployees = new TreeMap<Integer,Employee>(); Output // Create some new employees. Employee e1 = new Employee("Lyton", "Xavier", 243558673, 77.88); Employee e2 = new Employee("Dern", "Donald", 476227851, 23.44); Employee e3 = new Employee("Lyton", "Nelly", 945678345, 41.45); Employee e4 = new Employee("Reilly", "Junita", 338290448, 82.45); Employee e5 = new Employee("Colter", "Ben", 883456321, 12.89); Employee e6 = new Employee("Mixton", "Zerby", 422789012, 22.76); // Put students in treemap. tmEmployees.put(e1.getSSNum(), tmEmployees.put(e2.getSSNum(), tmEmployees.put(e3.getSSNum(), tmEmployees.put(e4.getSSNum(), tmEmployees.put(e5.getSSNum(), tmEmployees.put(e6.getSSNum(), e1); e2); e3); e4); e5); e6); // headMap example. SortedMap<Integer,Employee> smEmployees = tmEmployees.headMap( 555555555 ); // Display, headMap. for( Employee e : smEmployees.values() ) System.out.println( e ); Lyton, Xavier - 243558673, 77.88 Reilly, Junita - 338290448, 82.45 Zenton, Violet - 422789012, 22.76 Dern, Donald - 476227851, 23.44 // tailMap example. smEmployees = tmEmployees.tailMap( 555555555 ); // Display, tailMap. for( Employee e : smEmployees.values() ) System.out.println( e ); Colter, Ben - 883456321, 12.89 Lyton, Nelly - 945678345, 41.45 // subMap example. smEmployees = tmEmployees.subMap( 300000000, 900000000 ); // Display, subMap. for( Employee e : smEmployees.values() ) System.out.println( e ); 35 Reilly, Junita - 338290448, 82.45 Zenton, Violet - 422789012, 22.76 Dern, Donald - 476227851, 23.44 Colter, Ben - 883456321, 12.89 Sections 22.12 – Singleton & Unmodifiable Collections & Maps 1. Omit 36