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.