Cosa c'è di bello nei generici, perché usarli?


87

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.


1
Duplicato di stackoverflow.com/questions/77632/… . Ma risposta semplice, anche se le raccolte non fanno parte della tua API, non mi piace il casting non necessario anche nell'implementazione interna.
Matthew Flaschen

La domanda è abbastanza simile, ma non credo che la risposta accettata risponda a questa.
Tom Hawtin - tackline


4
@MatthewFlaschen strano, il tuo duplicato indirizza a questa domanda ... glitch nella matrice!
jcollum

@jcollum, penso che la domanda originale su cui ho postato quel commento sia stata unita a questa domanda.
Matthew Flaschen

Risposte:


126
  • Consente di scrivere codice / utilizzare metodi di libreria indipendenti dai tipi, ad esempio una <stringa> List è garantita come un elenco di stringhe.
  • Come risultato dell'utilizzo di generics, il compilatore può eseguire controlli in fase di compilazione sul codice per l'indipendenza dai tipi, ad esempio stai cercando di inserire un int in quell'elenco di stringhe? L'utilizzo di un ArrayList lo renderebbe un errore di runtime meno trasparente.
  • Più veloce dell'utilizzo di oggetti in quanto evita il boxing / unboxing (dove .net deve convertire i tipi di valore in tipi di riferimento o viceversa ) o il cast da oggetti al tipo di riferimento richiesto.
  • Consente di scrivere codice applicabile a molti tipi con lo stesso comportamento sottostante, ovvero un Dictionary <string, int> utilizza lo stesso codice sottostante di Dictionary <DateTime, double>; usando i generici, il team del framework doveva scrivere solo un pezzo di codice per ottenere entrambi i risultati con i vantaggi di cui sopra.

9
Ah, molto carino, e mi piacciono gli elenchi puntati. Penso che questa sia la risposta più completa ma succinta finora.
MrBoJangles,

l'intera spiegazione è nel contesto "collezioni". sto cercando un aspetto più ampio. qualche idea?
jungle_mole

buona risposta voglio solo aggiungere altri 2 vantaggi, la restrizione generica ha 2 vantaggi accanto a 1- Puoi usare le proprietà del tipo vincolato in tipo generico (ad esempio dove T: IComparable prevede di usare CompareTo) 2- La persona che scriverà il codice dopo saprai cosa farà
Hamit YILDIRIM

52

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.


1
Non sono sicuro di capire. Non sto suggerendo di creare wrapper "MyObject", sarebbe orribile. Sto suggerendo che se la tua collezione di oggetti è una collezione di Cars, allora scrivi un oggetto Garage. Non avrebbe avuto (auto), avrebbe metodi come buyFuel (100) che comprerebbe $ 100 di carburante e lo distribuirà tra le auto che ne hanno più bisogno. Sto parlando di vere e proprie classi di business, non solo di "wrapper". Ad esempio, non dovresti quasi mai ottenere (auto) o eseguire il loop su di essi al di fuori della raccolta: non chiedi a un oggetto per i suoi membri, invece gli chiedi di eseguire un'operazione per te.
Bill K

Anche all'interno del tuo garage, dovresti avere l'indipendenza dai tipi. Quindi o hai List <Cars>, ListOfCars o trasmetti ovunque. Non sento il bisogno di dichiarare la tipologia oltre i tempi minimi necessari.
James Schek,

Sono d'accordo sul fatto che la protezione dai tipi sarebbe piacevole, ma è molto meno utile poiché è limitata alla classe garage. Quindi è incapsulato e facilmente controllabile, quindi il punto è che ti dà questo piccolo vantaggio al costo di una nuova sintassi. È come modificare la costituzione degli Stati Uniti per rendere illegale il semaforo rosso - Sì, dovrebbe sempre essere illegale ma giustifica un emendamento? Penso anche che incoraggi le persone a NON usare l'incapsulamento per questo caso, che in realtà è relativamente dannoso.
Bill K,

