MokaByte 83 - Marzo 2004 
Java 2 Standard Edition
1.5 Beta 1
di
Andrea Gini
Da alcune settimane è disponibile per il download il J2SDK 1.5, che introduce sette nuovi costrutti al linguaggio Java. L'iniziativa sta suscitando in egual misura entusiasmo e critiche, dato l'enorme impatto di queste aggiunte sul design originale di Java. Questo mese andremo ad analizzare queste nuove caratteristiche, in modo da poter formulare un giudizio a riguardo.

Un nuovo SDK
Dopo quasi dieci anni di carriera, Java sta per entrare in una nuova fase grazie ad alcune importanti aggiunte ai costrutti del linguaggio. Dalla sua prima release, Java ha subito solamente quattro modifiche a livello di linguaggio, con un impatto molto lieve sulle abitudini del comune programmatore:

JDK 1.0.2 - Rimozione della combinazione di modificatori "private protected"
JDK 1.1 - Introduzione di Classi e Interfacce interne
J2SDK 1.2 - Aggiunta la keyword strictfp
J2SDK 1.4 - Aggiunta di un meccanismo di Assertion

Dopo dieci anni di gestione conservativa, il J2SDK 1.5 [1] si appresta ad introdurre ben sette nuovi costrutti, la cui portata è tale da non lasciar spazio all'indifferenza. Gli obiettivi dichiarati dai progettisti sono quattro:

  • Aumentare l'affidabilità delle dichiarazioni di tipo
  • Rendere il codice più compatto
  • Migliorare la leggibilità
  • Mantenere la compatibilità all'indietro, sia a livello di bytecode che di sorgente

Tre di questi costrutti (Generics, foreach e Autoboxing/Unboxing) sono indirizzati a semplificare la gestione di Array e Collections. Un'altro costrutto, le enumeration, fornisce un meccanismo standard per la definizione di insiemi di oggetti. Infine, ma non meno importante, sono stati introdotti metadati, import statico e metodi con numero variabile di argomenti.

Curiosamente le nuove features non hanno richiesto alcun cambiamento alla Virtual Machine: esse vengono implementate nel compilatore con un meccanismo tipo espansione di macro, che produce bytecode compatibile con la precedente versione della JVM. In secondo luogo, l'aggiunta di questi sette nuovi costrutti ha richiesto una sola nuova keyword, la parola "enum" che non può più essere utilizzata come identificatore di metodo, classe o variabile.

 

Inizia l'era del C--?
Storicamente Java nasce come sottoinsieme del C++, un linguaggio molto potente ma ricco di idiosincrasie e di costrutti potenzialmente pericolosi. I progettisti di Java, pur conservando la sintassi di base del C++, decisero di rimuovere i costrutti più macchinosi, come l'ereditarietà multipla e l'aritmetica dei puntatori, dando vita ad un linguaggio compatto ed espressivo che ha riscosso un enorme successo, sia in ambito accademico che aziendale.
L'aggiunta di queste sette nuove features costituisce una grossa rottura con il passato: costrutti come i Generics o le Enum presentano delle sottigliezze di utilizzo tanto sul piano sintattico quanto su quello semantico, un fatto che tradisce il design originale. Non deve pertanto stupire che al partito degli entusiasti si contrapponga un drappello di scettici, che inondano forum e newsgroup di critiche, arrivando addirittura ad ipotizzare "La fine di Java" [2].
La somiglianza sintattica tra i nuovi costrutti Java e quelli analoghi presenti nel C++ spaventa gli scettici, che interpretano questo fenomeno come una pericolosa frattura col design originario e temono ricadute disastrose sul lungo termine.
Bisogna tuttavia sottolineare che esistono sostanziali differenze tra l'implementazione di queste features in Java e quella delle omologhe presenti nel C++. Per fare un esempio, i Generics Java, a differenza dei Templates C++, prevedono un controllo statico sui tipi presenti nelle dichiarazioni e, cosa ancor più importante, la loro implementazione non prevede la generazione di codice extra ("Code Bloat") per ogni realizzazione concreta del modello. Per rendere l'idea di quale sia la differenza tra le due implementazioni in termini di complessità, è sufficiente pensare che se da una parte esistono interi libri sulla Standard Template Library C++, dall'altra è sufficiente un tutorial di venti pagine per illustrare nei dettagli l'uso dei Generics in Java [6].
E' chiaro pertanto che solo il tempo permetterà di giudicare l'impatto reale di queste novità sulla comunità degli utenti. Andiamo ora ad analizzare queste novità una per una.

 

