gianluca.aguzzi@unibo.it
angelo.filaseta@unibo.it
Questo materiale è ampiamente basato su quello realizzato dai Prof. Mirko Viroli e Roberto Casadei, che ringrazio.
Ogni errore riscontratovi è esclusiva responsabilità degli autori di questo documento.
Un oggetto è ottenuto per composizione di oggetti di altre classi
Una nuova classe è ottenuta riusando il codice di una classe pre-esistente
Una funzionalità realizzata per lavorare su valori/oggetti del tipo A
, può lavorare con qualunque valore/oggetto del sottotipo B
(p.e., se B
estende la classe A
, o se B
implementa l’interfaccia A
)
Una funzionalità (classe o metodo) generica è costruita in modo tale da lavorare uniformemente su valori/oggetti indipendentemente dal loro tipo: tale tipo diventa quindi una sorta di parametro addizionale
Device
IntVector
IntVector
UseIntVector
public class UseIntVector {
public static void main(String[] s) {
final IntVector vi = new IntVector();
// Serie di Fibonacci: fib(0)=fib(1)=1, fib(N)=fib(N-1)+fib(N-2) if N>1
vi.addElement(1);
vi.addElement(1);
for (int i = 0; i < 20; i++) {
vi.addElement(
vi.getElementAt(vi.getLength() - 1) + // ultimo
vi.getElementAt(vi.getLength() - 2) // penultimo
);
}
System.out.println(vi);
// |1|1|2|3|5|8|13|21|34|55|89|144|233|..
// 377|610|987|1597|2584|4181|6765|10946|17711|
}
}
IntVector
– implementazioneIntVector
IntVector
pt 1public class IntVector {
private static final int INITIAL_SIZE = 10;
private int[] elements; // Deposito per gli elementi
private int size; // Numero di elementi
public IntVector(){ // Inizialmente vuoto
this.elements = new int[INITIAL_SIZE];
this.size = 0;
}
public void addElement(final int e) {
if (this.size == elements.length) {
this.expand(); // Se non c'è più spazio..
}
this.elements[this.size] = e;
this.size++;
}
public int getElementAt(final int position) {
return this.elements[position];
}
IntVector
pt 2 public int getLength() {
return this.size;
}
private void expand() { // Raddoppio lo spazio..
final int[] newElements = new int[this.elements.length*2];
for (int i=0; i < this.elements.length; i++){
newElements[i] = this.elements[i];
}
this.elements = newElements;
//this.elements = java.util.Arrays.copyOf(this.elements, this.elements.length*2);
}
public String toString() {
String s="|";
for (int i=0; i < size; i++){
s = s + this.elements[i] + "|";
}
return s;
}
}
int
?float
, double
, boolean
, … ossia di ogni tipo primitivoString
, Date
, ecceteraObjectVector
, semplicemente sostituendo int
con Object
ObjectVector
è monomorfica in quanto “vede” un solo tipo, Object
(sebbene i riferimenti agli Object
siano polimorfici)IntVector
a ObjectVector
UseObjectVector
public class UseObjectVector {
public static void main(String[] s) {
// Serie di Fibonacci
final ObjectVector vobj = new ObjectVector();
// fib(0)=fib(1)=1, fib(N)=fib(N-1)+fib(N-2) if N>1
vobj.addElement(1); // grazie all'autoboxing
vobj.addElement(1);
for (int i = 0; i < 20; i++) {
vobj.addElement( // servono downcast specifici
(Integer) vobj.getElementAt(vobj.getLength() - 1)
+ (Integer) vobj.getElementAt(vobj.getLength() - 2));
}
System.out.println(vobj);
// |1|1|2|3|5|8|13|21|34|55|89|144|233|..
// 377|610|987|1597|2584|4181|6765|10946|17711|
// Altro esempio
final ObjectVector vobj2 = new ObjectVector();
vobj2.addElement("Prova");
vobj2.addElement("di");
vobj2.addElement("vettore");
vobj2.addElement(new Object());
System.out.println(vobj2);
String str = (String) vobj2.getElementAt(1); // "di"
// String str2 = (String)vobj2.getElementAt(3); // Exception
}
}
ObjectList
/* Lista linkata di oggetti, con soli metodi getter */
public class ObjectList {
private final Object head;
private final ObjectList tail;
public ObjectList(final Object head, final ObjectList tail) {
this.head = head;
this.tail = tail;
}
public Object getHead() { // Testa della lista
return this.head;
}
public ObjectList getTail() { // Coda della lista
return this.tail;
}
public int getLength() { // Dimensione della lista
return (this.tail == null) ? 1 : 1 + this.tail.getLength();
}
public String toString() { // Rappr. a stringa
return "|" + this.head
+ ((this.tail == null) ? "|" : this.tail.toString());
}
}
UseObjectList
public class UseObjectList {
public static void main(String[] s) {
final ObjectList list =
new ObjectList(10, new ObjectList(20,
new ObjectList(30, new ObjectList(40, null))));
// Cast necessari, eccezioni possibili
final int first = (Integer) list.getHead(); // Unboxing
final int second = (Integer) list.getTail().getHead();
final int third = (Integer) list.getTail().getTail().getHead();
System.out.println(first + " " + second + " " + third);
System.out.println(list.toString());
System.out.println(list.getLength());
// Usabile anche con le stringhe
final ObjectList list2 = new ObjectList("a",
new ObjectList("b",
new ObjectList("c",
new ObjectList("d", null))));
System.out.println(list2.toString());
}
}
ObjectVector
o ObjectList
Integer
? solo delle String
?ClassCastException
Il problema si manifesta ogni volta che voglio collezionare oggetti il cui tipo non è noto a priori, ma potrebbe essere soggetto a polimorfismo inclusivo
F
che lavora su un certo tipo, diciamo String
, se può lavorare in modo uniforme su altri tipi…String
una sorta di variabile o parametro X
(chiamata type variable o type parameter, ossia una variabile/parametro che denota un tipo)
F<String>
, ossia si richiede che X
diventi String
F<Integer>
javac
“compila via i generici”, quindi la JVM non li vedeList
List
si dice che è un tipo parametrico (in quanto accetta il “parametro di tipo” X
)/* Classe generica in X:
- X è il tipo degli elementi della lista */
public class List<X> {
private final X head; // Testa della lista, tipo X
private final List<X> tail; // Coda della lista, tipo List<X>
public List(final X head, final List<X> tail) {
this.head = head;
this.tail = tail;
}
public X getHead() {
return this.head;
}
public List<X> getTail() {
return this.tail;
}
// getLength() e toString() invariate
...
}
X
in un tipo specifico (argomento di tipo), ad es. Integer
public class UseList {
public static void main(String[] s) {
final List<Integer> list =
new List<Integer>(10, // Autoboxing
new List<Integer>(20,
new List<Integer>(30,
new List<Integer>(40, null))));
// Cast NON necessari
final int first = list.getHead(); // Unboxing
final int second = list.getTail().getHead();
final int third = list.getTail().getTail().getHead();
System.out.println(first + " " + second + " " + third);
System.out.println(list.toString());
System.out.println(list.getLength());
// Usabile anche con le stringhe
final List<String> list2 = new List<String>("a",
new List<String>("b",
new List<String>("c",
new List<String>("d", null))));
System.out.println(list2.toString());
}
}
C<X,Y>
..C
è detta tipo parametricoX
e Y
sono dette le sue type-variable o parametri di tipoX
e Y
possono essere usati come un qualunque tipo dentro la classe (con alcune limitazioni che vedremo)C<String,Integer>
, C<C<Object,Object>,Object>
C
senza parametri, altrimenti vengono segnalati dei warningObject
, String
,..X,Y
(usate dentro la classe C<X,Y>
)C<Object,Object>
C<Object,X>
(in C<X,Y>
)Vector
public class Vector<X>{
// X è la type-variable, ossia il tipo degli elementi
public Vector() { /* ... */ }
public void addElement(X e) { /* ... */ } // Input di tipo X
public X getElementAt(int pos) { /* ... */ } // Output di tipo X
public int getLength() { /* ... */ }
public String toString() { /* ... */ }
}
Vector<X>
public class UseVector {
public static void main(String[] s) {
// Il tipo di vs è Vector<String>
// Ma la sua classe è Vector<X>
final Vector<String> vs = new Vector<String>();
vs.addElement("Prova");
vs.addElement("di");
vs.addElement("Vettore");
final String str = vs.getElementAt(0) + " " +
vs.getElementAt(1) + " " +
vs.getElementAt(2); // Nota, nessun cast!
System.out.println(str);
final Vector<Integer> vi=new Vector<Integer>();
vi.addElement(10); // Autoboxing
vi.addElement(20);
vi.addElement(30);
final int i = vi.getElementAt(0) + // Unboxing
vi.getElementAt(1) +
vi.getElementAt(2);
System.out.println(i);
}
}
Vector
pt 1new X[10]
(errore statico)public class Vector<X> {
private final static int INITIAL_SIZE = 10;
private Object[] elements; // No X[], devo usare Object[]!!
private int size;
public Vector() {
this.elements = new Object[INITIAL_SIZE]; // Object[]
this.size = 0;
}
public void addElement(X e) { // Tutto come atteso
if (this.size == elements.length) {
this.expand();
}
this.elements[this.size] = e;
this.size++;
}
...
Vector
pt 2 ...
public X getElementAt(int position) {
return (X)this.elements[position]; // Conversione a X
// Genera un unchecked warning!
}
private void expand() {
// Ancora Object[]
Object[] newElements = new Object[this.elements.length*2];
for (int i=0; i < this.elements.length; i++){
newElements[i] = this.elements[i];
}
this.elements = newElements;
}
// getLength() e toString() inalterate
...
}
Pair<X,Y>
public class Pair<X, Y> {
private final X first;
private final Y second;
public Pair(final X first, final Y second) {
this.first = first;
this.second = second;
}
public X getFirst() {
return this.first;
}
public Y getSecond() {
return this.second;
}
public String toString() {
return "<" + this.first + "," + this.second + ">";
}
}
Pair<X,Y>
public class UsePair {
public static void main(String[] s) {
Pair<String, Integer> p = new Pair<String, Integer>("aa", 1);
String fst = p.getFirst();
int snd = p.getSecond();
System.out.println(fst + " " + snd);
final Vector<Pair<String, Integer>> v = new Vector<Pair<String, Integer>>();
v.addElement(new Pair<String, Integer>("Prova", 1));
v.addElement(new Pair<String, Integer>("Vettore", 2));
final String str = v.getElementAt(0).getFirst() + " " +
v.getElementAt(1).getFirst(); // Nota, nessun cast!
System.out.println(str);
System.out.println(v);
final List<Pair<Integer, Integer>> l =
new List<Pair<Integer, Integer>>(
new Pair<Integer, Integer>(1, 1),
new List<Pair<Integer, Integer>>(
new Pair<Integer, Integer>(2, 2),
new List<Pair<Integer, Integer>>(
new Pair<Integer, Integer>(3, 3),
null)));
System.out.println(l);
}
}
new
si possono tentare di omettere i parametri (istanziazione delle type-variable), indicando il “diamond symbol” <>
new
e l’eventuale contesto dentro il quale la new
è posizionata, per esempio, se assegnata ad una variabile<>
, altrimenti viene confuso con un raw type, un meccanismo usato per gestire il legacy con le versioni precedenti di Javavar
)<>
public class UsePair2 {
public static void main(String[] s) {
// Parametri in new Vector() inferiti dal tipo della variabile
final Vector<Pair<String,Integer>> v = new Vector<>();
// Parametri in new Pair(..) inferiti dal tipo degli argomenti
v.addElement(new Pair<>("Prova",1));
v.addElement(new Pair<>("Vettore",2));
final String str = v.getElementAt(0).getFirst() + " " +
v.getElementAt(1).getFirst(); // Nota, nessun cast!
System.out.println(str);
System.out.println(v);
// Inferenza grazie agli argomenti e tipo variabile..
final List<Pair<Integer,Integer>> l =
new List<>(new Pair<>(1,1),
new List<>(new Pair<>(2,2),
new List<>(new Pair<>(3,3), null)));
System.out.println(l);
// Local variable type inference
final var v2 = new Vector<Integer>();
v2.addElement(1);
System.out.println(v2);
}
}
Coi generici, Java diventa un linguaggio molto più espressivo!
interface I<X,Y> { ... }
Per creare contratti uniformi rispetto ai tipi utilizzati
Iterator
public interface Iterator<E> {
// torna il prossimo elemento dell'iterazione
E next();
// dice se vi saranno altri elementi
boolean hasNext();
/*
* Nota: non è noto cosa succede se si chiama next()
* quando hasNext() ha dato esito falso
*/
}
IntRangeIterator
/* Itera tutti i numeri interi fra 'start' e 'stop' inclusi */
public class IntRangeIterator implements Iterator<Integer> {
private int current; // valore corrente
private final int stop; // valore finale
public IntRangeIterator(final int start, final int stop) {
this.current = start;
this.stop = stop;
}
public Integer next() {
return this.current++;
}
public boolean hasNext() {
return this.current <= this.stop;
}
}
ListIterator
/* Itera tutti gli elementi di una List */
public class ListIterator<E> implements Iterator<E> {
private List<E> list; // Lista corrente
public ListIterator(final List<E> list) {
this.list = list;
}
public E next() {
final E element = this.list.getHead(); // Elemento da tornare
this.list = this.list.getTail(); // Aggiorno la lista
return element;
}
public boolean hasNext() {
return this.list != null;
}
}
VectorIterator
/* Itera tutti gli elementi di un Vector */
public class VectorIterator<E> implements Iterator<E> {
private final Vector<E> vector; // Vettore da iterare
private int current; // Posizione nel vettore
public VectorIterator(final Vector<E> vector) {
this.vector = vector;
this.current = 0;
}
public E next() {
return this.vector.getElementAt(this.current++);
}
public boolean hasNext() {
return this.vector.getLength() > this.current;
}
}
UseIterators
: nota l’accesso uniforme!public class UseIterators {
public static void main(String[] s) {
final List<String> list = new List<>("a",
new List<>("b",
new List<>("c", null)));
final Vector<java.util.Date> vector = new Vector<>();
vector.addElement(new java.util.Date());
vector.addElement(new java.util.Date());
// creo 3 iteratori..
final Iterator<Integer> iterator = new IntRangeIterator(5, 10);
final Iterator<String> iterator2 = new ListIterator<>(list);
final Iterator<java.util.Date> iterator3 = new VectorIterator<>(vector);
// ne stampo il contenuto..
printAll(iterator);
printAll(iterator2);
printAll(iterator3);
}
static <X> void printAll(Iterator<X> iterator) {
while (iterator.hasNext()) {
System.out.println("Elemento : " + iterator.next());
}
}
}
Un metodo che lavora su qualche argomento e/o valore di ritorno in modo independente dal suo tipo effettivo. Tale tipo viene quindi astratto in una type-variable del metodo.
<X1,..,Xn> ret-type nome-metodo(formal-args) { ... }
receiver.<X1,..,Xn>nome-metodo(actual-args) { ... }
<>
import java.util.Date;
public class UseIterators2 {
public static <E> void printAll(Iterator<E> iterator) {
while (iterator.hasNext()) {
System.out.println("Elemento : " + iterator.next());
}
}
// ...
import java.util.Date;
public class UseIterators2 {
public static <E> void printAll(Iterator<E> iterator){ /* ... */ }
public static void main(String[] s) {
Iterator<Integer> iterator = new IntRangeIterator(5, 10);
List<String> list = // ...
Iterator<String> iterator2 = new ListIterator(list);
Vector<Date> vector= // ...
Iterator<Date> iterator3 = new VectorIterator(vector);
// Attenzione, il nome della classe è obbligatorio
UseIterators2.<Integer>printAll(iterator);
UseIterators2.<String>printAll(iterator2);
UseIterators2.<Date>printAll(iterator3);
// Con inferenza, il nome della classe non è obbligatorio
printAll(iterator);
printAll(iterator2);
printAll(iterator3);
}
}
public class Vector<X> {
// ...
<E> Vector<Pair<X, E>> genVectorPair(E e) {
Vector<Pair<X, E>> vp = new Vector<>(); // Inferenza
for (int i = 0; i < this.size; i++) {
vp.addElement(new Pair<>(this.getElementAt(i), e));
}
return vp;
}
}
public class UseGenMeth {
public static void main(String[] s) {
Vector<String> vs = new Vector<>();
vs.addElement("prova");
vs.addElement("di");
vs.addElement("vettore");
Vector<Pair<String, Integer>> vp = vs.<Integer>genVectorPair(101);
// versione con inferenza..
// Vector<Pair<String,Integer>> vp2 = vs.genVectorPair(101);
System.out.println(vp);
// |<prova,101>|<di,101>|<vettore,101>|
}
}
C<T>
, ma di ogni C<S>
dove S <: T
printAll()
che prende in ingresso un iteratore e ne stampa gli elementi// Gerarchia dei wrapper Numbers in java.lang
abstract class Number extends Object { /* ... */ }
class Integer extends Number { /* ... */ }
class Double extends Number { /* ... */ }
class Long extends Number { /* ... */ }
class Float extends Number { /* ... */ }
/* ... */
// Accetta qualunque Vector<T> con T <: Number
// Vector<Integer>, Vector<Double>, Vector<Float>, ...
void m(Vector<? extends Number> arg){ /* ... */ }
// Accetta qualunque Vector<T>
void m(Vector<?> arg){ /* ... */ }
// Accetta qualunque Vector<T> con Integer <: T
// Vector<Integer>, Vector<Number>, e Vector<Object> solo!
void m(Vector<? super Integer> arg){ /* ... */ }
Bounded (covariante): C<? extends T>
C<S>
con S <: T
Bounded (controvariante): C<? super T>
C<S>
con S >: T
Unbounded: C<?>
C<S>
public class Wildcard {
// Metodo che usa la wildcard
public static void printAll(Iterator<?> it) {
while (it.hasNext()) {
System.out.println(it.next());
}
}
// Analoga versione con metodo generico
public static <T> void printAll2(Iterator<T> it) {
while (it.hasNext()) {
System.out.println(it.next());
}
}
// Quale versione preferibile?
public static void main(String[] s) {
Wildcard.printAll(new IntRangeIterator(1, 5));
Wildcard.printAll2(new IntRangeIterator(1, 5));
Wildcard.<Integer>printAll2(new IntRangeIterator(1, 5));
}
}
public class Wildcard2 {
// Metodo che usa la wildcard
public static Vector<Integer> toIntVector(Vector<? extends Number> vec) {
Vector<Integer> out = new Vector<>();
for(int i = 0; i < vec.getLength(); i++) {
// Si noti accesso al metodo intValue() del contratto di Number
out.addElement(vec.getElementAt(i).intValue());
}
return out;
}
public static void main(String[] s) {
Vector<Double> vd = new Vector<>();
vd.addElement(1.5);
vd.addElement(6.7);
Vector<Integer> vi = toIntVector(vd);
System.out.println("" + vi.getElementAt(0) + ", " + vi.getElementAt(1));
// var vd2 = toIntVector(new Vector<String>()); // ERROR: method not applicable
}
}
public class Wildcard3 {
// Le wildcard pongono limiti sulle operazioni eseguibili
public static void addElementToVector(Vector<? extends Number> vec) {
// Per Vector<T> covariante non possiamo invocare metodi che accettano T in input..
Number n = vec.getElementAt(0); // questo è ok
Number toAdd = n.doubleValue() + 1;
// vec.addElement(toAdd); // ERROR!
}
public static Number returnFirstNumber(Vector<? super Integer> vec) {
// Per Vector<T> contravariante non possiamo invocare metodi che restituiscono T in output..
vec.addElement(Integer.valueOf(7)); // questo è ok
Number out = null;
// out = vec.getElementAt(0); // ERROR!
return out;
}
}
Vector<? extends Number>
si può leggere come “Vector
che può fornire ma non accettare Number
” (cf. in C# dove la keyword out
è usata per specificare parametri di tipo covarianti)Vector<? super Number>
si può leggere come “Vector
che può accettare ma non fornire Number
” (cf. in C# dove la keyword in
è usata per specificare parametri di tipo controvarianti)