Bill: potresti avere ragione sul non usare l'incapsulamento, ma il resto della tua argomentazione è un confronto scarso. Il costo per l'apprendimento della nuova sintassi in questo caso è minimo (confrontalo con lambda in C #); paragonare questo all'emendamento della Costituzione non ha senso. La Costituzione non ha intenzione di specificare il codice del traffico. È più come passare a una versione stampata con caratteri Serif su cartoncino invece che a una copia scritta a mano su pergamena. È lo stesso significato, ma è molto più chiaro e facile da leggere.
James Schek

L'esempio rende il concetto più pratico. Grazie per quello.
RayLoveless

22

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 ClassCastExceptionelettronica 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 HashMapda 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 Sete 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 10viene inserito automaticamente in un Integercon il valore di 10. (E lo stesso per 42). Quindi quello Integerviene gettato in quello Setche è noto per contenere Integers. Il tentativo di lanciare un Stringcauserebbe un errore di compilazione.

Successivamente, for for-each loop prende tutti e tre quelli:

for (int i : set) {
  total += i;
}

In primo luogo, i Setcontenitori Integervengono usati in un ciclo for-each. Ogni elemento è dichiarato come an inte ciò è consentito in quanto Integerviene ripristinato alla primitiva int. E il fatto che si verifichi questo unboxing è noto perché i generici sono stati utilizzati per specificare che erano Integertenuti 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 Setdall'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 Setsenza 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 addinserito nel set.

set.add(10);
set.add(42);

Nelle righe precedenti, l'autoboxing è in gioco: il intvalore primitivo 10e 42viene autoboxed in Integeroggetti, che vengono aggiunti al file Set. Tuttavia, tieni presente che gli Integeroggetti vengono gestiti come Objects, poiché non ci sono informazioni sul tipo per aiutare il compilatore a sapere quale tipo Setdovrebbe aspettarsi.

for (Object o : set) {

Questa è la parte cruciale. Il motivo per cui il ciclo for-each funziona è perché Setimplementa l' Iterableinterfaccia, che restituisce una Iteratorcon informazioni sul tipo, se presente. ( Iterator<T>, cioè.)

Tuttavia, poiché non ci sono informazioni sul tipo, Setrestituirà an Iteratorche restituirà i valori in Setas Objects, 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 Integereseguirne il cast manualmente per eseguire l'aggiunta:

  total += (Integer)o;

Qui, un typecast viene eseguito da un Objecta 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 ClassCastExceptionattesa che accada, ma sto divagando ...)

Il Integerè ora unboxed in un inte consentito di eseguire l'aggiunta nella intvariabile 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.


L'integrazione con il ciclo for migliorato è davvero bella (anche se penso che il dannato ciclo sia rotto perché non posso usare la stessa identica sintassi con una raccolta non generica - dovrebbe usare solo cast / autobox su int, ha tutte le informazioni di cui ha bisogno!), ma hai ragione, l'aiuto nell'IDE è probabilmente buono. Non so ancora se è sufficiente per giustificare la sintassi aggiuntiva, ma almeno è qualcosa di cui posso beneficiare (che risponde alla mia domanda).
Bill K

@ Bill K: ho aggiunto un esempio di utilizzo delle funzionalità di Java 5 senza l'utilizzo di generici.
coobird,

Questo è un buon punto, non l'avevo usato (ho detto che sono rimasto bloccato, su 1.4 e presto da anni ormai). Sono d'accordo sul fatto che ci siano dei vantaggi, ma le conclusioni a cui sto arrivando sono che A) tendono ad essere abbastanza vantaggiosi per le persone che non usano correttamente OO e sono meno utili per coloro che lo fanno, e B) I vantaggi per te ' abbiamo elencato i vantaggi, ma difficilmente valgono abbastanza da giustificare anche un piccolo cambiamento di sintassi, per non parlare dei grandi cambiamenti richiesti per fare davvero i generici come implementati in Java. Ma almeno ci sono dei vantaggi.
Bill K,

15

Se dovessi cercare nel database dei bug di Java appena prima del rilascio della 1.5, troverai sette volte più bug con NullPointerExceptionche 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.


6
Non solo è documentato (che di per sé è una grande vittoria), ma è anche applicato dal compilatore.
James Schek

Buon punto, ma non si ottiene anche racchiudendo la raccolta in una classe che definisce "una raccolta di questo tipo di oggetto"?
Bill K

1
James: Sì, questo è il punto in cui viene documentato in codice .
Tom Hawtin - tackline

