danilo.pianini@unibo.itgianluca.aguzzi@unibo.itangelo.filaseta@unibo.itCompiled on: 2025-12-05 — versione stampabile
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
DeviceIntVectorIntVector
UseIntVectorpublic 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 ObjectObjectVector è monomorfica in quanto “vede” un solo tipo, Object (sebbene i riferimenti agli Object siano polimorfici)IntVector a ObjectVector
UseObjectVectorpublic 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
}
}
ObjectVector o ObjectListInteger? solo delle String?ClassCastExceptionIl 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 StringF<Integer>javac “compila via i generici”, quindi la JVM non li vedeVector<X>Vector si dice che è un tipo parametrico (in quanto accetta il “parametro di tipo” X)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() { /* ... */ }
}
X in un tipo specifico (argomento di tipo), ad es. Integerpublic 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);
}
}
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 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);
}
}
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 int v1second = v.getElementAt(1).getSecond(); // Nota, nessun cast!
final String str = v.getElementAt(0).getFirst() + " " + v1second;
System.out.println(str);
System.out.println(v);
}
}
Coi generici, Java diventa un linguaggio molto più espressivo!
interface I<X,Y> { ... }Per creare contratti uniformi rispetto ai tipi utilizzati
Iteratorpackage it.unibo.generics.iterators;
public interface Iterator<E> {
/*
* Restituisce il prossimo elemento dell'iterazione
*
* Nota: non è noto cosa succede se si chiama next()
* quando hasNext() ha dato esito falso
*/
E next();
// dice se vi saranno altri elementi
boolean hasNext();
}
IntRangeIteratorpackage it.unibo.generics.iterators;
/* 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;
}
}
VectorIteratorpackage it.unibo.generics.iterators;
/* 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!package it.unibo.generics.iterators;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class UseIterators {
public static void main(String[] s) {
final Vector<Calendar> vector = new Vector<>();
vector.addElement(new GregorianCalendar());
vector.addElement(new GregorianCalendar());
// creo 2 iteratori
final Iterator<Integer> iterator1 = new IntRangeIterator(5, 10);
final Iterator<Calendar> iterator2 = new VectorIterator<>(vector);
// ne stampo il contenuto
printAll(iterator1);
printAll(iterator2); // Notare l'accesso uniforme! È uguale per Integer e Calendar
}
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> ReturnType nomeMetodo(ArgType1 argName1, ...) { ... }receiver.<X1, ..., Xn>nomeMetodo(ArgType1 argName1, ...)receiver.nomeMetodo(ArgType1 argName1, ...)static non possono vedere i parametri di tipo della classe che li ospita
static genericistatic e suo utilizzopackage it.unibo.generics.iterators;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class UseIterators2 {
public static <E> void printAll(final Iterator<E> iterator) {
while (iterator.hasNext()) {
System.out.println("Elemento : " + iterator.next());
}
}
public static void main(String[] s) {
final Iterator<Integer> iterator = new IntRangeIterator(5, 10);
final LList<String> list = new LList<>("a", new LList<>("b", new LList<>("c", new LList<>("d", null))));
final Iterator<String> iterator2 = new LListIterator<>(list);
final Vector<Calendar> vector = new Vector<>();
vector.addElement(new GregorianCalendar());
vector.addElement(new GregorianCalendar());
vector.addElement(new GregorianCalendar());
final Iterator<Calendar> iterator3 = new VectorIterator<>(vector);
// con inferenza:
UseIterators2.printAll(iterator); // Equivale a: UseIterators2.<Integer>printAll(iterator)
UseIterators2.printAll(iterator2); // Equivale a: UseIterators2.<String>printAll(iterator)
UseIterators2.printAll(iterator3); // Equivale a: UseIterators2.<Calendar>printAll(iterator)
}
}
package it.unibo.generics.iterators;
public class PairReplacer<X, Y> {
private final Pair<X, Y> pair;
public PairReplacer(final Pair<X, Y> pair) {
this.pair = pair;
}
public <Z> Pair<Z, Y> replaceFirst(final Z newFirst) {
return new Pair<>(newFirst, this.pair.getSecond());
}
public <Z> Pair<X, Z> replaceSecond(final Z newSecond) {
return new Pair<>(this.pair.getFirst(), newSecond);
}
static void main() {
final var replacer = new PairReplacer<>(new Pair<>("Hello", 42));
final var newFirstPair = replacer.replaceFirst(3.14); // Con inferenza, uso tipico
System.out.println("Replaced First: " + newFirstPair);
Pair<String, Boolean> newSecondPair = replacer.<Boolean>replaceSecond(true); // Senza inferenza
System.out.println("Replaced Second: " + newSecondPair);
}
}
C<T>, se S è sottotipo di T, non vale che C<S> è sottotipo di C<T>
Student extends Person non implica che Vector<Student> estenda Vector<Person>
Vector<Person> sarebbe valida anche per Vector<Student>add(Person p)Person ad un Vector<Student>, starei violando la sicurezza dei tipi!Vector di un sottotipo non noto di Number
NumberVector di un supertipo non noto di Double
Double, ma non potrò leggere nulla di più specifico di ObjectC<? extends T>
C<S> con S sottotipo di TC<? super T>
C<S> con S supertipo di TC<?>
C<S>package it.unibo.generics.wildcard;
import it.unibo.generics.iterators.IntRangeIterator;
import it.unibo.generics.iterators.Iterator;
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?
static void main() {
Wildcard.printAll(new IntRangeIterator(1, 5));
Wildcard.printAll2(new IntRangeIterator(1, 5));
Wildcard.<Integer>printAll2(new IntRangeIterator(1, 5));
}
}
package it.unibo.generics.wildcard;
import it.unibo.generics.generics.Vector;
public class Wildcard2 {
// Metodo che usa la wildcard
public static Vector<Integer> toIntVector(final Vector<? extends Number> vec) {
final 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;
}
static void main() {
final 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
}
}
package it.unibo.generics.wildcard;
import it.unibo.generics.generics.Vector;
public class Wildcard3 {
// Le wildcard pongono limiti sulle operazioni eseguibili
public static void addElementToVector(final Vector<? extends Number> vec) {
// Per Vector<T> covariante non possiamo invocare metodi che accettano T in input
final Number n = vec.getElementAt(0); // questo è ok
Number toAdd = n.doubleValue() + 1;
// vec.addElement(toAdd); // ERROR!
}
public static Number returnFirstNumber(final 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;
}
}
Un modo più intuitivo di interpretare le wildcard è quello di considerare i tipi generici come produttori o consumatori di valori del tipo specificato.
in e out.
C con C<out T>, e la contravarianza con C<in T>.C<? extends T> per la covarianza e C<? super T> per la contravarianza.Vector<? extends Number> si può leggere “Vector che può fornire ma non accettare Number”
Vector<out Number> in Kotlin o C#, dove la keyword out è usata per specificare parametri di tipo covariantiVector<? super Number> si può leggere “Vector che può accettare ma non fornire Number”
Vector<in Number> in Kotlin o C#, dove la keyword in è usata per specificare parametri di tipo contravariantidanilo.pianini@unibo.itgianluca.aguzzi@unibo.itangelo.filaseta@unibo.itCompiled on: 2025-12-05 — versione stampabile