Thursday, February 09, 2012

I Generics e la magia nera.

 Il problema generale

La JVM non supporta e non supporterà ancora per lungo tempo (JAVA 8 ??) i reified generics . Questo ha dato luogo, negli anni, a molte polemiche sull'usabilità effettiva dei generics.
In realtà se ci si trova ad utilizzare i Generics per fare qualcosa di più avanzato, ci si scontra prima o poi col problema di dover fare qualcosa del genere:




class MyClass<T> {
private final T o;
public MyClass() {
this.o = new T();
}
}



questo NON si può fare poiché a runtime MyClass non sa niente circa la natura del parametro generico T. Vedete, il fatto è che laddove javac è capace di effettuare un controllo sintattico e di validazione semantica del nostro codice, la jre non sa niente circa il tipo generico T che è stato inferito perché l'informazione è andata completamente persa in compilazione (type erasure).
Ma, -incredibile ma vero- c'è un ma, in determinate circostanze ciò diventa possibile mediante l'utilizzo di alcune booster feature abbastanza nascoste di Java che sono le Classi Anonime.

Tramite questa feature è possibile creare a runtime delle copie "personalizzate" delle classi che sono, per così dire, "usa e getta".

Classi Anonime (CA)

Ma che cosa sono le classi anonime? E soprattutto: che cosa NON sono?
Con le CA diviene possibile fare questa cosa:


MyClass<Double> myClass = new MyClass<Double>() {

// In questo blocco si aggiungono funzionalità

// a MyClass, specializzandola


// };


può essere cioè creato un oggetto myClass istanza di una classe anonima MyClass; il che non significa che MyClass sia una classe anonima.

Infatti MyClass non è assolutamente anonima (la dichiariamo), d'altra parte utilizziamo il "potere" di aggiungere funzionalità al codice di base, cosa che caratterizza le classi anonime.

javac e le CA

Già, ma come fa il compilatore in questi casi? Da quello che abbiamo visto è chiaro: traduce nel bytecode delle strutture dati completamente caratterizzate dal punto di vista dei parametri dei tipi generici utilizzati (che è bene) e dunque non attiva la type erasure (che è male).Messa diversamente:

la summenzionata MyClass non conosce nessuna informazione di tipo quando è chiamata così:


MyClass<Double> myClass = new MyClass<Double>();

ma conosce l'informazione che deve manipolare un tipo generico quando è chiamata così:


MyClass<Double> myClass = new MyClass<Double>() { /*something here */ };

Deve essere dunque cambiata la chiamata utilizzando la nostra classe "anonimizzata", dunque:


MyClass<Double> myClass1 = new MyClass<Double>(); //type erasure happens

MyClass<Double> myClass2 = new MyClass<Double>() { }; //type erasure DOES NOT happen! :)


Come vedete, abbiamo bisogno solo dello scheletro della nostra classe anonima, e non di tutta l'implementazione, se non abbiamo bisogno di logica aggiuntiva.

La soluzione classica


Se ricordiamo per un momento il tema generale, vediamo che quello che abbia mo visto finora, benché interessante e certamente utilizzabile, non è tutto. Per completare il discorso serve...la magia (nera?).
Qui noi non siamo solo interessati a che non intervenga la type erasure da parte del compilatore, ma anche ci interessa dare alla nostra classe MyClass la possibilità di sapere che il suo parametro generico T è effettivamente un tipo (ovvero T.class).

La soluzione classica di questo problema prevede che si faccia una chiamata di questo tipo:


MyClass<Double>myClass = new MyClass<Double>(Double.class);



Questa non è una soluzione ottimale sebbene è proprio quello che fanno gli sviluppatori, in ogni caso è perfettamente lecita. Ma..è tipo...magia! Sì però non è proprio buona. Non è ottimale per esempio il ricorso alle ripetizioni del tipo "reificato" Double: chissà che sforzo il compilatore!! Scherzi a parte, quando il nostro codice cuba 5k o 10k linee (seppure organizzate secondo tutte le best practices del mondo) diventa difficile da manutenere.
Fin qui la teoria, ma io mi sono chiesto se questa cosa funzionasse sul serio, l'ho implementata per un problema abbastanza complicato (ok,ok...me lo sono complicato da solo :) ) e grazie a questo "trucchetto" ho portato a casa la giornata.
La soluzione più smart è nell'articolo da cui ho tratto spunto per redigere questo post, e del quale ringrazio l'autore. Seppure si tratta di un articolo esplicativo di un particolare framework che serve ad un task ancora più particolare, la parte generale è ben scritta e comprensibile.
Fin quando non avremo in java un'implementazione nativa dei reified generics dovremo attrezzarci con le arti...magiche! Tant'è. Stay Tuned!