Generics
I Generics sono senza dubbio l'aspetto più interessante e più atteso di questa nuova release di Java. Per anni alcuni sviluppatori ne hanno lamentato l'assenza, mentre altri ne criticavano la complessità. La storia di Java e del polimorfismo parametrico è piuttosto interessante, e merita di essere raccontata.
Un po' di storia
La comunità degli sviluppatori ha sempre lamentato l'assenza di una forma di Polimorfismo Parametrico in Java, qualcosa di simile ai Templates C++. James Gosling, il capo del team di sviluppo di Java, scelse di non includere questa feature proprio a causa della macchinosità dei Templates C++.

Tuttavia, già a partire dal 1996 era possibile reperire su Intenet dei compilatori in grado di lavorare su versioni potenziate di Java che includevano una qualche forma di polimorfismo parametrico. La più famosa di queste, conosciuta con l'italianissimo nome Pizza [3], includeva anche altre caratteristiche tipiche dei linguaggi funzionali, come funzioni di prima classe e tipi algebrici. I progettisti di Pizza, convinti del potenziale della loro implementazione, svilupparono GJ [4], una versione semplificata di Pizza che aggingeva a Java il Polimorfismo Parametrico e l'Overriding Covariante. Nel maggio del '99 stilarono una documento da sottoporre al Java Community Process [5] affinchè GJ diventasse parte integrante del linguaggio Java. Dopo cinque anni di lavoro, passati a vagliare i pro e i contro di questa architettura, la Sun Microsystem ha riconosciuto la validità della proposta, ed ha introdotto in Java il polimorfismo parametrico nella forma proposta dal team di GJ.
Generics e Polimorfismo Parametrico
Il polimorfismo parametrico è un costrutto che permette di delegare l'attribuzione del tipo di uno o più attributi di una classe ad appositi parametri, forniti alla classe al momento della creazione.

Grazie a questo costrutto è possibile eliminare virtualmente qualunque necessità di ricorrere al casting, un'operazione fortemente sconsigliata ma necessaria in tutte le circostanze in cui il tipo di un parametro non può essere stabilito a priori.

La tipica situazione in cui si avverte l'esigenza di un meccanismo di polimorfismo parametrico è quando si utilizzano le Collection Java, una libreria di contenitori di oggetti generici:

Vector list = new Vector();
list.add("Elemento 1");
list.add("Elemento 2");

Il problema nasce quando si desidera recuperare un elemento da una Collection: i metodi di interrogazione infatti restituiscono oggetti del tipo generico Object, e per attribuire all'oggetto il suo tipo originario è necessario ricorrere ad un'operazione di casting:

String s = (String)list.get(0);

Se per errore si inserisce in una Collection un oggetto del tipo sbagliato, lo sbaglio verrà individuato solamente in fase di esecuzione, nel momento in cui l'istruzione viene valutata:

Vector list = new Vector();
...
list.add(new Integer(187));

String s = (String)list.get(0); // ClassCastException

Grazie ai Generics è ora possibile dichiarare in fase di creazione cosa si desidera inserire in una Collection, in modo da impedire alla radice un possibile un uso scorretto:

Vector<String> list = new Vector<String>();
list.add("Elemento");
String s = list.get(0); // Non è più necessario ricorrere al casting

In fase di creazione viene assegnato in maniera esplicita il tipo di dato manipolato dalla Collection, eliminando la necessità di ricorrere al casting. A questo punto è possibile identificare eventuali usi scorretti in fase di compilazione, eliminando il problema delle ClassCastException a Runtime:

Vector<String> list = new Vector<String>();
list.add(new Integer(12)); // Errore di compilazione!

L'eliminazione dell'operazione di cast porta dei vantaggi immediati sulla leggibilità del codice.
Si osservi il seguente frammento di codice:

((String)list.get(0)).append("****");

Grazie ai Generics, esso può essere riscritto nel modo seguente:

list.get(0).append("****");

L'uso di una API generica non presenta a prima vista particolari difficoltà: l'utente deve semplicemente dichiarare il tipo utilizzato ricorrendo alla notazione <Tipo>. D'altra parte la creazione di una classe generica presenta di sicuro maggiori problemi, che accenneremo nel prossimo paragrafo.
Dichiarazioni di classi e metodi parametrici
Se si desidera creare una classe generica è necessario specificare nella dichiarazione di classe il nome simbolico del tipo all'interno dei delimitatori < e >. La classe Vector, ad esempio, può essere definita nel modo seguente:

public class Vector<E> {
  ...
}

A questo punto il simbolo E può essere usato all'interno del sorgente come se fosse un normale identificatore di tipo:

public class Vector<E> {
  public void add(E x) {
    ...
  }

  public E get(int i) {
    ...
  }
}

Il simbolo E denota un tipo parametrico: il suo valore verrà stabilito solo al momento della creazione di istanze concrete dell'oggetto. Non esiste un limite al numero di parametri generici, come si vede nel seguente esempio:

public class Coppia<A, B> {

  private A primo;
  private B secondo;

  public Coppia (A primo, B secondo) {
    this. primo = primo;
    this. secondo = secondo;
  }

  public A getPrimo() {
    return primo;
  }

  public B getSecondo() {
    return secondo;
  }
}

La classe Coppia fa uso di due tipi parametrici, rappresentati dai simboli A e B. In fase di creazione sarà ovviamente necessario specificare il tipo dei parametri, cosa che può essere fatta anche in modo implicito, senza ricorrere alla notazione <Tipo>, attraverso il passaggio di parametri al costruttore:

Coppia<String, int> = new Coppia("Una Stringa",105);

Una classe come questa torna utile ogni qualvolta si debba dichiarare un metodo che restituisce una coppia di oggetti:

public Coppia<String, int> getValues() {
  return new Coppia(this.nome,this.età);
}

L'uso avanzato dei tipi generici è abbastanza complesso. In una classe parametrica è possibile dichiarare metodi che a loro volta dipendono dal tipo specificato in fase di creazione. La classe Vector, ad esempio, possiede un metodo removeAll() che permette di rimuovere un gruppo di elementi dalla collection. Tale metodo richiede il passaggio di una collection contenente elementi dello stesso tipo, pertanto la firma del metodo dovrà specificare la cosa:

public boolean removeAll(Collection<E> c);

E' possibile estendere i limiti di un tipo parametrico, ad esempio dichiarando un metodo che accetta Collection di qualunque sottoclasse di E, compreso E stesso:

public boolean addAll(Collection<? extends E> c)

Queste ed altre funzionalità dei Generics sono molto potenti, ma anche piuttosto macchinose: è chiaro che pertanto che servirà un certo tempo per imparare a sfruttarle nel migliore dei modi.

 

Autoboxing / Unboxing
La presenza di tipi primitivi è una caratteristica importante del linguaggio Java, dato che permette di operare in modo semplice ed intuitivo sulle unità di informazione più elementari, come i tipi numerici e i caratteri. D'altra parte la dicotomia Oggetti/Tipi Primitivi comporta degli svantaggi pratici ogni qualvolta si voglia dichiarare un metodo potenzialmente capace di operare su entrambi. Ancora una volta le Collection costituiscono un esempio lampante di questo tipo di problema: fino ad ora l'unico modo per inserire valori primitivi all'interno di una collection, era necessario ricorrere alle apposite classi wrapper:

Vector v = new Vector();
v.add(new Integer(10));
v.add(new Integer(15));

Il ricorso alle classi wrapper costringe a ricorrere ad una sintassi macchinosa nel momento in cui di vuole recuperare dalla Collection il valore originario:

int i = ((Integer)v.get(0)).intValue();

A partire dal Java 1.5 è possibile utilizzare i tipi primitivi in tutti i contesti in cui è permesso l'uso di Object: le conversioni tra tipi primitivi e wrapper type viene svolta in modo automatico dal compilatore.

Vector v = new Vector();
v.add(10);
int i = (int)v.get(0);

Ovviamente l'Autoboxing / Unboxing e presenta ulteriori vantaggi se usato congiuntamente ai Generics:

Vector <Integer> v = new Vector <Integer>();
v.add(12);
v.add(15);

int i = list.get(0);

In conclusione, l'Autoboxing / Unboxing è una funzionalità comoda, che non presenta particolari difficoltà concettuali e che pertanto non dovrebbe causare grossi problemi in fase di adozione.
Costrutto for di tipo "foreach"
Liste e vettori sono i contenitori di dati più usati, e per questa ragione è molto facile trovare in nei programmi frammenti di codice che processano liste in questo modo:

int[] numeri = new int[1000];
....
int totale;
for(int i=0;i< numeri.length;i++)
totale = toale + array[i];

Il framework Collection prevede l'uso di un appositor oggetto Iterator:

Vector<String> v = new Vector<String>();
….
Iterator<String> i = v.iterator();
While(i.hasNext())
System.out.println(i.next());

Molti linguaggi, tra cui ad esempio il Visual Basic, dispongono di un'apposita keyword "foreach" che permette di iterare tra gli elementi di una lista ricorrendo ad una formulazione del tipo foreach(element in list). I progettisti di Java hanno inserito un costrutto simile come variante del ciclo for. Il risultato è che ora è possibile riscrivere gli esempi precedenti in questo modo:

int[] numeri = new int[1000];
....
int totale;
for(int n : numeri)
totale = toale + n;

Vector<String> v = new Vector<String>();
….
Iterator<String> i = v.iterator();
for(String s : v)
System.out.println(s);

Il nuovo ciclo for richiede di specificare una variabile a cui verrà assegnato ad ogni iterazione l'elemento corrente, e un reference ad un vettore o ad una Collection separati dal simbolo ":".

L'utilità di questo costrutto è indiscutibile, ma rimane il dubbio se non sarebbe stato opportuno introdurre un'apposita keyword "foreach", in modo da rendere il codice più leggibile. D'altra parte è vero che il costrutto foreach canonico avrebbe richiesto anche l'uso della keyword "in", già utilizzata in migliaia di programmi come identificatore di variabile. E' pertanto probabile che in ultima istanza sia prevalsa la linea conservatrice, per ridurre al minimo le incompatibilità verso codice sorgente già esistente.
Annotazioni
Le annotazioni sono valori user defined che possono essere abbinati a classi, interfacce, metodi e attributi in modo da permettere successive elaborazioni da parte di ambienti di sviluppo, compilatori o tool di manipolazione del codice. Le annotazioni vengono definite "Metadati", dal momento che si tratta di dati che descrivono altri dati (in questo caso gli elementi del programma).

Le annotazioni sono in pratica una sorta di infrastruttura che gli sviluppatori di tool per la manipolazione di codice possono utilizzare per memorizzare informazioni utili direttamente nel codice sorgente, invece che in files dal formato proprietario. Vediamo un esempio di annotazione:

public @remote void call() {
….
}

L'annotazione vera e propria è un identificatore preceduto dal simbolo @, situato nella firma di una classe, un'interfaccia, un attributo o un metodo. Le annotazioni possono anche contenere dei parametri tra parentesi:

@debug(devbuild=production,counter=1) public void testMethod() {
}

I valori contenuti nelle annotazioni vengono memorizzati nel codice della classe, e pertanto possono essere letti e manipolati anche a Runitme con i meccanismi per la reflection:

ClasseAnnotata c = new ClasseAnnotata();
try {
Annotation[] a =c.getClass().getMethod("metodoAnnotato").getAnnotations();
for (int i=0; i<a.length ; i++) {
System.out.println("a["+i+"]="+a[i]+" ");
}
}
catch(NoSuchMethodException e) {
System.out.println(e);
}

Un tool può utilizzare le annotazioni per generare codice parametrizzato, o per eseguire operazioni particolari durante il debugging. Le annotazioni, ad esempio, possono semplificare la programmazione di Java Beans o EJB, dove le classi devono contenere codice ripetitivo legato al particolare framework applicativo.

Inutile nascondere che tra tutte le novità di questa edizione, le annotazioni sono certo la cosa che lascia più perplessi, non per la sua potenziale utilità, ma per il fatto che è difficile immaginare che un suo uso intensivo possa rendere il codice più comprensibile.
Import Statico
Nelle attuali versioni di Java si ricorre a variabili static final per definire costanti. Per accedere alle variabili statiche è necessario utilizzare la notazione puntata sull'identificatore della classe, ad esempio:

// Il volume della sfera qual'è? Quattro terzi pigreco r tre
double volumeSfera = 4/3 * Math.PI * raggio^3;

Le costanti sono molto usate all'interno delle API del JDK; si osservi ad esempio un frammento di codice che fa uso dell'API grafica Swing:

Jpanel p = new Jpanel();
p.setLayout(new BorderLayout());
p.add(BorderLayout.NORTH,p1);
p.add(BorderLayout.CENTER,p2);
p.add(BorderLayout.SOUTH,p3);