Non credo di conoscere buone librerie che usano le raccolte per "Algoritmi" (forse sottintendendo che stai parlando di "Funzioni", il che mi porta a credere che dovrebbero essere un metodo nell'oggetto della raccolta). Penso che il JDK estrae quasi sempre un array bello e pulito che è una copia dei dati in modo che non possa essere manipolato, almeno il più delle volte.
Bill K

Inoltre, avere un oggetto che descrive esattamente come viene utilizzata la raccolta e che ne limita l'uso non è una forma MOLTO migliore di documentazione in-code? Trovo le informazioni fornite in generici estremamente ridondanti in un buon codice.
Bill K

11

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 Listcui elementi sono Ts . 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, Ae B, c'è una funzione (chiamata f) che accetta Ae restituisce a B. Quando si implementa questa interfaccia, Ae si Bpuò essere di qualsiasi tipo si desideri, purché si fornisca una funzione fche 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 extendsparola 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 Widgetprorogato di FooWidget, BarWidgete BazWidget, con i generici si può avere una sola classe generica Widget<A>che prende un Foo, Baro Baznel suo costruttore di darvi Widget<Foo>, Widget<Bar>e Widget<Baz>.


Sarebbe un buon punto sulla sottoclasse se Java non avesse interfacce. Non sarebbe corretto che FooWidget, BarWidtet e BazWidget implementassero Widget? Potrei sbagliarmi su questo però .. Ma questo è davvero un buon punto se mi sbaglio.
Bill K

Perché hai bisogno di una classe generica dinamica per calcolare i valori hash? Una funzione statica con parametri appropriati al lavoro non sarebbe più efficiente?
Ant_222

8

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.


Questa è l'essenza, non è vero? Bello.
MrBoJangles,

Beh, non del tutto; sono utili per la sicurezza dei tipi + evitando anche i cast per i tipi di riferimento senza che il boxing / unboxing entri affatto in esso.
ljs

Quindi immagino che la domanda che segue sia: "Perché mai dovrei voler usare un ArrayList?" E, azzardando una risposta, direi che gli ArrayList vanno bene fintanto che non ti aspetti di inscatolare e decomprimere molto i loro valori. Commenti?
MrBoJangles,

No, sono defunti a partire da .net 2; utile solo per compatibilità con le versioni precedenti. Le liste di array sono più lente, non sono sicure e richiedono boxing / unboxing o casting.
ljs

Se non stai memorizzando tipi di valori (ad esempio int o struct), non c'è boxing quando usi ArrayList - le classi sono già 'oggetto'. Usare List <T> è ancora molto meglio a causa del tipo di sicurezza, e se vuoi davvero memorizzare oggetti, allora è meglio usare List <object>.
Wilka,

6

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.


5

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).


5
  • 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?


1
per essere precisi riguardo all'evitare il casting: il casting avviene, secondo la documentazione di Sun, ma è fatto dal compilatore. Devi solo evitare di scrivere cast nel tuo codice.
Demi

Forse mi sembra di essere bloccato in una lunga serie di aziende che non vogliono andare oltre 1.4, quindi chissà, potrei ritirarmi prima di arrivare a 5. Ultimamente ho anche pensato che il fork di "SimpleJava" potrebbe essere un concetto interessante - Java continuerà ad aggiungere funzionalità e per gran parte del codice che ho visto, non sarà una cosa salutare. Ho già a che fare con persone che non sono in grado di codificare un loop: mi dispiacerebbe vederle provare a iniettare farmaci generici nei loro loop inutilmente svolti.
Bill K

@ Bill K: Poche persone hanno bisogno di usare tutta la potenza dei generici. È necessario solo quando scrivi una classe che è essa stessa generica.
Eddie

5

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();
}

Immagino che tu abbia risposto prima di leggere la domanda. Questi sono i due casi che non mi aiutano molto (descritti nella domanda). Ci sono altri casi in cui fa qualcosa di utile?
Bill K

4

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 Listdelle stringhe, non una Listdelle cose che spero siano stringhe!)?

Ecco perché dovresti voler usare generici (o qualcosa di meglio).


Non ho mai dovuto copiare / incollare il riutilizzo per questo tipo di cose (non sei sicuro di come sarebbe potuto accadere?). Implementi un'interfaccia che si adatta alle tue esigenze e usi quell'interfaccia. Il raro caso in cui non puoi farlo è quando stai scrivendo un'utilità pura (come le collezioni), e per quelle (come ho descritto nella domanda) non è mai stato un problema. Scendo a compromessi sulla digitazione forte, proprio come devi fare se usi la riflessione. Ti rifiuti assolutamente di usare la riflessione perché non puoi avere una digitazione forte? (Se devo usare la riflessione, la incapsulerò come una raccolta).
Bill K,

3

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>>).


Non penso che sia vero per le eccezioni. Mi fa pensare, ma sono sicuro al 95% che otterresti esattamente gli stessi risultati senza usare i generici (e un codice più chiaro). Le informazioni sul tipo fanno parte dell'oggetto, non a cosa le lanci. Sono tentato di fare un tentativo però - forse lo farò domani al lavoro e ti ricontatterò .. Ti dico cosa, lo proverò e se hai ragione, lo farò la tua lista di post e vota 5 dei tuoi post :) In caso contrario, potresti considerare che la semplice disponibilità di generici ti ha fatto complicare il tuo codice senza alcun vantaggio.
Bill K

