Documentazione Contatti      
Documentazione > Tutorial > Due parole sui Generics
Hide
Best Practices
EJB
Frameworks
Howto
J2EE
J2ME and Wireless
J2SE
JSP e Servlet
Java Application Server
Java IDE/Tools
Java Media
Java Security
Java Sys Admin
Java e XML
Java e SQL
OpenSource Java
Patterns
Repository
Tesi
UML
Web Services
Slide
White Paper di jws.it
project management
Eventi
Groovy



All4web Day a Milano, 8 maggio


Publio Cornelio Tacito
Tutte le cose che ora si credono antichissime furono nuove


Misurare il software con i Function Points



  Visualizza Commenti (0) Aggiungi Commento    
 
Due parole sui Generics
By Marco Pugliese
31 gennaio 2007

  Due parole sui Generics
Program Implementiamo il codice senza i generics...
Program ...e ora vediamo cosa succede usandoli
Program ...spingiamoci un po' più in là ancora
Program Cosa vuol dire tutto ciò?
Program Conclusioni


Se sentiamo parlare di generics, probabilmente ci viene facile l'associazione con oggetti come le mappe, le liste, le collezioni e tutti i cast (e le ClassCastException), ma le implicazioni di questa feature vanno ben oltre.

In questo articolo vorrei mostrare come l'uso dei generics, introdotti nella versione 1.5 della JDK, possa rendere più robusta e fruibile una gerarchia di classi.

Per la comprensione ottimale dell’ articolo, occorre avere una  conoscenza di base  delle principali novità introdotte nella  versione della JDK, 1.5, comunemente chiamata Tiger.
Per una introduzione, vi consiglio perciò la lettura di http://www.javaportal.it/download/1.5Tiger.pdf.

Iniziamo con il prendere in esame un esempio che riproduce una situazione frequente nella progettazione del codice.
La situazione è la presente:
Dobbiamo realizzare un insieme di oggetti per trasferire delle informazioni da un livello (tier) ad un altro dell'applicazione; come dominio utilizzeremo quello di un piccolo carrello della spesa rappresentato dalle seguenti classi:
BaseDto, ProdottoDto, OrdineDto, CdDto , LibroDto.

Per focalizzare l'attenzione sui  generics, abbiamo scelto di utilizzare un modello estremamente semplificato.
In questo scenario infatti non vengono  inclusi riferimenti a colui che effettua l'ordine o allo stato dello stesso, oppure si considera che gli autori dei cd e dei libri siano sempre semplici stringhe, e così a seguire.

 

La classe BaseDto ha unicamente la responsabilità di salvare e fornire l'identificativo univoco dell'elemento (per esempio la primary key della tabella che rappresenta).
In più è la radice della gerarchia delle nostre classi (DTO semplici classi contenitore).

La classe OrdineDto contiene una lista dei prodotti e ha la responsabilità di fornire il totale del prezzo dell'ordine.

La classe astratta ProdottoDto ha solo il prezzo e non può essere usata direttamente.

Le classi CdDto e LibroDto sono due possibili prodotti.



Implementiamo il codice senza i generics... top

Il problema è: come rappresentiamo l' id che sarà condiviso da tutti i DTO?

Tutti gli oggetti della gerarchia ereditano i metodi di accesso (setter e getter) all'id dalla classe BaseDto,  inoltre va considerato che ogni oggetto potrebbe avere per l' id un qualsiasi tipo java.
La soluzione che abbiamo adottato è stata quella di definire l’ id come Object. (Non è l'unica soluzione e neppure la migliore, ma si presta bene per evidenziare  i vantaggi dell'uso dei generics.)

Nel package org.generics.esempio1   (che torvate in allegato in questo articolo), vediamo come implementare il codice della gerarchia e come usarlo senza sfruttare i generics.

Attenzione: abbiamo messo in evidenza le righe di codice critiche (cioè quelle che più saranno coinvolte dalla riscrittura dello stesso esempio con i generics).

