Java 1.5: siate più precisi, cioè più generici
Alla fine di questo anno arriverà la versione Tiger di Java con un’interessante nuova funzionalità battezzata generics. Ecco perché vi semplificherà la vita.La prossima versione di Java, la 1.5 ha nome in codice Tiger ed è attesa per la fine del 2003. Tiger dovrebbe vantare un ricchissimo corredo di innovazioni al linguaggio, una di queste, chiamata generics, è molto interessante perché aggiunge al linguaggio una funzionalità nuova, che possiamo provare e mettere a frutto fin da subito, grazie ad una versione modificata del compilatore e delle librerie di supporto che possiamo scaricare dal sito del Java Community Process.
Per la gioia dei più smanettoni sottolineiamo che il compilatore e le librerie modificate sono disponibili in formato sorgente, quindi si possono smontare, studiare, seguire passo per passo. Attenzione però: la licenza vieta qualunque altro uso dei sorgenti. Ma non corriamo troppo e vediamo per prima cosa da dove nasce l’esigenza di cambiare in profondità il modo in cui si creano alcune strutture dati in certi programmi.
Il problema
Consideriamo un esempio concreto: la classe di nome ListProblems di cui abbiamo il codice in figura.
Il metodo main della classe crea due variabili di tipo LinkedList, si tratta di due liste concatenate di nome integerList e stringList, destinate a scopi diversi. Come implica il nome, il programmatore ha intenzione di utilizzare la prima di queste per immagazzinare degli interi, la seconda per contenere delle stringhe.
|
L’uso delle liste in Java presta il fianco ad alcuni problemi perché la sintassi del linguaggio non permette di specificare il tipo di oggetti che un contenitore può accettare. | |
String item = (String)listIterator.next();
oppure si usa uno dei metodi di accesso della lista stessa
String item = (String)list.get(3);
il cast a String è obbligatorio in entrambi i casi, dato che entrambi i metodi restituiscono variabili di classe Object. La sintassi di Java non permette di eliminare il cast. Quello che si può fare, al massimo è cercare di creare una classe con dal contenitore che interessa.
Il compilatore Java si ribella vigorosamente al tentativo di eludere il problema sia con l’approccio della derivazione sia con l’approccio della mediazione. Entrambi i tentativi più ovvi si infrangono contro le regole sintattiche di Java: non è possibile restringere il valore di ritorno della get da Object a String. Fin qui possiamo ancora accettare il verdetto del compilatore, in fin dei conti le regole più strette sono proprio ciò che fa la differenza tra Java e altri linguaggi. Il codice che passa il vaglio del compilatore normalmente esegue correttamente e fa - in generale - quello che il programmatore aveva in mente. Certamente non si può dire altrettanto del C++. Questo paracadute sintattico è proprio una delle particolarità di Java che attraggono chi ha esperienza in C++: accettando regole più strette si rinuncia a un po’ di potenza e un po’ di libertà, ma si ha in cambio serenità e una produttività alla Visual Basic.
Java però delude proprio sotto il punto di vista della precisione perché possiamo facilmente scrivere il codice che va in errore durante l’esecuzione e, quel che è peggio, anche in modo non deterministico. Vediamo il primo esempio di codice errato dalla classe ListProblems.
Iniziamo creando un iteratore per scorrere la lista integerList.
Iterator listIterator = integerList.iterator();
Potenzialmente parecchie righe di codice più tardi facciamo un errore banale: usiamo ListIterator, un iteratore che abbiamo creato sulla lista integerList convinti di averlo associato a stringList.
// Primo problema.
// Il compilatore non può scoprire l’errore
// volevamo scorrere stringList
while(listIterator.hasNext()) {
// Gli elementi sono in realtà Integer
String item = (String)listIterator.next();
}
Il compilatore non ha nessuna possibilità di rilevare lo sbaglio. Ci accorgeremo dello scambio solo quando andremo a cercare le cause dell’errore che avremo in esecuzione.
Il secondo problema è ancora più subdolo, perché l’errore non si verifica sempre e comunque, ma solo in certe circostanze.
Osservando il codice non ci sono problemi per il compilatore e nemmeno la macchina virtuale avrà nulla da obiettare durante l’esecuzione:
// Secondo problema.
// Nessuna garanzia di omogeneità
while (listIterator.hasNext()) {
// Avremo un errore sul secondo elemento
String item = (String)listIterator.next();
} Il problema origina in un’altra parte del programma, quella in cui inseriamo in lista un oggetto di classe Integer. L’inserimento non è per nulla illegale perché la lista è ecumenica e accetta oggetti di ogni tipo, ma il nostro codice fa l’assunzione che troveremo solo String all’interno della lista. Non appena incontreremo scorrendo la lista un oggetto di tipo diverso avremo un errore di run time. Può benissimo capitare che un errore simile non sia rilevato in fase di test, ad esempio perché durante le prove si inseriscono in lista solo stringhe. Specie se chi fa i test è lo stesso che ha scritto il codice, l’assunzione che la lista è destinata a contenere stringhe può propagarsi dal codice ai programmi di prova. L’errore, insomma, può essere abbastanza antipatico da presentarsi solo dopo la consegna del programma.
La soluzione
La soluzione che è stata adottata va sotto il nome di generics ed è semplice a vedersi, come in questo caso
LinkedList<Integer> integerList =
new LinkedList<Integer>();
La notazione ricorda parecchio la sintassi dei template del C++.
L’implementazione è stata sofferta: James Gosling ne ha parlato durante il discorso introduttivo del JavaOne 2001 e ha ammesso che per un certo tempo si è cercato soprattutto di far prevalere la stabilità di Java sull’innovazione. Diverse soluzioni alternative sono state scartate per un motivo o per l’altro e sicuramente non si è esagerato per imprudenza, dato che assert e generics sono state presentate insieme, ma la prima è entrata nel Jdk 1.4, mentre la seconda è slittata alla 1.5. L’esempio con cui Gosling ha presentato i generics è ancora più significativo per convincere che il linguaggio aveva bisogno di questa estensione:
// map strings to employees
Map employees = new HashMap;
Employees e = …;
employees.put(e.name, e);
// errore!
employees.put(e, e.name);
// non serve il cast
e = employees.get(“Harry Bovik”);
Se si stabilisce che la tabella hash ha una String come chiave un Employee come contenuto, il compilatore può scoprire un errore, come l’inversione dei parametri nell’uso del metodo put. Ecco qualche esempio di dichiarazioni che fanno uso dei generics:
Vector<String>
Seq<Seq><A>>
Seq<String>.Zipper<Integer>
Collection<Integer>
Pair<String,String>
L’implementazione che è stata scelta si basa su un prototipo, chiamato GJ. Una caratteristica vincenti sta nel fatto che non è richiesta nessuna collaborazione da parte della macchina virtuale. Ne segue che i programmi scritti con i compilatori aggiornati potranno essere eseguiti senza errori su macchine virtuali più anziane.
|
Il buon vecchio Emacs e un makefile sono sempre un buon strumento di lavoro. Bisogna però investire parecchio tempo per conoscere bene l’editor. | |
Per fortuna c’è un file jar che aiuta a fare il bootstrap.
• Scarica il Listato 1
• Scarica il Listato 2
• Scarica il Listato 3






Ancora nessun commento.