È vero che questo aggiunge ulteriore complessità. Tuttavia, penso che abbia qualche utilità. Il problema è che non puoi lanciare un semplice vecchio Throwabledall'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à?
jigawot

Ah, ho capito. Quindi evita che il cast venga fuori ... buon punto. Ti devo 5.
Bill K,

2

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.


Data la scelta, preferirei non lanciare piuttosto che cast.
MrBoJangles,

La digitazione forte è senza dubbio l'aspetto migliore di generics imho, soprattutto dato il controllo del tipo in fase di compilazione che consente.
ljs

2

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.


2

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.


2

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] "


Non ho mai trovato un caso in cui la digitazione generica delle raccolte avrebbe aiutato il mio codice. Le mie collezioni sono strettamente limitate. Ecco perché ho formulato questo "Può aiutarmi?". Come domanda a margine, però, stai dicendo che prima di iniziare a usare i generici questo ti è successo occasionalmente? (inserendo il tipo di oggetto sbagliato nella tua raccolta). Mi sembra una soluzione senza problemi reali, ma forse sono solo fortunato.
Bill K

@ Bill K: se il codice è strettamente controllato (cioè, usato solo da te) forse non lo troverai mai come un problema. È fantastico! I rischi maggiori sono nei grandi progetti su cui lavorano più persone, IMO. È una rete di sicurezza e una "migliore pratica".
Demi

@ Bill K: ecco un esempio. Supponi di avere un metodo che restituisce un ArrayList. Diciamo che ArrayList non è una raccolta generica. Il codice di qualcuno prende questo ArrayList e tenta di eseguire alcune operazioni sui suoi elementi. L'unico problema è che hai riempito ArrayList con BillTypes e il consumatore sta cercando di operare su ConsumerTypes. Questo si compila, ma esplode durante il runtime. Se usi raccolte generiche, avrai invece errori del compilatore, che sono molto più piacevoli da gestire.
Demi

@ Bill K: ho lavorato con persone con livelli di abilità molto diversi. Non ho il lusso di scegliere con chi condividere un progetto. Pertanto, l'indipendenza dai tipi è molto importante per me. Sì, ho trovato (e corretto) bug convertendo il codice per utilizzare Generics.
Eddie,

1

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.


1

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.


1

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.


1

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.


1

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.


Sicurezza dei tipi, una discussione in sé. Forse qualcuno dovrebbe fare una domanda al riguardo.
MrBoJangles

Sì, ma a cosa stai suggerendo? L'intero punto della sicurezza dei tipi?
Vin

1

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.


1

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é".


1

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


1

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.



0

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.


1
Senza generici puoi fare: List stuffList = getStuff (); for (Object stuff: stuffList) {((Stuff) stuff) .doStuff (); }, che non è molto diverso.
Tom Hawtin - tackline

Senza generici faccio stuffList.doAll (); Ho detto che ho sempre una classe wrapper che è esattamente il posto per codice del genere. Altrimenti come eviti di copiare questo loop altrove? Dove lo metti per il riutilizzo? La classe wrapper avrà probabilmente un ciclo di qualche tipo, ma in quella classe, dal momento che quella classe è tutta incentrata sulle "cose", l'uso di un ciclo cast for come suggerito da Tom è abbastanza chiaro / auto documentante.
Bill K

@ Jherico, è davvero interessante. Mi chiedo se ci sia una ragione per la quale ti senti in quel modo, e se questo è ciò che non capisco - che c'è qualcosa nella natura delle persone che trova qualsiasi casting per qualsiasi motivo in qualche modo osceno ed è disposto a fare cose innaturali (come aggiungendo una sintassi molto più complessa) per evitarlo. Perché "Se devi lanciare qualcosa, hai già perso"?
Bill K,

@ Jherico: secondo la documentazione Sun, i generici forniscono al compilatore la conoscenza di cosa lanciare gli oggetti. Poiché il compilatore esegue il casting per i generici, piuttosto che l'utente, questo cambia la tua dichiarazione?
Demi

Ho convertito il codice dalla versione precedente a Java 1.5 a Generics e nel processo ho trovato bug in cui il tipo di oggetto sbagliato è stato inserito in una raccolta. Cado anche nel campo "se devi lanciare hai perso", perché se devi lanciare manualmente, corri il rischio di una ClassCastException. Con Generics il compilatore lo fa in modo invisibile per te, ma la possibilità di un'eccezione ClassCastException è molto ridotta a causa dell'indipendenza dai tipi. Sì, senza Generics puoi usare Object, ma per me è un terribile odore di codice, lo stesso di "lancia Eccezione".
Eddie,

0

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.


0
  1. 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.

  2. 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.

  3. Eliminazione dei cast.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.