Programmazione Parametrica ( a.k.a. Generics ) • Introduzione ai meccanismi e concetti della programmazione parametrica • Generics e relationi di sottotipo wildcards generics e vincoli • Implemendaizone di classi e metodi parametrici • Supporto per i generics nella JVM Programmazione polimorfa • Polimorfo ~ multiforme, di molti tipi • Programmazione polimorfa: creazione di costrutti (classi e metodi) che possono essere utilizzati in modo uniforme su dati di tipo diverso In Java, tradizionalmente ottenuta mediante i meccanismi di sottotipo ed ereditarietà Da Java 1.5. mediante i meccanismi di parametrizzazione di tipo (a.k.a. generics) Variabili di Tipo • Le variabili (o parametri) di tipo pemettono di creare astrazioni di tipo • Classico caso di utilzzo nelle classi “Container” public class ArrayList<E> { public ArrayList() { . . . } public void add(E element) { . . . } . . . } • E = variabile di tipo astrae (e rappresenta) il tipo delle componenti Continua Variabili di Tipo • Possono essere istanziate con tipi classe o interfaccia ArrayList<BankAccount> ArrayList<Measurable> • Vincolo: tipi che istanziano variabili di tipo non possono essere primitivi (devono essere tipi riferimento) ArrayList<double> // No! • Classi wrapper utili allo scopo ArrayList<Double> Variabili di tipo e controlli di tipo • Utilizzare variabili di tipo nella programmazione permette maggiori controlli sulla correttezza dei tipi in fase di compilazione • Aumenta quindi la solidità e robustezza del codice Continua Variabili di tipo e controlli di tipo • Un classico caso di utilizzo di containers List intList = new LinkedList(); intList.add(new Integer(0)); Integer x = (Integer) intList.iterator().next(); • Il cast è problematico, per vari motivi verboso, fonte di errori a run time • Ma necessario per la compilazione e per localizzare l’eventuale errore a run time Continua Variabili di tipo e controlli di tipo • Container generici: più sintetici ed eleganti List<Integer> intList = new LinkedList<Integer>(); intList.add(new Integer(0)); Integer x = intList.iterator().next(); • Compilatore può stabilire un invariante sugli elementi della lista garantire l’assenza di errori a run-time in forza di quell’invariante. Continua Variabili di tipo e controlli di tipo List<Integer> intList = new LinkedList<Integer>(); intList.add(new Integer(0)); Integer x = intList.iterator().next(); • Ora non è possibile aggiungere una stringa ad intlist:List<Integer> • Le variabili di tipo rendono il codice parametrico più robusto e semplice da leggere e manutenere Classi parametriche: definizione • Un frammento delle interfacce List e Iterator nel package java.util.* // nulla di particolare, a parte i parametri // tra parentesi angolate public interface List<E> { void add(E x); Iterator<E> iterator(); } public interface Iterator<E> { E next(); boolean hasNext(); } Classi parametriche: uso • Quando utilizziamo un tipo parametrico, tutte le occorrenze dei parametri formali sono rimpiazzate dall’argomento (parametro attuale) • Meccanismo simile a quello del passaggio dei parametri in un metodo • Diversi usi generano tipi diversi • Ma . . . classi parametriche compilate una sola volta danno luogo ad un unico file .class Sintassi: uso GenericClassName<Type1, Type2, . . .> Esempio: ArrayList<BankAccount> HashMap<String, Integer> Scopo: Fornire tipo specifici per ciascuna delle variabili di tipo introdotte nella dichiarazione Esempio: Pair<T,S> • Una semplice classe parametrica per rappresentare coppie di oggetti public class Pair<T, S> { public Pair(T firstElement, S secondElement) { first = firstElement; second = secondElement; } public T getFirst() { return first; } public S getSecond() { return second; } private T first; private S second; } Continua Esempio: Pair<T,S> • Una semplice classe parametrica per rappresentare coppie di oggetti: Pair<String, BankAccount> result = new Pair<String, BankAccount> ("Harry Hacker", harrysChecking); • I metodi getFirst e getSecond restituiscono il primo e secondo elemento, con i tipi corrispondenti String name = result.getFirst(); BankAccount account = result.getSecond(); Variabili di tipo: convenzioni Variabile Significato Inteso E Tipo degli elementi in una collezione K Tipo delle chiavi in una mappa V Tipo dei valori in una mappa T,S,U Tipi generici Esempio: LinkedList<E> public class LinkedList<E> { . . . public E removeFirst() { if (first == null) throw new NoSuchElementException(); E element = first.data; first = first.next; return element; } . . . private Node first; private class Node { E data; Node next; } } Continua Esempio: LinkedList<E> private class Node<F> { F data; Node next; } public class LinkedList<E> { . . . public E removeFirst() { if (first == null) throw new NoSuchElementException(); E element = first.data; first = first.next; return element; } . . . private Node<E> first; } Continua Esempio: LinkedList<E> • Notiamo la struttura della classe ausiliaria che specifica la struttura dei nodi • Se la classe è interna, come in questo caso, non serve alcun accorgimento all’interno di Node possiamo utilizzare il tipo E, il cui scope è tutta la classe • Se invece la classe è esterna, dobbiamo renderla generica public class ListNode<E> File LinkedList.java 001: 002: 003: 004: 005: 006: 007: 008: 009: 010: 011: 012: 013: 014: 015: 016: 017: 018: import java.util.NoSuchElementException; /** A linked list is a sequence of nodes with efficient element insertion and removal. This class contains a subset of the methods of the standard java.util.LinkedList class. */ public class LinkedList<E> { /** Constructs an empty linked list. */ public LinkedList() { first = null; } Continua File LinkedList.java 019: 020: 021: 022: 023: 024: 025: 026: 027: 028: 029: 030: 031: 032: 033: 034: 035: /** Returns the first element in the linked list. @return the first element in the linked list */ public E getFirst() { if (first == null) throw new NoSuchElementException(); return first.data; } /** Removes the first element in the linked list. @return the removed element */ public E removeFirst() Continua { File LinkedList.java 036: 037: 038: 039: 040: 041: 042: 043: 044: 045: 046: 047: 048: 049: 050: 051: 052: 053: if (first == null) throw new NoSuchElementException(); E element = first.data; first = first.next; return element; } /** Adds an element to the front of the linked list. @param element the element to add */ public void addFirst(E element) { Node newNode = new Node(); newNode.data = element; newNode.next = first; first = newNode; } Continua File LinkedList.java 054: 055: 056: 057: 058: 059: 060: 061: 062: 063: 064: 065: 066: 067: 068: 069: 070: 071: /** Returns an iterator for iterating through this list. */ public ListIterator<E> listIterator() { return new LinkedListIterator(); } private Node first; private class Node { E data; Node next; } Continua LinkedListIterator • La definiamo come classe interna di LinkedList<E> • Implements l’interfaccia ListIterator<E> • Ha accesso al campo first e alla classe interna Node Classe LinkedListIterator 072: 073: 074: 075: 076: 077: 078: 079: 080: 081: 082: 083: private class LinkedListIterator implements ListIterator<E> { /** Costruisce un iteratore posizionato sul primo elemento della lista */ public LinkedListIterator() { last = null; // ultimo nodo visitato previous = null; // precedente a position } Continua LinkedListIterator – next() 084: 085: 086: 087: 088: 089: 090: 091: 092: 093: 094: 095: 096: 097: 098: 099: 100: 101: 102: /** Posizione l’iteratore sul prossimo. @return il prossimo elemento */ public E next() { if (!hasNext()) throw new NoSuchElementException(); previous = last; // Remember for remove if (last == null) last = first; else last = last.next; return last.data; } Continua LinkedListIterator – hasNext() 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: /** Testa se c’è un elemento dopo l’ultimo visitato. @return true se esiste un elemento dopo l’ultimo visitato */ public boolean hasNext() { if (last == null) return first != null; else return last.next != null; } Continua LinkedListIterator – add() 117: 118: 119: 121: 122: 123: 124: 125: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: /** Aggiunge un elemento dopo last e sposta l’iteratore sul prossimo elemento. */ public void add(E element) { if (last == null) { addFirst(element); last = first; } else { Node newNode = new Node(); newNode.data = element; newNode.next = last.next;// (1) last.next = newNode; // (2) last = newNode; // (3) } previous = last; // (4) Continua } LinkedListIterator – add() last LinkedListIterator – remove() 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: /** Rimuove l’elemento puntato da last. Può essere invocato solo dopo una chiamata a next() */ public void remove() { if (previous == last) throw new IllegalStateException(); if (last == first) { removeFirst(); } else { previous.next = last.next; // (1) } last = previous; // (2) } Continua LinkedListIterator –remove() last Classe LinkedListIterator 159: /** 160: Sets the last traversed element to a different 161: value. 162: @param element the element to set 163: */ 164: public void set(E element) 165: { 166: if (position == null) 167: throw new NoSuchElementException(); 168: position.data = element; 169: } 170: 171: private Node position; 172: private Node previous; 173: } // end LinkedListIterator 174: } // end LinkedList File ListIterator.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: /** A list iterator allows access of a position in a linked list. This interface contains a subset of the methods of the standard java.util.ListIterator interface. The methods for backward traversal are not included. */ public interface ListIterator<E> { /** Moves the iterator past the next element. @return the traversed element */ E next(); /** Tests if there is an element after the iterator position. Continua File ListIterator.java 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: @return true if there is an element after the iterator position */ boolean hasNext(); /** Adds an element before the iterator position and moves the iterator past the inserted element. @param element the element to add */ void add(E element); /** Removes the last traversed element. This method may only be called after a call to the next() method. */ Continua File ListIterator.java 34: 35: 36: 37: 38: 39: 40: 41: 42: } void remove(); /** Sets the last traversed element to a different value. @param element the element to set */ void set(E element); Generics e sottotipi • Consideriamo List<Integer> li = new ArrayList<Integer>(); List<Number> ln = li; • La prima istruzione è sicuramente legale, la seconda è più delicata … Number è una classe astratta che ha Integer , Double e altre classi wrapper come sottotipi Per capire se la seconda istruzione sia da accettare come legale continuiamo con l’esempio … Continua Generics e sottotipi List<Integer> li = new ArrayList<Integer>(); List<Number> ln = li; ln.add(3.14); Integer i = li.get(0); // uh oh ... • Come si vede abbiamo un problema nella terza istruzione inseriamo un Double (via autoboxing, ricordate?) nella quarta estraiamo un Integer ! • Il vero problema è nella seconda istruzione soluzione: errore di compilazione per l’assegnamento al secondo Continua Generics e sottotipi • In generale, dati due tipi A e B , ed tipo generico C<T> abbiamo che: A ≤ B non implica C<A> ≤ C<B> • Quindi, per le stesse ragioni di prima Set<Integer> non è sottotipo di Set<Object> Continua Generics e sottotipi • Le limitazione sulle relazioni di sottotipo sono contro-intuitive uno degli aspetti più complessi dei generics • Non solo … sono anche decisamente troppo restrittive illustriamo con un esempio Continua Generics e sottotipi • Un metodo che stampa gli elementi di una collezione • Versione tradizionale void printCollection(Collection c) { Iterator i = c.iterator(); for (k = 0; k < c.size(); k++) { System.out.println(i.next()); } } Continua Generics e sottotipi • Stampa degli elementi di una collezione • Versione generics: primo tentativo void printCollection(Collection<Object> c) { for (Object e:c) System.out.println(e); } utile solo per Collection<Object> non per qualsiasi collezione Collection<Object> non è il supertipo di tutte le collezioni Continua Wildcards • Stampa degli elementi di una collezione • Versione generics: secondo tentativo void printCollection(Collection<?> c) { for (Object e:c) System.out.println(e); } Collection<?> è il supertipo di tutte le Collections la wildcard ? indica un qualche tipo, non specificato Continua Wildcards void printCollection(Collection<?> c) { for (Object e:c) System.out.println(e); } • Possiamo estrarre gli elementi di c al tipo Object • Corretto perché, qualunque sia il loro vero tipo, sicuramente è sottotipo di Object Continua Wildcards • D’altra parte … Collection<?> c = new ArrayList<String>(); c.add(new String()); // errore di compilazione! • visto che non sappiamo esattamente quale tipo indica ?, non possiamo assegnare valori Continua Domanda • Date un esempio di codice che causerebbe errore in esecuzione se permettessimo di aggiungere elementi a Collection<?> Risposta Collection<Integer> ci = new ArrayList<Integer>; Colletion<?> c = ci; c.add(“a string”); ci.get(0).intValue(); • L’ultima istruzione invocherebbe intValue() sul primo elemento di ci • ma quell’elemento ha tipo String … • In realtà il compilatore anticipa l’errore, segnalando il problema sulla add() Wilcards con vincoli (bounded) • Shapes: un esempio classico public abstract class Shape { public abstract void draw(Graphics g); } public class Circle extends Shape { private int x, y, radius; public void draw(Graphics g) { ... } } public class Rectangle extends Shape { private int x, y, width, height; public void draw(Graphics g) { ... } } Continua Wilcards con vincoli (bounded) • Graphics e il metodo draw() public class Graphics { // disegna una shape public void draw(Shape s) { s.draw(this); } // disegna tutte le shapes di una lista public void drawAll(List<Shape> shapes) { for (Shape s:shapes) s.draw(this) } . . . } • Solito problema: drawAll() non può essere invocato su una List<Circle> Continua Bounded Wilcards • Quello che ci serve è un metodo che accetti liste di qualunque (sotto) tipo di Shape void drawAll(List<? extends Shape> shapes) { ... } • List<? extends Shape> bounded wildcard indica un tipo sconosciuto, sottotipo di Shape il bound può essere qualunque tipo riferimento (classe o interfaccia) • Ora il metodo ha la flessibilità necessaria e desiderata Continua Bounded Wilcards • Graphics e il metodo draw() public class Graphics { // disegna una shape public void draw(Shape s) { s.draw(this); } // disegna tutte le shapes di una lista public void drawAll(List<? extends Shape> shapes) { for (Shape s:shapes) s.draw(this) } . . . } Continua Bounded Wilcards • D’altra parte, c’è sempre un prezzo da pagare • Il solito vincolo … void addRectangle(List<? extends Shape> shapes) { // errore di compilazione shapes.add(new Rectangle()); } • Non possiamo modificare strutture con questi tipi [ perché? ] Metodi Generici • Metodi che dipendono da una variabile di tipo • Possono essere definiti all’interno di qualunque classe, generica o meno /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static void array2List(Object[] a, List<?> l){ . . . } • N.B. Evitiamo List<Object> perché List<Object> renderebbe il metodo poco utile (non utilizzabie su liste arbitrarie) Continua Metodi Generici • Al solito però . . . /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static void array2List(Object[] a, List<?> l) { for (Object o : a) l.add(o) // compiler error } • . . . non possiamo aggiungere elementi ad una struttura (o modificare) con elementi di tipo wildcard Continua Metodi Generici • Soluzione: rendiamo il metodo parametrico /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static <T> void array2List(T[] a, List<T> l) { for (T o : a) l.add(o) } • possiamo invocare questo metodo con una qualunque lista il cui tipo sia supertipo del tipo base dell’array purché sia un tipo riferimento Invocazione di metodi generici • Nell’invocazione di un metodo generico non è necessario passare l’argomento di tipo • il compilatore inferisce il tipo, se esiste, dai tipi degli argomenti del metodo Invocazione di metodi generici • Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromArrayToCollection(oa, co); // T = Object (inferito) String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); fromArrayToCollection(sa, cs); // T = String (inferito) fromArrayToCollection(sa, co); // T = Object (inferito) Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection<Number> cn = new ArrayList<Number>(); fromArrayToCollection(ia, cn); // T = Number (inferito) fromArrayToCollection(fa, cn); // T = Number (inferito) fromArrayToCollection(na, cn); // T = Number (inferito) fromArrayToCollection(na, co); // T = Object (inferito) fromArrayToCollection(na, cs); // compiler error Continua Wildarcds vs variabili di tipo • Ci sono situazioni in cui è possibili usare equivalentemente wildcards e variabili di tipo. • Nella libreria Collection troviamo interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); . . . } Continua Wildarcds vs variabili di tipo • Queste specifiche possono essere espresse equivalentemente con metodi parametrici interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); . . . } • Il secondo metodo è parametrico in qualunque sottotipo di E i bounds si possono utilizzare anche con variabili, non solo con wildcards Continua Wildarcds vs variabili di tipo • Wildcards e variabili di tipo possono coesistere interface Collection<E> { public static <T> void copy(List<T> dest, List<? extends T> src) . . . } • Notiamo la dipendenza tra i tipi dei due parametri: il tipo della sorgente deve essere un sottotipo del tipo della destinazione Continua Wildarcds vs variabili di tipo • Potremmo analogamente riformulare in modo da evitare le wildcards interface Collection<E> { public static <T, S extends T> void copy(<List<T> dest, List<S> src) . . . } • Come scegliere tra le due soluzioni? Continua Wildarcds vs variabili di tipo • In generale, preferiamo le wildcards quando entrambe le soluzioni sono possibili • Possiamo darci la seguente “rule of thumb” se una variabile di tipo ha una unica occorrenza nella specifica di un metodo e il tipo non è il target di un operazione di modifica utilizziamo una wildcard al posto della variabile Variabili di Tipo e Bounds • Abbiamo visto che possiamo definire bounds anche per variabili di tipo (non solo wildcards) • Un caso paradigmatico public static <T extends Comparable<T>> T max(Collection<T> coll) { T candidate = coll.iterator().next(); for (T e : coll) if candidate.compareTo(e) < 0) candidate = e; return candidate; } Variabili di Tipo e Bounds • Il bound su una variabile impone vincoli sulla variabile, determinando quali metodi possono essere utilizzati su valori del tipo variabile public static <T extends Comparable<T>> T max(T max(List <T> coll) • Qui il bound è ricorsivo: informa che i valori con cui operiamo forniscono un metodo compareTo() che gli argomenti del metodo devono essere dello stesso tipo dei valori Generics e “erasure” • I tipi generici sono significativi a compile-time • La JVM opera invece con tipi “raw” • Il tipo raw è ottenuto da un tipo generico mediante un processo detto erasure che rimuove le variabili di tipo il bycode generato da un tipo generico è lo stesso che viene generato dal corrispondente tipo raw. Generics e “erasure” • Generano lo stesso bytecode List<Strin> words = new ArrayList<String>(); words.add(“hi”); words.add(“there”); String welcome = words.get(0) + words.get(1); List words = new ArrayList(); words.add(“hi”); words.add(“there”); String welcome = (String)words.get(0) + (String)words.get(1); Generics e “erasure” • Cast-iron guarantee i cast impliciti che vengono aggiunti dalla compilazione di codice generico non falliscono mai. • Constraining Type Variables • Very occasionally, you need to supply two or more type bounds <E extends Comparable & Cloneable> • extends, when applied to type variables, actually means "extends or implements" • The bounds can be either classes or interfaces • Type variable can be replaced with a class or interface type Raw Types • For example, generic class Pair<T, S> turns into the following raw class: public class Pair { public Pair(Object firstElement, Object secondElement) { first = firstElement; second = secondElement; } public Object getFirst() { return first; } public Object getSecond() { return second; } private Object first; private Object second; } Raw Types • Knowing about raw types helps you understand limitations of Java generics • For example, you cannot replace type variables with primitive types Self Check 9. What is the erasure of the print method in Section 22.3? 10. What is the raw type of the LinkedList<E> class in Section 22.2? Answers 9. public static void print(Object[] a) { for (Object e : a) System.out.print(e + " "); System.out.println(); } 10. The LinkedList class of Chapter 20. Chapter 21 Advanced Data Structures Chapter Goals • To learn about the set and map data types • To understand the implementation of hash tables • To be able to program hash functions • To learn about binary trees • To be able to use tree sets and tree maps Continued Chapter Goals • To become familiar with the heap data structure • To learn how to implement the priority queue data type • To understand how to use heaps for sorting Sets • Set: unordered collection of distinct elements • Elements can be added, located, and removed • Sets don't have duplicates A Set of Printers Figure 1: A Set of Printers Fundamental Operations on a Set • Adding an element Adding an element has no effect if the element is already in the set • Removing an element Attempting to remove an element that isn't in the set is silently ignored • Containment testing (does the set contain a given object?) • Listing all elements (in arbitrary order) Sets • We could use a linked list to implement a set Adding, removing, and containment testing would be relatively slow • There are data structures that can handle these operations much more quickly Hash tables Trees Continued Sets • Standard Java library provides set implementations based on both data structures HashSet TreeSet • Both of these data structures implement the Set interface Set Classes and Interface in the Standard Library Figure 2: Set Classes and Interfaces in the Standard Library Iterator • Use an iterator to visit all elements in a set • A set iterator does not visit the elements in the order in which they were inserted • An element can not be added to a set at an iterator position • A set element can be removed at an iterator position Code for Creating and Using a Hash Set • //Creating a hash set Set<String> names = new HashSet<String>(); • //Adding an element names.add("Romeo"); • //Removing an element names.remove("Juliet"); • //Is element in set if (names.contains("Juliet") { . . .} Listing All Elements with an Iterator Iterator<String> iter = names.iterator(); while (iter.hasNext()) { String name = iter.next(); Do something with name } // Or, using the "for each" loop for (String name : names) { Do something with name } File SetTester.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: import import import import java.util.HashSet; java.util.Iterator; java.util.Scanner; java.util.Set; /** This program demonstrates a set of strings. The user can add and remove strings. */ public class SetTester { public static void main(String[] args) { Set<String> names = new HashSet<String>(); Scanner in = new Scanner(System.in); Continued File SetTester.java 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: boolean done = false; while (!done) { System.out.print("Add name, Q when done: "); String input = in.next(); if (input.equalsIgnoreCase("Q")) done = true; else { names.add(input); print(names); } } done = false; while (!done) { Continued File SetTester.java 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: System.out.println("Remove name, Q when done"); String input = in.next(); if (input.equalsIgnoreCase("Q")) done = true; else { names.remove(input); print(names); } } } /** Prints the contents of a set of strings. @param s a set of strings */ private static void print(Set<String> s) { Continued File SetTester.java 53: 54: 55: 56: 57: 58: 59: 60: 61: } 62: 63: System.out.print("{ "); for (String element : s) { System.out.print(element); System.out.print(" "); } System.out.println("}"); } Continued File SetTester.java • Output Add name, Q when done: Dick { Dick } Add name, Q when done: Tom { Tom Dick } Add name, Q when done: Harry { Harry Tom Dick } Add name, Q when done: Tom { Harry Tom Dick } Add name, Q when done: Q Remove name, Q when done: Tom { Harry Dick } Remove name, Q when done: Jerry { Harry Dick } Remove name, Q when done: Q Self Test 1. Arrays and lists remember the order in which you added elements; sets do not. Why would you want to use a set instead of an array or list? 2. Why are set iterators different from list iterators? Answers 1. Efficient set implementations can quickly test whether a given element is a member of the set. 2. Sets do not have an ordering, so it doesn't make sense to add an element at a particular iterator position, or to traverse a set backwards. Maps • A map keeps associations between key and value objects • Mathematically speaking, a map is a function from one set, the key set, to another set, the value set • Every key in a map has a unique value • A value may be associated with several keys • Classes that implement the Map interface HashMap TreeMap An Example of a Map Figure 3: An Example of a Map Map Classes and Interfaces Figure 4: Map Classes and Interfaces in the Standard Library Code for Creating and Using a HashMap • //Changing an existing association favoriteColor.put("Juliet",Color.RED); • //Removing a key and its associated value favoriteColors.remove("Juliet"); Code for Creating and Using a HashMap • • • //Creating a HashMap Map<String, Color> favoriteColors = new HashMap<String, Color>(); //Adding an association favoriteColors.put("Juliet", Color.PINK); //Changing an existing association favoriteColor.put("Juliet",Color.RED); Continued Code for Creating and Using a HashMap • • //Getting the value associated with a key Color julietsFavoriteColor = favoriteColors.get("Juliet"); //Removing a key and its associated value favoriteColors.remove("Juliet"); Printing Key/Value Pairs Set<String> keySet = m.keySet(); for (String key : keySet) { Color value = m.get(key); System.out.println(key + "->" + value); } File MapTester.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: import import import import import java.awt.Color; java.util.HashMap; java.util.Iterator; java.util.Map; java.util.Set; /** This program tests a map that maps names to colors. */ public class MapTester { public static void main(String[] args) { Map<String, Color> favoriteColors = new HashMap<String, Color>(); favoriteColors.put("Juliet", Color.pink); favoriteColors.put("Romeo", Color.green); Continued File MapTester.java 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: } favoriteColors.put("Adam", Color.blue); favoriteColors.put("Eve", Color.pink); Set<String> keySet = favoriteColors.keySet(); for (String key : keySet) { Color value = favoriteColors.get(key); System.out.println(key + "->" + value); } } Continued File MapTester.java • Output Romeo->java.awt.Color[r=0,g=255,b=0] Eve->java.awt.Color[r=255,g=175,b=175] Adam->java.awt.Color[r=0,g=0,b=255] Juliet->java.awt.Color[r=255,g=175,b=175] Self Check 3. What is the difference between a set and a map? 4. Why is the collection of the keys of a map a set? Answers 3. A set stores elements. A map stores associations between keys and values. 4. The ordering does not matter, and you cannot have duplicates. Hash Tables • Hashing can be used to find elements in a data structure quickly without making a linear search • A hash table can be used to implement sets and maps • A hash function computes an integer value (called the hash code) from an object Continued Hash Tables • A good hash function minimizes collisions– identical hash codes for different objects • To compute the hash code of object x: int h = x.hashCode(); Sample Strings and Their Hash Codes String Hash Code "Adam" 2035631 "Eve" 70068 "Harry" 69496448 "Jim" 74478 "Joe" 74676 "Juliet" 2065036585 "Katherine" 2079199209 "Sue" 83491 Simplistic Implementation of a Hash Table • To implement Generate hash codes for objects Make an array Insert each object at the location of its hash code • To test if an object is contained in the set Compute its hash code Check if the array position with that hash code is already occupied Simplistic Implementation of a Hash Table Figure 5: A Simplistic Implementation of a Hash Table Problems with Simplistic Implementation • It is not possible to allocate an array that is large enough to hold all possible integer index positions • It is possible for two different objects to have the same hash code Solutions • Pick a reasonable array size and reduce the hash codes to fall inside the array int h = x.hashCode(); if (h < 0) h = -h; h = h % size; • When elements have the same hash code: Use a node sequence to store multiple objects in the same array position These node sequences are called buckets Hash Table with Buckets to Store Elements with Same Hash Code Figure 6: A Hash Table with Buckets to Store Elements with Same Hash Code Algorithm for Finding an Object x in a Hash Table • Get the index h into the hash table Compute the hash code Reduce it modulo the table size • Iterate through the elements of the bucket at position h For each element of the bucket, check whether it is equal to x • If a match is found among the elements of that bucket, then x is in the set Otherwise, x is not in the set Hash Tables • A hash table can be implemented as an array of buckets • Buckets are sequences of nodes that hold elements with the same hash code • If there are few collisions, then adding, locating, and removing hash table elements takes constant time Big-Oh notation: O(1) Continued Hash Tables • For this algorithm to be effective, the bucket sizes must be small • The table size should be a prime number larger than the expected number of elements An excess capacity of 30% is typically recommended Hash Tables • Adding an element: simple extension of the algorithm for finding an object Compute the hash code to locate the bucket in which the element should be inserted Try finding the object in that bucket If it is already present, do nothing; otherwise, insert it Continued Hash Tables • Removing an element is equally simple Compute the hash code to locate the bucket in which the element should be inserted Try finding the object in that bucket If it is present, remove it; otherwise, do nothing • If there are few collisions, adding or removing takes O(1) time File HashSet.java 001: 002: 003: 004: 005: 006: 007: 008: 009: 010: 011: 012: 013: 014: 015: 016: import java.util.AbstractSet; import java.util.Iterator; import java.util.NoSuchElementException; /** A hash set stores an unordered collection of objects, using a hash table. */ public class HashSet extends AbstractSet { /** Constructs a hash table. @param bucketsLength the length of the buckets array */ public HashSet(int bucketsLength) Continued { File HashSet.java 017: 018: 019: 020: 021: 022: 023: 024: 025: 026: 027: 028: 029: 030: 031: 032: 033: 034: buckets = new Node[bucketsLength]; size = 0; } /** Tests for set membership. @param x an object @return true if x is an element of this set */ public boolean contains(Object x) { int h = x.hashCode(); if (h < 0) h = -h; h = h % buckets.length; Node current = buckets[h]; while (current != null) { Continued File HashSet.java 035: 036: 037: 038: 039: 040: 041: 042: 043: 044: 045: 046: 047: 048: 049: 050: 051: 052: if (current.data.equals(x)) return true; current = current.next; } return false; } /** Adds an element to this set. @param x an object @return true if x is a new object, false if x was already in the set */ public boolean add(Object x) { int h = x.hashCode(); if (h < 0) h = -h; h = h % buckets.length; Continued File HashSet.java 053: 054: 055: 056: 057: 058: 059: 060: 061: 062: 063: 064: 065: 066: 067: Node current = buckets[h]; while (current != null) { if (current.data.equals(x)) return false; // Already in the set current = current.next; } Node newNode = new Node(); newNode.data = x; newNode.next = buckets[h]; buckets[h] = newNode; size++; return true; } Continued File HashSet.java 068: 069: 070: 071: 072: 073: 074: 075: 076: 077: 078: 079: 080: 081: 082: 083: 084: 085: /** Removes an object from this set. @param x an object @return true if x was removed from this set, false if x was not an element of this set */ public boolean remove(Object x) { int h = x.hashCode(); if (h < 0) h = -h; h = h % buckets.length; Node current = buckets[h]; Node previous = null; while (current != null) { if (current.data.equals(x)) { Continued File HashSet.java 086: 087: 088: 089: 090: 091: 092: 093: 094: 095: 096: 097: 098: 099: 100: 101: 102: 103: 104: if (previous == null) buckets[h] = current.next; else previous.next = current.next; size--; return true; } previous = current; current = current.next; } return false; } /** Returns an iterator that traverses the elements of this set. @param a hash set iterator */ public Iterator iterator() { return new HashSetIterator(); } Continued File HashSet.java 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: /** Gets the number of elements in this set. @return the number of elements */ public int size() { return size; } private Node[] buckets; private int size; private class Node { public Object data; public Node next; } Continued File HashSet.java 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: private class HashSetIterator implements Iterator { /** Constructs a hash set iterator that points to the first element of the hash set. */ public HashSetIterator() { current = null; bucket = -1; previous = null; previousBucket = -1; } public boolean hasNext() { if (current != null && current.next != null) return true; Continued File HashSet.java 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: for (int b = bucket + 1; b < buckets.length; b++) if (buckets[b] != null) return true; return false; } public Object next() { previous = current; previousBucket = bucket; if (current == null || current.next == null) { // Move to next bucket bucket++; while (bucket < buckets.length && buckets[bucket] == null) bucket++; Continued File HashSet.java 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: if (bucket < buckets.length) current = buckets[bucket]; else throw new NoSuchElementException(); } else // Move to next element in bucket current = current.next; return current.data; } public void remove() { if (previous != null && previous.next == current) previous.next = current.next; else if (previousBucket < bucket) buckets[bucket] = current.next; else throw new IllegalStateException(); Continued File HashSet.java 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: } current = previous; bucket = previousBucket; } private private private private } int bucket; Node current; int previousBucket; Node previous; File SetTester.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: import java.util.Iterator; import java.util.Set; /** This program tests the hash set class. */ public class SetTester { public static void main(String[] args) { HashSet names = new HashSet(101); // 101 is a prime names.add("Sue"); names.add("Harry"); names.add("Nina"); names.add("Susannah"); names.add("Larry"); names.add("Eve"); Continued File SetTester.java 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: } names.add("Sarah"); names.add("Adam"); names.add("Tony"); names.add("Katherine"); names.add("Juliet"); names.add("Romeo"); names.remove("Romeo"); names.remove("George"); Iterator iter = names.iterator(); while (iter.hasNext()) System.out.println(iter.next()); } Continued File SetTester.java • Output Harry Sue Nina Susannah Larry Eve Sarah Adam Juliet Katherine Tony Self Check 5. If a hash function returns 0 for all values, will the HashSet work correctly? 6. What does the hasNext method of the HashSetIterator do when it has reached the end of a bucket? Answers 5. Yes, the hash set will work correctly. All elements will be inserted into a single bucket. 6. It locates the next bucket in the bucket array and points to its first element.