L'import statico permette di importare nel namespace di un sorgente metodi e attributi statici di una classe. Se ad esempio si desidera poter usare all'interno di un sorgente le funzioni matematiche (sin() , cos(), abs() ecc) come se fossero metodi della classe di lavoro, sarà ora sufficiente scrivere nell'intestazione di un sorgente la seguente istruzione:

Import static java.lang.Math.*;

Allo stesso modo, l'istruzione:

import static java.awt.BorderLayout.*;

permetterà di riscrivere l'esenpio precedente in questo modo:

p.add(NORTH,p1);
p.add(CENTER,p2);
p.add(SOUTH,p3);

Si noti che è possible importare anche singoli membri, come un singolo attributo o metodo:

import static java.lang.Math.PI;

double volumeSfera = 4/3 * PI * raggio^3;

L'unica critica che può eventualmente essere mossa a questo costrutto è che esso produce una grossa espansione dello spazio dei nomi, simile a quella prodotta dalla direttiva include presente in C e C++. L'abituale sintassi di chiamata a metodi statici è certamente verbosa, ma d'altra parte permette di identificare con precisione l'oggetto di una chiamata. Un'uso eccessivo di import statici potrebbe in definitiva creare confusione in fase di manutenzione dei sorgenti.

 

Typesafe Enums
Le enumerations sono insiemi di oggetti dotati di proprietà comuni; ogni oggetto è presente in memoria in un'unica istanza, e viene identificato attraverso un nome simbolico. In altre parole, le Enumerations sono insiemi di identificatori che denotano un tipo logico.
Un esempio tipico di enumerazione è l'elenco dei giorni della settimana: Lunedì, Martedì, Mercoledì, Giovedì, Venerdì, Sabato, Domenica. Altri esempi sono i colori dell'arcobaleno (Rosso, Arancio, Giallo, Verde, Blu, Indaco, Viola) i segni cardinali (Nord, Sud, Est, Ovest) e le stagioni (Autunno, Inverno, Primavera, Estate).

Dal momento che l'iterazione è uno dei fondamenti dell'automazione, non deve stupire che l'iterazione su insiemi logici possa tornare utile nella programmazione. Vediamo alcuni esempi:

  • Esegui una certa operazione per ogni giorno della settimana
  • Assegna ad auto della tua collezione un nome caratterizzato da uno dei colori dell'arcobaleno scelto a rotazione, seguito da un numero progressivo
  • Associa ad ogni sport le stagioni in cui può essere praticato
  • Componi un bouquet di fiori usando quelli disponibili

Fino ad ora la tecnica di rappresentazione delle enumerations dipendeva dall'utente. La tecnica più usata prevede il ricorso a costanti numeriche, come nel seguente esempio:

public class Settimana {
public static final int LUNEDI = 0;
public static final int MARTEDI = 1;

}

Il codice client accede ai valori statici attraverso la notazione puntata:

appuntamento.setDay(Settimana.LUNEDI);

Questa tecnica di programmazione è semplice da capire e da implementare, ma presenta d'altra parte numerosi difetti:

  • Non è tipizzata: per essere sicuri che il chiamante passi un valore corretto è necessario un controllo esplicito sul valore numerico
  • I nomi delle costanti appartengono al namespace di una particolare classe, e per essere utilizzati richiedono il ricorso alla notazione puntata
  • Il valore delle costanti viene compilato nel codice client. In questo modo si presenta una dipendenza pericolosa tra la classe che fornisce il servizio e quelle che lo utilizzano: se per qualche ragione i valori delle costanti devono essere cambiati, è necessario ricompilare tutte le classi che ne fanno uso.
  • Il valore reale del parametro non contiene informazioni stampabili.

Il costrutto enum permette di definire un insieme in questo modo:

public enum GiorniDellaSettimana { Lunedì, Martedì, Mercoledì, Giovedì, Venerdì, Sabato, Domenica};

Una variabile di tipo GiorniDellaSettimana può assumere unicamente uno dei valori specificati. Il controllo di appartenenza viene effettuato dal compilatore, e pertanto viene garantita la consistenza. Gli elementi di una enum sono oggetti: pertanto è possibile inserirli in una collection, stamparli, usarli come indici per una Hashtable, serializzarli ed usarli in un costrutto switch - case. Ogni enum presenta un attributo VALUES che contiene tutti i possibili valori, in modo da permettere l'iterazione. E' anche possibile, ricorrendo ad una sintassi un po' più complessa, definire una enum i cui elementi posseggono metodi ed attributi:

// Trenta giorni ha Novembre, con April, Giugno e Settembre…
public enum Mesi {
gennaio(31), febbraio(28), marzo(31), aprile(30),maggio(31),giugno(30),luglio(31),
agosto(31),settembre(30),ottobre(31),novembre(30),dicembre(31);

private final int giorni; // attributo
Coin(int giorni) { // costruttore
this. giorni = giorni;
}
public int getGiorni() { // metodo
return giorni;
}
}

In questo esempio l'enum Mesi denota un tipo caratterizzato dai valori gennaio, febbraio, marzo, aprile, maggio, giugno, luglio, agosto, settembre, ottobre, novembre, dicembre. Ciascun elemento della enumerazione è a sua volta caratterizzato da un differente valore dell'attributo "giorni", che può essere letto attraverso il metodo getGiorni():

public static void main(String[] args) {
for (Mesi m : Mesi.VALUES)
System.out.println("Il mese" + m + "ha "+m.getGiorni()+" giorni");
}

Da un punto di vista estetico il risultato è notevole: le enum permettono di definire, con una unica dichiarazione, una classe e l'insieme di tutte le sue possibili istanze che, è bene ricordare, nel caso di enumerazioni ha cardinalità finita. Se usate in contesti adeguati, le enum non dovrebbero presentare alcun problema al programmatore.

 

Varargs
L'ultima novità di Java 1.5 è la possibilità di dichiarare metodi che accettano un numero variabile di argomenti. Attualmente l'unico modo di passare un numero variabile di argomenti ad un metodo è attraverso un vettore:

public void stampa(String[] stringhe) {
for(int i=0;i<stringhe.length ; i++)
System.out.println(stringhe[i]);
}

L'aspetto più macchinoso di questo sistema è la formulazione della chiamata: prima di effettuare la chiamata vera e propria è infatti necessario inizializzare in modo esplicito il vettore degli argomenti:

String[] arguments = { "La","Vispa","Teresa","Avea","Tra","L'Erbetta"};
stampa(arguments);

Su Java 1.5 è invece possibile riscrivere il metodo stampa in questo modo:

public void stampa(String … stringhe) {
for(String s : stringhe)
System.out.println(s);
}

A differenza dell'esempio precedente, l'array viene definito in modo implicito attraverso l'identificatore "...". Il codice client ora può chiamare il metodo stampa() specificando gli argomenti direttamente nella chiamata:

stampa("La","Vispa","Teresa","Avea","Tra","L'Erbetta");

Si noti che il metodo così definito accetta un qualunque numero di argomenti:

stampa("Uno");
stampa("Uno","Due","Tre");
stampa("Uno","Due","Tre","Quattro","Cinque","Sei","Sette");
....

L'aggiunta di dei parametri variabili ha permesso di implementare una funzione printf() del tutto simile all'analoga funzione C:

System.out.printf("Lo studente %s ha totalizzato %5 punti.d\n", student,rating);

In questo modo è possibile effettuare un porting di codice legacy scritto in C mantenendo il formato di visualizzazione esistente. Nel complesso si tratta di un'aggiunta interessante, realizzata in modo pulito: per questo motivo non dovrebbero sorgere problemi in fase di adozione.

 

Conclusioni
L'uscita del nuovo J2SDK è destinata ad avere un grosso impatto sulla comunità degli sviluppatori, data la portata trasversale delle nuove features. Per dare un'idea di quanto profondo sia il cambiamento in atto, basti pensare che la classe java.lang.Class, una delle classi più importanti del linguaggio, è ora generica, e che questa scelta condiziona (sicuramente in meglio) tutte le funzionalità legate alla reflection. D'altra parte è innegabile che da oggi la curva di apprendimento di Java sarà un po' più ripida: solo col tempo potremo sapere se questa scelta non andrà ad influenzare in modo negativo la diffusione di Java nel mondo.

 

Bibliografia
[1] http://java.sun.com/j2se/1.5.0/download.jsp
[2] http://weblogs.java.net/pub/wlg/261
[3] http://pizzacompiler.sourceforge.net/
[4] http://www.cis.unisa.edu.au/~pizza/gj/
[5] http://jcp.org/en/jsr/detail?id=014
[6] http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it