Esercizio - generics e riflessione con la coda e il caricatore di classi (esempi del corso)
Esercizio basato sugli esempi del corso (Coda, CodaG, Main2, Main3, Loader2, Coccodrillo, Cane della cartella generics e reflect), sviluppati in un unico programma. Teoria: Java - generics, reflection e passaggio dei parametriIn Java ogni dato ha una propria identità a tempo di esecuzione (il cast è controllato e può lanciare ClassCastException, a differenza del C) e i parametri si passano sempre per valore (di un riferimento, per gli oggetti: si può modificare l'oggetto ma non riassegnare il riferimento del chiamante); i generics (CodaG
Testo
Gli esempi del corso contengono una coda circolare di Object (Coda), la sua versione generica CodaG<T> e i caricatori di classi che usano la riflessione.
Parte A (generics). Partendo da CodaG<T>:
- spiegare perché
Main2del corso (che inserisce nellaCodadiObjectle stringhe degli argomenti e poi unaCoda) fallisce conClassCastExceptione perché lo stesso errore, conCodaG<String>, non compila; - rendere
CodaG<T>iterabile (Iterable<T>); - scrivere un metodo generico
massimoche restituisca l'elemento maggiore di unaCodaG<T>per qualunqueTconfrontabile; - scrivere un metodo
sommache accetti una coda di qualunque tipo numerico (CodaG<Integer>,CodaG<Double>...).
Parte B (riflessione). Scrivere una classe Caricatore con un metodo crea(String nomeClasse, String argomento), versione non interattiva del Loader2 del corso: carica la classe dal nome, stampa i costruttori, usa il costruttore con un solo parametro String se esiste (altrimenti quello senza parametri) e restituisce l'oggetto; un secondo metodo chiama(Object o, String metodo) invoca per nome un metodo senza parametri. Provarlo con Coccodrillo (tre costruttori, uno che lancia FileNotFoundException) e Cane (blocco static che stampa al caricamento). Infine mostrare che cosa arriva quando il costruttore lancia.
Soluzione
Parte A
1. Perché Main2 fallisce e Main3 no. Con Coda che contiene Object, add(argv[i]) e add(new Coda(0)) sono entrambi validi per il compilatore (tutto è un Object). Alla lettura il programma fa (String) c.take(): quando incontra la Coda, il cast controllato a runtime lancia ClassCastException, e l'eccezione compare dopo che le stringhe sono già state sommate. Con CodaG<String> il parametro di tipo fa sì che add(new Coda(0)) sia un errore di compilazione (il corso lo lascia commentato: //c.add(new Coda(0));, e analogamente ci.add("pippero") su CodaG<Integer>). Il controllo si sposta da runtime a compilazione, e non servono più i cast.
2. Iterable<T>. La classe implementa iterator() con una classe anonima che scorre da testa per size elementi, in modo circolare ((testa + i) % storage.length); così si può usare for (T x : coda).
3. massimo. Un metodo generico con vincolo: static <T extends Comparable<T>> T massimo(CodaG<T> c). Il vincolo permette di chiamare x.compareTo(m); senza di esso T sarebbe solo un Object. Funziona con Integer e con String (ordine alfabetico).
4. somma con wildcard. static double somma(CodaG<? extends Number> c) accetta qualunque coda di un sottotipo di Number; non si può scrivere CodaG<Number>, perché CodaG<Integer> non è un sottotipo di CodaG<Number> (i generics non sono covarianti). La wildcard vale in sola lettura: dalla coda si legge un Number, ma non vi si può aggiungere niente (non si sa di quale sottotipo sia). CodaG.somma(cs) con CodaG<String> non compila.
Parte B
Class.forName(nome) carica e inizializza la classe (Cane stampa subito la riga del blocco static); getDeclaredConstructors() fornisce i costruttori (l'ordine non è garantito dalla JVM: l'esecuzione sotto può elencarli in un ordine diverso); getDeclaredConstructor(String.class) cerca quello a un solo parametro String e, se manca, lancia NoSuchMethodException, che il metodo intercetta per ripiegare sul costruttore senza parametri (Cane). Il metodo chiamato per nome si cerca con getDeclaredMethod(nome) (nome e tipi dei parametri) e si esegue con invoke(oggetto).
Codice
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.NoSuchElementException;
/** Parte A: la CodaG<T> del corso, resa Iterable, con metodi generici. */
class CodaG<T> implements Iterable<T> {
private final T[] storage;
private int testa, size;
@SuppressWarnings("unchecked")
CodaG(int len) { storage = (T[]) new Object[len]; }
void add(T o) {
if (size == storage.length) throw new IllegalStateException("Coda piena");
storage[(testa + size) % storage.length] = o;
size++;
}
T take() {
if (size == 0) throw new NoSuchElementException("Coda vuota");
T r = storage[testa];
storage[testa] = null;
testa = (testa + 1) % storage.length;
size--;
return r;
}
@Override public Iterator<T> iterator() {
return new Iterator<T>() {
int i = 0;
public boolean hasNext() { return i < size; }
public T next() { return storage[(testa + i++) % storage.length]; }
};
}
/** Metodo generico con vincolo: T deve essere confrontabile con se' stesso. */
static <T extends Comparable<T>> T massimo(CodaG<T> c) {
T m = null;
for (T x : c) if (m == null || x.compareTo(m) > 0) m = x;
return m;
}
/** Wildcard: accetta CodaG<Integer>, CodaG<Double>... (si legge soltanto: "producer extends"). */
static double somma(CodaG<? extends Number> c) {
double s = 0;
for (Number n : c) s += n.doubleValue();
return s;
}
}
/** Parte B: classi da caricare dinamicamente (come Coccodrillo e Cane del corso). */
class Coccodrillo {
private final String msg;
public Coccodrillo() throws FileNotFoundException { throw new FileNotFoundException("niente palude"); }
public Coccodrillo(int i) { this.msg = "int " + i; }
public Coccodrillo(String msg) { this.msg = msg; }
public String saluta() { return "sono un coccodrillo <" + msg + ">"; }
}
class Cane {
static { System.out.println(" [static] classe Cane caricata"); }
public String saluta() { return "Bau"; }
}
/** Equivalente non interattivo del Loader2 del corso. */
class Caricatore {
/** Istanzia con il costruttore a un solo parametro String, se esiste, altrimenti con quello di default. */
static Object crea(String nomeClasse, String argomento) throws Exception {
Class<?> clazz = Class.forName(nomeClasse);
for (Constructor<?> k : clazz.getDeclaredConstructors()) System.out.println(" costruttore: " + k);
try {
Constructor<?> c = clazz.getDeclaredConstructor(String.class);
return c.newInstance(argomento);
} catch (NoSuchMethodException e) {
return clazz.getDeclaredConstructor().newInstance();
}
}
static Object chiama(Object o, String metodo) throws Exception {
Method m = o.getClass().getDeclaredMethod(metodo);
return m.invoke(o);
}
}
public class GenericiRiflessione {
public static void main(String[] args) throws Exception {
CodaG<Integer> ci = new CodaG<>(5);
ci.add(3); ci.add(9); ci.add(4);
System.out.println("massimo = " + CodaG.massimo(ci) + ", somma = " + CodaG.somma(ci));
CodaG<String> cs = new CodaG<>(5);
cs.add("rosa"); cs.add("pendolo"); cs.add("baudolino");
System.out.println("massimo (ordine alfabetico) = " + CodaG.massimo(cs));
// CodaG.somma(cs); // errore di compilazione: String non estende Number
Object o1 = Caricatore.crea("Coccodrillo", "ciao");
System.out.println(Caricatore.chiama(o1, "saluta"));
Object o2 = Caricatore.crea("Cane", "ignorato"); // niente costruttore String: usa il default
System.out.println(Caricatore.chiama(o2, "saluta"));
// il costruttore di default di Coccodrillo lancia: arriva InvocationTargetException con la causa
try {
Coccodrillo.class.getDeclaredConstructor().newInstance();
} catch (InvocationTargetException e) {
System.out.println("causa: " + e.getCause());
}
}
}Uscita:
massimo = 9, somma = 16.0
massimo (ordine alfabetico) = rosa
costruttore: public Coccodrillo(java.lang.String)
costruttore: public Coccodrillo(int)
costruttore: public Coccodrillo() throws java.io.FileNotFoundException
sono un coccodrillo <ciao>
[static] classe Cane caricata
costruttore: Cane()
Bau
causa: java.io.FileNotFoundException: niente paludeLettura dell'uscita:
massimo = 9, somma = 16.0per la coda di interi 3, 9, 4;massimo (ordine alfabetico) = rosaper le stringhe;- il caricatore stampa i tre costruttori di
Coccodrilloe usa quelloString(sono un coccodrillo <ciao>); perCanestampa[static] classe Cane caricataprima dell'elenco dei costruttori, ne trova uno solo (quello di default), esaluta()dàBau; - il costruttore di default di
CoccodrillolanciaFileNotFoundException: la riflessione la restituisce incapsulata in unaInvocationTargetException, e la causa si legge congetCause()(causa: java.io.FileNotFoundException: niente palude). Una chiamata direttanew Coccodrillo()avrebbe invece obbligato a dichiarare o catturare l'eccezione controllata.
Versione ripasso
Main2del corso:CodadiObject:add(argv[i])eadd(new Coda(0))compilano entrambi; il cast(String) c.take()fallisce a runtime conClassCastException. ConCodaG<String>add(new Coda(0))non compila (il corso lo lascia commentato): controllo a compilazione, niente cast.- Iterabile:
CodaG<T> implements Iterable<T>,iterator()scorre datestapersizein modo circolare. - Metodo generico con vincolo:
static <T extends Comparable<T>> T massimo(CodaG<T> c); senza vincoloTsarebbe soloObject. Wildcard:static double somma(CodaG<? extends Number> c);CodaG<Integer>non è sottotipo diCodaG<Number>; la wildcard è in sola lettura.
static <T extends Comparable<T>> T massimo(CodaG<T> c) {
T m = null;
for (T x : c) if (m == null || x.compareTo(m) > 0) m = x;
return m;
}
static double somma(CodaG<? extends Number> c) {
double s = 0;
for (Number n : c) s += n.doubleValue();
return s;
}- Caricatore:
Class.forName(carica e inizializza:Canestampa il bloccostatic),getDeclaredConstructors()(ordine non garantito),getDeclaredConstructor(String.class)con ripiego su quello senza parametri se manca (NoSuchMethodException),getDeclaredMethod(nome).invoke(oggetto).
Class<?> clazz = Class.forName(nomeClasse);
try {
return clazz.getDeclaredConstructor(String.class).newInstance(argomento);
} catch (NoSuchMethodException e) {
return clazz.getDeclaredConstructor().newInstance();
}- Uscite:
massimo = 9, somma = 16.0(3, 9, 4);massimo (ordine alfabetico) = rosa;sono un coccodrillo <ciao>;[static] classe Cane caricatapoiBau;causa: java.io.FileNotFoundException: niente palude(costruttore che lanciaInvocationTargetException, causa ingetCause()). - Perché
Main3non fallisce a runtime:CodaG<Integer>rifiutaci.add("pippero")in compilazione;accumula += ci.take()non richiede né cast néInteger.parseInt; la somma degli argomenti si fa con unboxing automatico. InvocationTargetException: il costruttore di default diCoccodrillolanciaFileNotFoundException; connewInstancel'eccezione arriva incapsulata e la causa si legge congetCause()(connew Coccodrillo()bisognerebbe dichiarare o catturare l'eccezione controllata).- Errori tipici: pensare che
CodaG<Integer>sia unCodaG<Number>; dimenticare il vincoloComparablee non poter chiamarecompareTo; non gestireInvocationTargetExceptionleggendone la causa; usareClassgrezzo; contare sull'ordine digetDeclaredConstructors.