Ho pensato di offrire questo softball a chiunque volesse tirarlo fuori dal parco. Cosa sono i generici, quali sono i vantaggi dei generici, perché, dove, come dovrei usarli? Per favore mantienilo abbastanza semplice. Grazie.
Ho pensato di offrire questo softball a chiunque volesse tirarlo fuori dal parco. Cosa sono i generici, quali sono i vantaggi dei generici, perché, dove, come dovrei usarli? Per favore mantienilo abbastanza semplice. Grazie.
Risposte:
Odio davvero ripetermi. Odio scrivere la stessa cosa più spesso di quanto sia necessario. Non mi piace ripetere le cose più volte con lievi differenze.
Invece di creare:
class MyObjectList {
MyObject get(int index) {...}
}
class MyOtherObjectList {
MyOtherObject get(int index) {...}
}
class AnotherObjectList {
AnotherObject get(int index) {...}
}
Posso creare una classe riutilizzabile ... (nel caso in cui non desideri utilizzare la raccolta non elaborata per qualche motivo)
class MyList<T> {
T get(int index) { ... }
}
Ora sono 3 volte più efficiente e devo mantenere solo una copia. Perché NON vorresti mantenere meno codice?
Questo vale anche per le classi non di raccolta come a Callable<T>
o a Reference<T>
che devono interagire con altre classi. Vuoi davvero estendere Callable<T>
e Future<T>
ed ogni altra classe associata per creare versioni type-safe?
Io non.
Non aver bisogno di typecast è uno dei maggiori vantaggi dei generici Java , poiché eseguirà il controllo del tipo in fase di compilazione. Ciò ridurrà la possibilità di messaggi di posta ClassCastException
elettronica che possono essere lanciati in fase di esecuzione e può portare a un codice più robusto.
Ma sospetto che tu ne sia pienamente consapevole.
Ogni volta che guardo i Generics mi viene il mal di testa. Trovo che la parte migliore di Java sia la semplicità e la sintassi minima e i generici non sono semplici e aggiungono una quantità significativa di nuova sintassi.
In un primo momento, non ho visto nemmeno il beneficio dei generici. Ho iniziato ad imparare Java dalla sintassi 1.4 (anche se Java 5 era fuori in quel momento) e quando ho incontrato i generici, ho sentito che era più codice da scrivere e non ho davvero capito i vantaggi.
Gli IDE moderni semplificano la scrittura di codice con i generici.
La maggior parte degli IDE moderni e decenti sono abbastanza intelligenti da aiutare a scrivere codice con i generici, specialmente con il completamento del codice.
Ecco un esempio di creazione Map<String, Integer>
di un file HashMap
. Il codice che dovrei digitare è:
Map<String, Integer> m = new HashMap<String, Integer>();
E in effetti, è molto da scrivere solo per crearne uno nuovo HashMap
. Tuttavia, in realtà, ho dovuto digitare solo così tanto prima che Eclipse sapesse di cosa avevo bisogno:
Map<String, Integer> m = new Ha
Ctrl+Space
È vero, avevo bisogno di selezionare HashMap
da un elenco di candidati, ma fondamentalmente l'IDE sapeva cosa aggiungere, compresi i tipi generici. Con gli strumenti giusti, usare i generici non è poi così male.
Inoltre, poiché i tipi sono noti, quando si recuperano elementi dalla raccolta generica, l'IDE si comporterà come se quell'oggetto fosse già un oggetto del suo tipo dichiarato: non è necessario eseguire il casting per l'IDE per sapere quale sia il tipo di oggetto è.
Un vantaggio chiave dei generici deriva dal modo in cui funziona bene con le nuove funzionalità di Java 5. Ecco un esempio di come inserire numeri interi in a Set
e calcolarne il totale:
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);
int total = 0;
for (int i : set) {
total += i;
}
In quel pezzo di codice, ci sono tre nuove funzionalità di Java 5 presenti:
Innanzitutto, i generici e l'autoboxing delle primitive consentono le seguenti righe:
set.add(10);
set.add(42);
L'intero 10
viene inserito automaticamente in un Integer
con il valore di 10
. (E lo stesso per 42
). Quindi quello Integer
viene gettato in quello Set
che è noto per contenere Integer
s. Il tentativo di lanciare un String
causerebbe un errore di compilazione.
Successivamente, for for-each loop prende tutti e tre quelli:
for (int i : set) {
total += i;
}
In primo luogo, i Set
contenitori Integer
vengono usati in un ciclo for-each. Ogni elemento è dichiarato come an int
e ciò è consentito in quanto Integer
viene ripristinato alla primitiva int
. E il fatto che si verifichi questo unboxing è noto perché i generici sono stati utilizzati per specificare che erano Integer
tenuti in Set
.
I generici possono essere il collante che riunisce le nuove funzionalità introdotte in Java 5 e rende la codifica più semplice e sicura. E la maggior parte delle volte gli IDE sono abbastanza intelligenti da aiutarti con buoni suggerimenti, quindi in generale, non sarà molto più necessario digitare.
E francamente, come si può vedere Set
dall'esempio, ritengo che l'utilizzo delle funzionalità di Java 5 possa rendere il codice più conciso e robusto.
Modifica: un esempio senza generici
Quanto segue è un'illustrazione dell'esempio precedente Set
senza l'uso di generici. È possibile, ma non è esattamente piacevole:
Set set = new HashSet();
set.add(10);
set.add(42);
int total = 0;
for (Object o : set) {
total += (Integer)o;
}
(Nota: il codice sopra genererà un avviso di conversione non controllato in fase di compilazione.)
Quando si utilizzano raccolte non generiche, i tipi immessi nella raccolta sono oggetti di tipo Object
. Pertanto, in questo esempio, a Object
è ciò che viene add
inserito nel set.
set.add(10);
set.add(42);
Nelle righe precedenti, l'autoboxing è in gioco: il int
valore primitivo 10
e 42
viene autoboxed in Integer
oggetti, che vengono aggiunti al file Set
. Tuttavia, tieni presente che gli Integer
oggetti vengono gestiti come Object
s, poiché non ci sono informazioni sul tipo per aiutare il compilatore a sapere quale tipo Set
dovrebbe aspettarsi.
for (Object o : set) {
Questa è la parte cruciale. Il motivo per cui il ciclo for-each funziona è perché Set
implementa l' Iterable
interfaccia, che restituisce una Iterator
con informazioni sul tipo, se presente. ( Iterator<T>
, cioè.)
Tuttavia, poiché non ci sono informazioni sul tipo, Set
restituirà an Iterator
che restituirà i valori in Set
as Object
s, ed è per questo che l'elemento che viene recuperato nel ciclo for-each deve essere di tipo Object
.
Ora che Object
è stato recuperato da Set
, è necessario Integer
eseguirne il cast manualmente per eseguire l'aggiunta:
total += (Integer)o;
Qui, un typecast viene eseguito da un Object
a un Integer
. In questo caso, sappiamo che funzionerà sempre, ma il typecasting manuale mi fa sempre sentire che è un codice fragile che potrebbe essere danneggiato se viene apportata una piccola modifica altrove. (Sento che ogni typecast è in ClassCastException
attesa che accada, ma sto divagando ...)
Il Integer
è ora unboxed in un int
e consentito di eseguire l'aggiunta nella int
variabile total
.
Spero di poter illustrare che le nuove funzionalità di Java 5 possono essere utilizzate con codice non generico, ma semplicemente non è così pulito e diretto come scrivere codice con generici. E, a mio parere, per sfruttare appieno le nuove funzionalità di Java 5, si dovrebbe esaminare i generici, se non altro, consentire controlli in fase di compilazione per evitare che i typecast non validi generino eccezioni in fase di esecuzione.
Se dovessi cercare nel database dei bug di Java appena prima del rilascio della 1.5, troverai sette volte più bug con NullPointerException
che ClassCastException
. Quindi non sembra che sia una grande caratteristica trovare bug, o almeno bug che persistono dopo un po 'di test del fumo.
Per me l'enorme vantaggio dei generici è che documentano nel codice informazioni importanti sul tipo. Se non volevo che le informazioni sul tipo fossero documentate nel codice, allora userei un linguaggio digitato dinamicamente, o almeno un linguaggio con inferenza di tipo più implicita.
Tenere per sé le raccolte di un oggetto non è un cattivo stile (ma lo stile comune è ignorare efficacemente l'incapsulamento). Dipende piuttosto da cosa stai facendo. Il passaggio delle raccolte agli "algoritmi" è leggermente più facile da controllare (in fase di compilazione o prima) con i generici.
I generici in Java facilitano il polimorfismo parametrico . Per mezzo dei parametri di tipo, puoi passare argomenti ai tipi. Proprio come un metodo come String foo(String s)
modella un comportamento, non solo per una stringa particolare, ma per qualsiasi stringa s
, così un tipo come List<T>
modella un comportamento, non solo per un tipo specifico, ma per qualsiasi tipo . List<T>
dice che per ogni tipo T
, c'è un tipo i List
cui elementi sono T
s . Quindi List
è in realtà un costruttore di tipi . Prende un tipo come argomento e ne costruisce un altro.
Ecco un paio di esempi di tipi generici che uso ogni giorno. Innanzitutto, un'interfaccia generica molto utile:
public interface F<A, B> {
public B f(A a);
}
Questa interfaccia dice che per alcuni due tipi, A
e B
, c'è una funzione (chiamata f
) che accetta A
e restituisce a B
. Quando si implementa questa interfaccia, A
e si B
può essere di qualsiasi tipo si desideri, purché si fornisca una funzione f
che accetta la prima e restituisce la seconda. Ecco un esempio di implementazione dell'interfaccia:
F<Integer, String> intToString = new F<Integer, String>() {
public String f(int i) {
return String.valueOf(i);
}
}
Prima dei generici, il polimorfismo veniva ottenuto mediante la sottoclasse utilizzando la extends
parola chiave. Con i generici, possiamo effettivamente eliminare le sottoclassi e utilizzare invece il polimorfismo parametrico. Si consideri ad esempio una classe parametrizzata (generica) utilizzata per calcolare codici hash per qualsiasi tipo. Invece di sovrascrivere Object.hashCode (), useremmo una classe generica come questa:
public final class Hash<A> {
private final F<A, Integer> hashFunction;
public Hash(final F<A, Integer> f) {
this.hashFunction = f;
}
public int hash(A a) {
return hashFunction.f(a);
}
}
Questo è molto più flessibile rispetto all'uso dell'ereditarietà, perché possiamo rimanere con il tema dell'uso della composizione e del polimorfismo parametrico senza bloccare gerarchie fragili.
I generici di Java non sono perfetti però. Puoi astrarre sui tipi, ma non puoi astrarre sui costruttori di tipi, ad esempio. Cioè, puoi dire "per qualsiasi tipo T", ma non puoi dire "per qualsiasi tipo T che accetta un parametro di tipo A".
Ho scritto un articolo su questi limiti dei generici Java, qui.
Una grande vittoria con i generici è che ti consentono di evitare le sottoclassi. La creazione di sottoclassi tende a produrre fragili gerarchie di classi difficili da estendere e classi difficili da comprendere individualmente senza considerare l'intera gerarchia.
Che a norma prima di farmaci generici si potrebbe avere classi come Widget
prorogato di FooWidget
, BarWidget
e BazWidget
, con i generici si può avere una sola classe generica Widget<A>
che prende un Foo
, Bar
o Baz
nel suo costruttore di darvi Widget<Foo>
, Widget<Bar>
e Widget<Baz>
.
I generici evitano il successo delle prestazioni di boxe e unboxing. Fondamentalmente, guarda ArrayList vs List <T>. Entrambi fanno le stesse cose fondamentali, ma List <T> sarà molto più veloce perché non devi boxare da / a oggetto.
Il miglior vantaggio per Generics è il riutilizzo del codice. Supponiamo che tu abbia molti oggetti di business e scriverai codice MOLTO simile per ogni entità per eseguire le stesse azioni. (IE Linq alle operazioni SQL).
Con i generici, puoi creare una classe che sarà in grado di funzionare dato uno qualsiasi dei tipi che ereditano da una data classe base o implementare una data interfaccia in questo modo:
public interface IEntity
{
}
public class Employee : IEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
}
public class Company : IEntity
{
public string Name { get; set; }
public string TaxID { get; set }
}
public class DataService<ENTITY, DATACONTEXT>
where ENTITY : class, IEntity, new()
where DATACONTEXT : DataContext, new()
{
public void Create(List<ENTITY> entities)
{
using (DATACONTEXT db = new DATACONTEXT())
{
Table<ENTITY> table = db.GetTable<ENTITY>();
foreach (ENTITY entity in entities)
table.InsertOnSubmit (entity);
db.SubmitChanges();
}
}
}
public class MyTest
{
public void DoSomething()
{
var dataService = new DataService<Employee, MyDataContext>();
dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
var otherDataService = new DataService<Company, MyDataContext>();
otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });
}
}
Si noti il riutilizzo dello stesso servizio dati i diversi tipi nel metodo DoSomething sopra. Veramente elegante!
Ci sono molti altri ottimi motivi per usare i generici per il tuo lavoro, questo è il mio preferito.
Mi piacciono solo perché ti danno un modo rapido per definire un tipo personalizzato (come li uso comunque).
Quindi, ad esempio, invece di definire una struttura composta da una stringa e un numero intero, e quindi dover implementare un intero set di oggetti e metodi su come accedere a un array di quelle strutture e così via, puoi semplicemente creare un Dizionario
Dictionary<int, string> dictionary = new Dictionary<int, string>();
E il compilatore / IDE fa il resto del lavoro pesante. Un dizionario in particolare consente di utilizzare il primo tipo come chiave (senza valori ripetuti).
Collezioni digitate: anche se non vuoi usarle, probabilmente dovrai gestirle da altre librerie, altre fonti.
Digitazione generica nella creazione della classe:
public class Foo <T> {public T get () ...
Evitare il casting - Non mi sono sempre piaciute cose come
nuovo Comparatore {public int compareTo (Object o) {if (o instanceof classIcareAbout) ...
Dove essenzialmente stai verificando una condizione che dovrebbe esistere solo perché l'interfaccia è espressa in termini di oggetti.
La mia reazione iniziale ai generici è stata simile alla tua: "troppo disordinato, troppo complicato". La mia esperienza è che dopo averli usati per un po 'ci si abitua e il codice senza di essi sembra meno chiaramente specificato e solo meno comodo. A parte questo, il resto del mondo Java li usa, quindi alla fine dovrai metterti d'accordo con il programma, giusto?
Per dare un buon esempio. Immagina di avere una classe chiamata Foo
public class Foo
{
public string Bar() { return "Bar"; }
}
Esempio 1 Ora vuoi avere una raccolta di oggetti Foo. Hai due opzioni, LIst o ArrayList, che funzionano entrambe in modo simile.
Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();
//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());
Nel codice sopra, se provo ad aggiungere una classe di FireTruck invece di Foo, ArrayList la aggiungerà, ma l'elenco generico di Foo causerà la generazione di un'eccezione.
Esempio due.
Ora hai i tuoi due elenchi di array e vuoi chiamare la funzione Bar () su ciascuno. Poiché hte ArrayList è pieno di oggetti, devi lanciarli prima di poter chiamare bar. Ma poiché l'elenco generico di Foo può contenere solo Foos, puoi chiamare Bar () direttamente su quelli.
foreach(object o in al)
{
Foo f = (Foo)o;
f.Bar();
}
foreach(Foo f in fl)
{
f.Bar();
}
Non hai mai scritto un metodo (o una classe) in cui il concetto chiave del metodo / classe non fosse strettamente legato a un tipo di dati specifico dei parametri / variabili di istanza (pensa a elenco collegato, funzioni max / min, ricerca binaria , eccetera.).
Non hai mai desiderato di poter riutilizzare l'algorthm / code senza ricorrere al riutilizzo cut-and-paste o compromettere la digitazione forte (ad esempio, voglio una List
delle stringhe, non una List
delle cose che spero siano stringhe!)?
Ecco perché dovresti voler usare generici (o qualcosa di meglio).
Non dimenticare che i generici non sono usati solo dalle classi, ma possono anche essere usati dai metodi. Ad esempio, prendi il seguente frammento:
private <T extends Throwable> T logAndReturn(T t) {
logThrowable(t); // some logging method that takes a Throwable
return t;
}
È semplice, ma può essere utilizzato in modo molto elegante. La cosa bella è che il metodo restituisce ciò che è stato fornito. Questo aiuta quando gestisci eccezioni che devono essere rispedite al chiamante:
...
} catch (MyException e) {
throw logAndReturn(e);
}
Il punto è che non si perde il tipo passandolo attraverso un metodo. Puoi lanciare il tipo corretto di eccezione invece di solo una Throwable
, che sarebbe tutto ciò che potresti fare senza i generici.
Questo è solo un semplice esempio di utilizzo di metodi generici. Ci sono molte altre cose interessanti che puoi fare con metodi generici. Il più interessante, secondo me, è il tipo che inferisce con i generici. Prendiamo il seguente esempio (tratto da Effective Java 2nd Edition di Josh Bloch):
...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
return new HashMap<K, V>();
}
Questo non fa molto, ma riduce un po 'di confusione quando i tipi generici sono lunghi (o annidati; cioè Map<String, List<String>>
).
Throwable
dall'interno di un corpo del metodo che ha eccezioni dichiarate specifiche. L'alternativa è scrivere metodi separati per restituire ogni tipo di eccezione o eseguire il cast da soli con un metodo non generico che restituisce un file Throwable
. Il primo è troppo prolisso e abbastanza inutile e il secondo non riceverà alcun aiuto dal compilatore. Usando i generici, il compilatore inserirà automaticamente il cast corretto per te. Quindi, la domanda è: vale la complessità?
Il vantaggio principale, come sottolinea Mitchel, è la digitazione forte senza la necessità di definire più classi.
In questo modo puoi fare cose come:
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
Senza i generici, dovresti lanciare blah [0] nel tipo corretto per accedere alle sue funzioni.
la jvm esegue comunque il cast ... crea implicitamente codice che tratta il tipo generico come "Object" e crea cast per l'istanza desiderata. I generici Java sono solo zucchero sintattico.
So che questa è una domanda C #, ma i generici sono usati anche in altri linguaggi e il loro uso / obiettivi sono abbastanza simili.
Le raccolte Java utilizzano generici a partire da Java 1.5. Quindi, un buon posto per usarli è quando crei il tuo oggetto simile a una raccolta.
Un esempio che vedo quasi ovunque è una classe Pair, che contiene due oggetti, ma deve occuparsi di quegli oggetti in modo generico.
class Pair<F, S> {
public final F first;
public final S second;
public Pair(F f, S s)
{
first = f;
second = s;
}
}
Ogni volta che si utilizza questa classe Pair è possibile specificare il tipo di oggetti con cui si desidera che si occupi e qualsiasi problema di cast di tipo verrà visualizzato in fase di compilazione, anziché in runtime.
I generici possono anche avere i loro limiti definiti con le parole chiave "super" e "extends". Ad esempio, se vuoi gestire un tipo generico ma vuoi assicurarti che estenda una classe chiamata Foo (che ha un metodo setTitle):
public class FooManager <F extends Foo>{
public void setTitle(F foo, String title) {
foo.setTitle(title);
}
}
Sebbene non sia molto interessante di per sé, è utile sapere che ogni volta che hai a che fare con un FooManager, sai che gestirà i tipi di MyClass e che MyClass estende Foo.
Dalla documentazione di Sun Java, in risposta a "perché dovrei usare generics?":
"Generics fornisce un modo per comunicare il tipo di una raccolta al compilatore, in modo che possa essere controllato. Una volta che il compilatore conosce il tipo di elemento della raccolta, il compilatore può verificare che tu abbia utilizzato la raccolta in modo coerente e può inserire i cast corretti sui valori estratti dalla raccolta ... Il codice che utilizza generics è più chiaro e sicuro .... il compilatore può verificare in fase di compilazione che i vincoli di tipo non siano violati in fase di esecuzione [enfasi mia]. Il programma viene compilato senza avvisi, possiamo affermare con certezza che non genererà un'eccezione ClassCastException in fase di esecuzione. L'effetto netto dell'utilizzo di generici, specialmente in programmi di grandi dimensioni, è una migliore leggibilità e robustezza . [enfasi mia] "
I generici ti consentono di creare oggetti fortemente tipizzati, ma non devi definire il tipo specifico. Penso che il miglior esempio utile sia List e classi simili.
Usando la lista generica puoi avere una lista List List qualunque cosa tu voglia e puoi sempre fare riferimento alla digitazione forte, non devi convertire o qualcosa come faresti con un array o un elenco standard.
I generici ti consentono di utilizzare una digitazione forte per oggetti e strutture dati che dovrebbero essere in grado di contenere qualsiasi oggetto. Elimina anche i typecast noiosi e costosi durante il recupero di oggetti da strutture generiche (boxing / unboxing).
Un esempio che utilizza entrambi è un elenco collegato. A cosa servirebbe una classe di liste collegate se potesse usare solo l'oggetto Foo? Per implementare un elenco collegato in grado di gestire qualsiasi tipo di oggetto, l'elenco collegato ei nodi in un'ipotetica classe interna di nodi devono essere generici se si desidera che l'elenco contenga un solo tipo di oggetto.
Se la tua raccolta contiene tipi di valore, non è necessario inscatolare / decomprimere gli oggetti quando vengono inseriti nella raccolta, quindi le tue prestazioni aumentano notevolmente. Componenti aggiuntivi interessanti come resharper possono generare più codice per te, come i cicli foreach.
Un altro vantaggio dell'utilizzo di Generics (specialmente con collezioni / elenchi) è che ottieni il controllo del tipo in fase di compilazione. Ciò è molto utile quando si utilizza un elenco generico invece di un elenco di oggetti.
La ragione principale è che forniscono sicurezza di tipo
List<Customer> custCollection = new List<Customer>;
al contrario di,
object[] custCollection = new object[] { cust1, cust2 };
come un semplice esempio.
In sintesi, i generici ti consentono di specificare in modo più preciso cosa intendi fare (digitazione più forte).
Questo ha diversi vantaggi per te:
Poiché il compilatore sa di più su ciò che vuoi fare, ti consente di omettere un sacco di casting del tipo perché sa già che il tipo sarà compatibile.
Questo ti dà anche un feedback anticipato sulla correttezza del tuo programma. Cose che in precedenza avrebbero fallito in fase di runtime (ad esempio perché un oggetto non poteva essere lanciato nel tipo desiderato), ora falliscono in fase di compilazione e puoi correggere l'errore prima che il tuo dipartimento di test invii un bug report criptico.
Il compilatore può eseguire più ottimizzazioni, come evitare la boxe, ecc.
Un paio di cose da aggiungere / espandere (parlando dal punto di vista .NET):
I tipi generici consentono di creare classi e interfacce basate sui ruoli. Questo è già stato detto in termini più basilari, ma trovo che inizi a progettare il tuo codice con classi implementate in modo indipendente dal tipo, il che si traduce in codice altamente riutilizzabile.
Argomenti generici sui metodi possono fare la stessa cosa, ma aiutano anche ad applicare il principio "Tell Don't Ask" al casting, ovvero "dammi quello che voglio, e se non puoi, dimmi perché".
Li uso ad esempio in un GenericDao implementato con SpringORM e Hibernate che assomigliano a questo
public abstract class GenericDaoHibernateImpl<T>
extends HibernateDaoSupport {
private Class<T> type;
public GenericDaoHibernateImpl(Class<T> clazz) {
type = clazz;
}
public void update(T object) {
getHibernateTemplate().update(object);
}
@SuppressWarnings("unchecked")
public Integer count() {
return ((Integer) getHibernateTemplate().execute(
new HibernateCallback() {
public Object doInHibernate(Session session) {
// Code in Hibernate for getting the count
}
}));
}
.
.
.
}
Usando i generici le mie implementazioni di questi DAO costringono lo sviluppatore a passare loro solo le entità per cui sono progettati semplicemente sottoclasse il GenericDao
public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
public UserDaoHibernateImpl() {
super(User.class); // This is for giving Hibernate a .class
// work with, as generics disappear at runtime
}
// Entity specific methods here
}
Il mio piccolo framework è più robusto (ha cose come filtraggio, caricamento lento, ricerca). Ho solo semplificato qui per darvi un esempio
Io, come Steve e te, all'inizio dicevamo "Troppo disordinato e complicato" ma ora ne vedo i vantaggi
Benefici evidenti come "protezione dai tipi" e "no casting" sono già menzionati, quindi forse posso parlare di altri "vantaggi" che spero aiutino.
Prima di tutto, i generici sono un concetto indipendente dalla lingua e, IMO, potrebbe avere più senso se si pensa allo stesso tempo al polimorfismo regolare (runtime).
Ad esempio, il polimorfismo come sappiamo dalla progettazione orientata agli oggetti ha una nozione di runtime in cui l'oggetto chiamante viene individuato in fase di esecuzione mentre procede l'esecuzione del programma e il metodo pertinente viene chiamato di conseguenza a seconda del tipo di runtime. Nei generici, l'idea è in qualche modo simile ma tutto accade in fase di compilazione. Cosa significa e come lo usi?
(Atteniamoci a metodi generici per mantenerlo compatto) Significa che puoi ancora avere lo stesso metodo su classi separate (come hai fatto in precedenza nelle classi polimorfiche) ma questa volta sono auto-generati dal compilatore a seconda dei tipi impostati in fase di compilazione. Parametrizzi i tuoi metodi sul tipo che dai in fase di compilazione. Quindi, invece di scrivere i metodi da zero per ogni singolo tipo che hai come fai nel polimorfismo di runtime (override del metodo), lasci che i compilatori facciano il lavoro durante la compilazione. Ciò ha un ovvio vantaggio poiché non è necessario dedurre tutti i possibili tipi che potrebbero essere utilizzati nel sistema, il che lo rende molto più scalabile senza una modifica del codice.
Le lezioni funzionano più o meno allo stesso modo. Si parametrizza il tipo e il codice viene generato dal compilatore.
Una volta ottenuta l'idea del "tempo di compilazione", è possibile utilizzare tipi "limitati" e limitare ciò che può essere passato come tipo parametrizzato tramite classi / metodi. Quindi, puoi controllare cosa passare attraverso il che è una cosa potente, specialmente se hai un framework consumato da altre persone.
public interface Foo<T extends MyObject> extends Hoo<T>{
...
}
Nessuno può impostare qc oltre a MyObject ora.
Inoltre, puoi "applicare" vincoli di tipo agli argomenti del tuo metodo, il che significa che puoi assicurarti che entrambi gli argomenti del tuo metodo dipendano dallo stesso tipo.
public <T extends MyObject> foo(T t1, T t2){
...
}
Spero che tutto questo abbia un senso.
Una volta ho tenuto un discorso su questo argomento. Puoi trovare le mie diapositive, il codice e la registrazione audio su http://www.adventuresinsoftware.com/generics/ .
Usare i generici per le collezioni è semplice e pulito. Anche se ci scommetti ovunque, il guadagno dalle collezioni è una vittoria per me.
List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
stuff.do();
}
vs
List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
Stuff stuff = (Stuff)i.next();
stuff.do();
}
o
List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
Stuff stuff = (Stuff)stuffList.get(i);
stuff.do();
}
Questo da solo vale il "costo" marginale dei generici, e non devi essere un guru generico per usarlo e ottenere valore.
I generici ti danno anche la possibilità di creare oggetti / metodi più riutilizzabili pur fornendo supporto specifico per tipo. In alcuni casi si ottengono anche molte prestazioni. Non conosco le specifiche complete su Java Generics, ma in .NET posso specificare vincoli sul parametro Type, come Implements a Interface, Constructor e Derivation.
Consentire ai programmatori di implementare algoritmi generici - Utilizzando i generici, i programmatori possono implementare algoritmi generici che funzionano su raccolte di diversi tipi, possono essere personalizzati e sono indipendenti dai tipi e più facili da leggere.
Controlli di tipo più severi in fase di compilazione: un compilatore Java applica un controllo di tipo forte al codice generico e genera errori se il codice viola l'indipendenza dai tipi. Risolvere gli errori in fase di compilazione è più facile che correggere gli errori di runtime, che possono essere difficili da trovare.
Eliminazione dei cast.