Il codice della classe BaseDto :

package org.generics.esempio1.dto;

public class BaseDto {
private Object id;
public Object getId() {
return id;
}
public void setId(Object id) {
this.id = id;
}
public boolean equals(Object object) {
if (object != null) {
if (object instanceof BaseDto) {
BaseDto dto = (BaseDto) object;
if (dto.getId() != null && getId() != null) {
return dto.getId().equals(getId());
}
}
}
return false;
}
}

Il codice della classe OrdineDto :

package org.generics.esempio1.dto;

import java.util.List;

public class OrdineDto extends BaseDto {
    private List prodotti;
    public List getProdotti() {
        return prodotti;
    }
    public void addProdotti(List prodotti) {
            if(this.prodotti==null)this.prodotti = new ArrayList();
        this.prodotti.addAll(prodotti);
    }
    public Double getTotale(){
        Double totale = .00;
        for (
Object prodotto : prodotti) {
           
totale += ((ProdottoDto) prodotto).getPrezzo();
        }
        return totale;
    }
    public boolean addProdotto(ProdottoDto prodotto){
        if(prodotti==null)prodotti = new ArrayList();
        return prodotti.add(prodotto);
    }

    public boolean removeProdotto(ProdottoDto prodotto){
        if(prodotti!=null){
            return prodotti.remove(prodotto);
        }
        return true;
    }
}


 

 Il codice delle altre classi  DTO non viene riportato per esteso nell’articolo, ma si può trovare nel file src.zip, in allegato.

Consideriamo anche come un client potrebbe utilizzare queste classi per creare e stampare un ordine (org.generics.esempio1.test.TestaCodice.java) .

Questo il codice per creare un libro:

List prodotti = new ArrayList();
LibroDto libroTmp;
String autoreTmp;
libroTmp = new LibroDto();
autoreTmp = "Stephen King";
libroTmp.setId(1);
libroTmp.setAutore(autoreTmp);
libroTmp.setNumeroPagine(202);
libroTmp.setPrezzo(22.30);
libroTmp.setTitolo("IT");
prodotti.add(libroTmp);

 

Questo il codice per creare un cd:

CdDto cdTmp = new CdDto();
autoreTmp = "Pat Metheny";
cdTmp.setId(1);
cdTmp.setAutore(autoreTmp);
cdTmp.setGenere(Genere.JAZZ);
cdTmp.setPrezzo(24.20);
cdTmp.setTitolo("We live here");
prodotti.add(cdTmp);


Questo il codice per creare al volo un prodotto non previsto con una descrizione aggiuntiva:

ProdottoDto prodotto = new ProdottoDto(){
private String descrizione = "usb-drive";

@Override
public String toString() {
return descrizione;
}
};
prodotto.setId(1);
prodotto.setPrezzo(79.90);
prodotti.add(prodotto);


Infine il codice che stampa l'ordine:


public void stampaOrdine(OrdineDto ordine) {
if (ordine != null) {
Integer idOrdine = (Integer) ordine.getId();
Double totaleOrdine = ordine.getTotale();
System.out.println("Ordine n°" + idOrdine + ":");
for (Object prodotto : ordine.getProdotti()) {
if (prodotto instanceof CdDto) {
stampa((CdDto) prodotto);
} else if (prodotto instanceof LibroDto) {
stampa((LibroDto) prodotto);
} else if (prodotto instanceof ProdottoDto) {
stampa((ProdottoDto) prodotto);
}
}
System.out.println("");
System.out.printf("Totale ordine %99s", totaleOrdine);
}
}

 

Questo approccio implica alcuni svantaggi che, vengo in parte risolti dai generics.

Gli svantaggi sono:

1.Ogni volta che vogliamo accedere al valore dell'id di un DTO, dobbiamo controllare che il tipo sia corretto e castarlo per poter accedere ai metodi dello stesso. Si pensi per esempio al tipo Date (secondo me sconsigliabile come campo chiave), in fase di presentazione richiede di essere formattato.

2.Il compilatore non sa se il tipo di dato passato al metodo setId(Object id) sia corretto.

3.Dal punto 2 segue anche che, il codice presentato, si presta ad un errore runtime di cast exception. Per esempio nel caso in cui l' id venga valorizzato da un client con un tipo di dato sbagliato, tuttavia accettato dal metodo setId(Object id).

4.Un altro possibile problema si presenta in fase di refactoring. 
Si immagini per esempio di cambiare il tipo dell'id dei libri da int a String per adottare l' isbn(cioè un codice alfanumerico) come chiave.
Stando al codice, ci dovremmo limitare a passare il codice isbn al metodo setId(Object id) in formato String, dato che è un codice alfanumerico.
Sopraggiunge però un problema perché il compilatore non rileva tutti i cast a Integer presenti nel codice, si è costretti a cercare tutte le chiamate al metodo getId() e sostituire l'eventuale cast al vecchio tipo con il nuovo.



...e ora vediamo cosa succede usandoli top

Nel codice del package org.generics.esempio2, si trovano le classi implementate sfruttando l'uso dei generics, in particolare vengono utilizzati per tipizzare l'id.
Ecco come si presenta BaseDto dopo il refactoring:

package org.generics.esempio2.dto;

public class BaseDto
<T> {

private
T id;

public
T getId() {
return id;
}

public void setId(
T id) {
this.id = id;
}

public boolean equals(Object object) {
if (object != null) {
if (object instanceof BaseDto) {
BaseDto dto = (BaseDto) object;
if (dto.getId() != null && getId() != null) {
return dto.getId().equals(getId());
}
}
}
return false;
}

}


Attenzione: Questa volta, in evidenza, ci sono le modifiche apportate al codice.

Partiamo dalla prima modifica cioè public class BaseDto<T> { .

La sintassi che segue la dichiarazione di classe <T> indica che, nel corpo della classe, si farà riferimento ad un tipo di dato con il nome T.

Il nome è simile al concetto di nome di variabile, quindi si può usare indifferentemente T o E o MioTipO o b1, ma ci si riferisce ad un tipo e non ad un valore. (Per convenzione si usa una lettera maiuscola per ogni tipo generico che si utilizza).

Infatti in BaseDto sarebbe possibile scrivere : T varTemp = (T)getId(); (questo codice però è ridondante perché il metodo getId() restituisce già il tipo T come vedremo in seguito).

Grazie a questa dichiarazione è quindi possibile scrivere private T id; e, di seguito, public T getId() { e public void setId(T id) {.

Una delle implicazioni più interessanti di questo codice è che se effettuiamo l'ovverriding dei due metodi, nel seguente modo:public Integer getId() e public void setId(Integer id), il compilatore li riconoscerà effettivamente come tali.
 
Perciò, ci viene data la possibilità di sfruttare la potenza del polimorfismo anche nel caso in cui i parametri in input e/o ouptut del metodo generico sono differenti nelle diverse specializzazioni della classe.

Come si può osservare, il tipo della variabile id non viene definito in fase di scrittura del codice, ma lo sarà a compile time in base alle indicazioni fornite nel momento in cui l'oggetto verrà dichiarato e istanziato.

Si può, quindi, scrivere (analogamente a come avviene con List, Collection etc) il seguente codice:

BaseDto<String> dto = new BaseDto<String>();
//a questo punto il compilatore sa con che
//tipo trattare l'id di BaseDto e T è sostituita
//da String
dto.setId("qualcosa");//corretto
dto.setId(12.3D);
//The method setId(String) in the type BaseDto<String>
//is not applicable for the arguments (double)



Il  codice  informa il compilatore su come trattare il tipo dell'id. 

Se, nella successiva riga si invocasse il metodo setId(T id), il compilatore si aspetterebbe un parametro di tipo String conformemente alla dichiarazione.

Anche il  codice della classe ProdottoDto subisce qualche adattamento:

package org.generics.esempio2.dto;

public abstract class ProdottoDto<
T> extends BaseDto<T> {

private Double prezzo;

public Double getPrezzo() {
return prezzo;
}

public void setPrezzo(Double prezzo) {
this.prezzo = prezzo;
}
}


In questa classe,  ProdottoDto rimanda il tipo a BaseDto, in pratica nel dichiarare un nuovo ProdottoDto si potrà scrivere new ProdottoDto<Integer>();

Integer viene passato a BaseDto e quindi potrà essere utilizzato per tipizzarne l'id.

Le classi restanti necessitano di una semplice modifica alla dichiarazione di classe:

public class CdDto extends ProdottoDto<Integer>{...}

public class LibroDto extends ProdottoDto<
Integer>{...}

La classe OdrineDto ha qualche differenza in più:

package org.generics.esempio2.dto;

import java.util.ArrayList;
import java.util.List;

import org.generics.esempio2.dto.ProdottoDto;

public class OrdineDto extends BaseDto<
Integer> {

    private List<ProdottoDto> prodotti;
        
    public Double getTotale(){
        Double totale = .00;
        for (ProdottoDto prodotto : prodotti) {
            totale += prodotto.getPrezzo();
        }
        return totale;
    }

    public List<ProdottoDto> getProdotti() {
        return prodotti;
    }

    public void addProdotti(List
<? extends ProdottoDto> prodotti) {
         if(this.prodotti==null)this.prodotti = new ArrayList<ProdottoDto>();
         this.prodotti.addAll(prodotti);
    }

    public boolean addProdotto(ProdottoDto prodotto){
        if(prodotti==null)prodotti = new ArrayList<ProdottoDto>();
        return prodotti.add(prodotto);
    }

    public boolean removeProdotto(ProdottoDto prodotto){
        if(prodotti!=null){
            return prodotti.remove(prodotto);
        }
        return true;
    }

}


Praticamente la dichiarazione OrdineDto extends BaseDto<Integer> indica al compilatore che, la classe OrdineDto, sarà una BaseDto tipizzata a Integer.
Questo significa che d'ora in poi il compilatore si comporterà come se i metodi di accesso all'id ereditati, fossero public Integer getId(); e public void setId(Integer id).

La sintassi List<? extends ProdottoDto> prodotti, è necessaria, perché List<ProdottoDto> non è superclasse di List<CdDto>, anche se ProdottoDto è superclasse di CdDto.
Con questa sintassi specifichiamo che il metodo deve accettare ogni parametro List che abbia come tipo una qualunque sottoclasse di ProdottoDto.

Se la signature del metodo fosse stata:

public void addProdotti(List<ProdottoDto> prodotti){...}

il seguente codice avrebbe generato un errore di compilazione:

List<CdDto> cds = new ArrayList<CdDto>();
ordine.addProdotti(cds);


perché ordine non definisce nessun metodo che prenda come parametro il tipo List<CdDto>.

Per quel che riguarda il client del modello, le differenze sono minime, a livello di scrittura del codice, ma profonde a livello di robustezza e manutenibilità.

Questo è il codice per  creare un libro:

List<ProdottoDto> prodotti = new ArrayList<ProdottoDto>();

LibroDto libroTmp;
String autoreTmp;
libroTmp = new LibroDto();
autoreTmp = "Stephen King";
libroTmp.setId(1);
libroTmp.setAutore(autoreTmp);
libroTmp.setNumeroPagine(202);
libroTmp.setPrezzo(22.30);
libroTmp.setTitolo("IT");
prodotti.add(libroTmp);


Questo è il codice per  creare un cd:

CdDto cdTmp = new CdDto();
autoreTmp = "Pat Metheny";
cdTmp.setId(1);
cdTmp.setAutore(autoreTmp);
cdTmp.setGenere(Genere.JAZZ);
cdTmp.setPrezzo(24.20);
cdTmp.setTitolo("We live here");
prodotti.add(cdTmp);


Questo è il codice per  creare un prodotto non previsto al volo con una descrizione aggiuntiva:

ProdottoDto<Integer> prodotto = new ProdottoDto<Integer>(){
private String descrizione = "usb-drive";

@Override
public String toString() {
return descrizione;
}
};
prodotto.setId(1);
prodotto.setPrezzo(79.90);
prodotti.add(prodotto);


Questo è il codice per  stampare l'ordine:

public void stampaOrdine(OrdineDto<Integer> ordine) {
if (ordine != null) {
Integer idOrdine = ordine.getId();
Double totaleOrdine = ordine.getTotale();
System.out.println("Ordine n°" + idOrdine + ":");
for (ProdottoDto
<?> prodotto : ordine.getProdotti()) {
if (prodotto instanceof CdDto) {
stampa((CdDto) prodotto);
} else if (prodotto instanceof LibroDto) {
stampa((LibroDto) prodotto);
} else{
stampa(prodotto);
}
}
System.out.println("");
System.out.printf("Totale ordine %99s", totaleOrdine);
}
}


Nel metodo stampaOrdine() troviamo la dichiarazione ProdottoDto<?>.

Questa dichiarazione  viene usata in quanto non si conosce a priori quale tipo sarà associato al prodotto, infatti <?> significa qualsiasi tipo.
Infatti, si rende  necessario usare  <?> perché List<Object> non è radice di tutte le liste generics. Questo particolare aspetto del paradigma, introdotto con i generics, può generare confusione, in quanto possiamo scrivere:

List<String> list = new ArrayList<String>();
list.add(new String("mah?"));

List<Object> list2 = new ArrayList<Object>();
list2.add(new String("boh!"));

list2=list;//Illegale, infatti il compilatore
//Type mismatch: cannot convert from List<String> to List<Object>
list2.addAll(list);//Corretto, ma perché?


La dichiarazione  list2=list; non viene accettata dal compilatore, mentre invece è corretto scrivere list2.addAll(list); , ma perché?
Perché il metodo addAll è così definito:
boolean addAll(Collection<? Extends E> c) .

il metodo  addAll accetterà ogni collezione (infatti la lista è una collezione) che sia tipizzata con un qualunque tipo che derivi da E.
Dal momento che list2 definisce il tipo Object, e String è sottoclasse di Object, l'invocazione del suddetto metodo, passando il parametro di tipo List<String>, sarà valida (la stessa considerazione è stata fatta per il metodo addProdotti(...) di OrdineDto).

Ok, tiriamo le somme:

1.Ogni volta che vogliamo accedere al valore dell'id di un DTO, sappiamo già a compile time quale sia il tipo da trattare.

2.Il compilatore sa che tipo di dato l'oggetto tratterà, ne consegue  che un client non può sbagliare il tipo passato al metodo setId(T id).

3.Nell’ eventualità che si decida di cambiare  il tipo dell'id dei libri da int a String, deve essere  ridefinita la dichiarazione della classe LibroDto.
Il compilatore rileverà e segnalerà come errori tutti i cast a Integer (peraltro superflui) e le assegnazioni a variabili Integer. Quindi, risolvendo opportunamente tutti gli errori a compile time, si sarà certi che in produzione il codice non farà sorprese (almeno nel trattamento dell'id ;-p).

 

...spingiamoci un po' più in là ancora top

Analizzando il  codice del package org.generics.esempio3, possiamo vedere un esempio di come estremizzare ulteriormente l'uso dei generics, che consiste nello spostare sul client del modello la definizione del tipo (così come avviene quando si usa una List).

Osservate che BaseDto e ProdottoDto non cambiano, a differenza delle dichiarazioni di LibroDto, CdDto e OrdineDto:

public class CdDto<T> extends ProdottoDto<T>{...}

public class LibroDto
<T> extends ProdottoDto<T>{...}

public class OrdineDto
<T> extends BaseDto<T> {...}

Così facendo, abbiamo tipizzato anche la classe finale, per cui i client potranno utilizzare i seguenti  codici.

Così creo un libro:

List<ProdottoDto> prodotti = new ArrayList<ProdottoDto>();

LibroDto
<Integer> libroTmp;
String autoreTmp;
libroTmp = new LibroDto
<Integer>();
autoreTmp = "Stephen King";
libroTmp.setId(1);
libroTmp.setAutore(autoreTmp);
libroTmp.setNumeroPagine(202);
libroTmp.setPrezzo(22.30);
libroTmp.setTitolo("IT");
prodotti.add(libroTmp);



Così creo un cd:

CdDto<Integer> cdTmp = new CdDto<Integer>();
autoreTmp = "Pat Metheny";
cdTmp.setId(1);
cdTmp.setAutore(autoreTmp);
cdTmp.setGenere(Genere.JAZZ);
cdTmp.setPrezzo(24.20);
cdTmp.setTitolo("We live here");
prodotti.add(cdTmp);


Infine il codice che stampa l'ordine:

public void stampaOrdine(OrdineDto<Integer> ordine) {
if (ordine != null) {
Integer idOrdine = ordine.getId();
Double totaleOrdine = ordine.getTotale();
System.out.println("Ordine n°" + idOrdine + ":");
for (ProdottoDto
<?> prodotto : ordine.getProdotti()) {
if (prodotto instanceof CdDto) {
stampa((CdDto) prodotto);
} else if (prodotto instanceof LibroDto) {
stampa((LibroDto) prodotto);
} else{
stampa(prodotto);
}
}
System.out.println("");
System.out.printf("Totale ordine %99s", totaleOrdine);
}
}


La differenza nell'uso del nostro modello è evidente, tuttavia ci sono degli inconvenienti.

Questo ulteriore approccio proposto è più adatto alla creazione di oggetti riutilizzabili, quali List<E>, Collection<E>, Map<K, V> o Class<T> o ancora Enum<E extends Enum<E>>.

Ok, ri-tiriamo le somme (!):

1.Ogni volta che vogliamo accedere al valore dell'id di un DTO, sappiamo già a compile time quale sia il tipo da trattare, ma questo sarà corretto solo se il client ha usato correttamente il codice. Infatti in questo caso è lecito dichiarare stramberie come CdDto<File> cdTmp = new CdDto<File>(); oppure CdDto cdTmp = new CdDto(); che non assegna il tipo, quindi implicitamente ci si riferisce ad un tipo Object.

2.Il compilatore sa che tipo di dato l'oggetto tratterà, ma solo dopo che il client avrà tipizzato l'istanza.

3.Dal punto 2 segue che il client del modello può sbagliare nel tipizzare le instanze dei prodotti, o attribuire un tipo diverso ad ogni nuova dichiarazione, con nefaste conseguenze a run time.

4.Nell’ eventualità che ci trovassimo nell’esigenza  cambiare il tipo dell'id dei libri da int a String, il client dovrà ridefinire tutte le dichiarazioni delle istanze sparse nel codice dell'applicazione senza l'aiuto degli errori di compilazione.



Cosa vuol dire tutto ciò? top

Se decompiliamo le classi generiche e analizziamo il codice, può sorgere il legittimo sospetto che in realtà nulla o quasi sia cambiato, infatti in corrispondenza di:

[...]
public class BaseDto
<T> {

private
T id;

public
T getId() {
return id;
}

public void setId(
T id) {
this.id = id;
}

[...]


... troveremo...

[...]
public class BaseDto{

private Object id;

public Object getId(){
return id;
}

public void setId(Object id){
this.id = id;
}
[...]


Perciò non c’è  nessuna traccia di generici.

Altro esempio, in corrispondenza di:

[...]
public class OrdineDto extends BaseDto
<Integer> {
[...]
public void addProdotti(List
<? extends ProdottoDto> prodotti) {
if(this.prodotti==null)this.prodotti = new ArrayList<ProdottoDto>();
this.prodotti.addAll(prodotti);
}
[...]
}
[...]

Il codice sembra apparire  come prima di tiger e dei generics...

[...]
public class OrdineDto extends BaseDto {
[...]
public void addProdotti(List prodotti)
{
if(this.prodotti == null)
this.prodotti = new ArrayList();
this.prodotti.addAll(prodotti);
}
[...]
}
[...]


Ma allora, se il codice generics viene convertito in codice standard, potremmo far girare classi generiche su una versione precedente della JDK?

In realtà no, analizziamo  le due seguenti classi:

public class SuperClasse {

public Object stampa(){
System.out.println("Metodo stampa() di SuperClasse");
return this;
}
}


e

public class SottoClasse extends SuperClasse{

public String stampa(){
System.out.println("Metodo stampa() di SottoClasse");
return "return value";
}

}


Se provate a compilarle con java 1.4 il compilatore vi restituirà (javac -source 1.4 *.java):





Giustamente, come eravamo abituati, java si aspetta la stessa signature per implementare l'overriding di un metodo.

Ma tiger ci riserva una sorpresa strettamente connessa al paradigma dei generics.
Ricompilando il codice con java 5, infatti, l'errore non c'è più!!
Il compilatore ha accettato l'overriding del metodo di SuperClasse, e in più ha aggiunto un pezzo di codice (formalmente illecito?!!?) a SottoClasse (almeno così sembra decompilando con Jad):

public class SottoClasse extends SuperClasse{

public SottoClasse(){
}

public String stampa(){
System.out.println("Metodo stampa() di SottoClasse");
return "return value";
}

public volatile Object stampa(){
return stampa();
}
}


Inoltre, eseguendo il codice come segue

SuperClasse classe = new SuperClasse();
Object o = classe.stampa();
System.out.println(o);
classe = new SottoClasse();
String s = (String)classe.stampa();
System.out.println(s);

Otteniamo il seguente output:

Metodo stampa() di SuperClasse
org.generics.esempio2.dto.SuperClasse@1bf52a5
Metodo stampa() di SottoClasse
return value


Risulta quindi chiaro che, il metodo public String stampa() di SottoClasse, effettua l'overriding di public Object stampa().

Ne consegue che java 5 ha esteso il concetto di overriding, che è strettamente connesso a quello di polimorfismo.

Nei  documenti della sun, si legge che questa scelta è stata resa necessaria per consentire una migrazione “easy” verso l'uso dei generics mantenendo intatta la gerarchia delle classi implementata con java 1.4. (http://java.sun.com/docs/books/jls/third_edition/html/classes.html#227927 , http://java.sun.com/docs/books/jls/third_edition/html/classes.html#38649) .



Conclusioni top

Trovo che l'approccio all'uso dei generics, offra una possibilità elegante e pulita di rendere più robusta la progettazione delle nostre classi consentendo di affrontare una eventuale operazione di refactoring con maggiore serenità.

I generics e le annotations offrono sicuramente vantaggi sia in termini di robustezza del codice sia in termini di minore verbosità, ma le implicazioni possono avere un impatto interessante anche sul piano della progettazione e manutenibilità del codice.

 Attachments List
Generic Documentsrc_generics.zip



JavaPortal è ideato da:    
K-Tech Logo










LICENZA



Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto licenza Creative Commons

Sitemap  © 2002-2004 Copyright Information. Privacy . Today is sabato 19 giugno 2010