Cosa c'è di sbagliato con i generici di Java? [chiuso]


49

Ho visto diverse volte su questo sito post che denigrano l'implementazione di generici da parte di Java. Ora posso onestamente dire che non ho avuto problemi con il loro utilizzo. Tuttavia, non ho tentato di creare una classe generica da solo. Quindi, quali sono i tuoi problemi con il supporto generico di Java?




come ha detto Clinton Begin (il "ragazzo iBatis"), "non funzionano ..."
moscerino

Risposte:


54

L'implementazione generica di Java utilizza la cancellazione del tipo . Ciò significa che le raccolte generiche fortemente tipizzate sono effettivamente di tipo Objectin fase di esecuzione. Questo ha alcune considerazioni sulle prestazioni in quanto significa che i tipi primitivi devono essere inscatolati quando aggiunti a una raccolta generica. Naturalmente i vantaggi della correttezza del tipo di tempo di compilazione superano la stupidità generale della cancellazione del tipo e della focalizzazione ossessiva sulla retrocompatibilità.


4
Solo per aggiungere, a causa della cancellazione del tipo, il recupero di informazioni generiche in fase di esecuzione non è possibile se non si utilizza qualcosa come TypeLiteral google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/…
Jeremy Heiler

43
Significa chenew ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
Nota a se stesso - pensa a un nome il

9
@Job no non lo è. E C ++ utilizza un approccio completamente diverso alla metaprogrammazione chiamata template. Usa la tipizzazione anatra e puoi fare cose utili come template<T> T add(T v1, T v2) { return v1->add(v2); }e lì hai un modo veramente generico di creare una funzione che addè due cose, indipendentemente da quali siano, devono solo avere un metodo chiamato add()con un parametro.
Trinidad,

7
@Job è il codice generato, anzi. I modelli hanno lo scopo di sostituire il preprocessore C e per farlo dovrebbero essere in grado di fare alcuni trucchi molto intelligenti e sono di per sé un linguaggio completo di Turing. I generici Java sono solo zucchero per contenitori sicuri per i tipi, e i generici C # sono migliori ma ancora un modello C ++ per poveri.
Trinidad,

3
@Job AFAIK, i generici Java non generano una classe per ogni nuovo tipo generico, ma aggiungono solo i tip typasts al codice che utilizza metodi generici, quindi in realtà non è meta-programmazione IMO. I modelli C # generano un nuovo codice per ogni tipo generico, ovvero se si utilizza List <int> e List <double> nel mondo di C # genererebbe codice per ciascuno di essi. Rispetto ai modelli, i generici di C # richiedono ancora di sapere a quale tipo puoi alimentarlo Non puoi implementare il semplice Addesempio che ho dato, non c'è modo di sapere senza anticipo a quali classi può essere applicato, il che è uno svantaggio.
Trinidad,

26

Osservare che le risposte già fornite si concentrano sulla combinazione di Java-the-language, JVM e Java Class Library.

Non c'è niente di sbagliato nei generici di Java per quanto riguarda Java-the-language. Come descritto in C # vs generici Java , i generici Java sono praticamente a livello di linguaggio 1 .

La cosa non ottimale è che la JVM non supporta direttamente i generici, il che ha diverse conseguenze importanti:

  • le parti relative alla riflessione della Libreria di classi non possono esporre le informazioni complete sul tipo disponibili in Java-the-language
  • è prevista una penalità per le prestazioni

Suppongo che la distinzione che sto facendo sia pedante poiché Java-the-language è ubiquamente compilato con JVM come destinazione e Java Class Library come libreria principale.

1 Con la possibile eccezione dei caratteri jolly, che si ritiene rendere l'inferenza del tipo indecidibile nel caso generale. Questa è una grande differenza tra generici C # e Java che non è menzionata molto spesso. Grazie, antimonio.


14
La JVM non ha bisogno di supportare generici. Ciò equivarrebbe a dire che C ++ non ha modelli perché la macchina reale ix86 non supporta i modelli, il che è falso. Il problema è l'approccio adottato dal compilatore per implementare tipi generici. E ancora una volta, i modelli C ++ non comportano alcuna penalità in fase di runtime, non c'è alcuna riflessione da fare, immagino che l'unica ragione che sarebbe necessario fare in Java sia solo un linguaggio di design scadente.
Trinidad,

2
@Trinidad ma non ho detto che Java non ha generici, quindi non vedo come sia lo stesso. E sì, la JVM non ha bisogno di supportarli, proprio come C ++ non ha bisogno di ottimizzare il codice. Ma non ottimizzarlo sarebbe certamente considerato "qualcosa di sbagliato".
Roman Starkov,

3
Quello che ho sostenuto è che non è necessario che la JVM abbia un supporto diretto per i generici per poter usare la riflessione su di essi. Altri l'hanno fatto in modo che possa essere fatto, il problema è che Java non genera nuove classi dai generici, la cosa senza reificazione.
Trinidad,

4
@Trinidad: in C ++, supponendo che il compilatore abbia tempo e memoria sufficienti, i template possono essere usati per fare qualsiasi cosa che possa essere fatta con le informazioni disponibili al momento della compilazione (poiché la compilazione dei template è Turing-complete), ma c'è nessun modo per creare modelli utilizzando informazioni che non sono disponibili prima dell'esecuzione del programma. In .net, al contrario, è possibile per un programma creare tipi basati sull'input; sarebbe abbastanza facile scrivere un programma in cui il numero di diversi tipi che potrebbero essere creati supera il numero di elettroni nell'universo.
supercat,

2
@Trinidad: Ovviamente, nessuna singola esecuzione del programma potrebbe creare tutti quei tipi (o, in effetti, qualsiasi cosa oltre una frazione infinitesimale di essi), ma il punto è che non è necessario. Deve solo creare tipi effettivamente utilizzati. Si potrebbe avere un programma in grado di accettare qualsiasi numero arbitrario (ad esempio 8675309) e da esso creare un tipo univoco per quel numero (ad esempio Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>), che avrebbe membri diversi da qualsiasi altro tipo. In C ++, tutti i tipi che potrebbero essere generati da qualsiasi input dovrebbero essere prodotti in fase di compilazione.
supercat,

13

La solita critica è la mancanza di reificazione. Vale a dire che gli oggetti in fase di runtime non contengono informazioni sui loro argomenti generici (sebbene le informazioni siano ancora presenti su campi, metodo, costruttori e classe estesa e interfacce). Questo significa che potresti lanciare, diciamo, ArrayList<String>a List<File>. Il compilatore ti avvertirà, ma ti avvertirà anche se lo ArrayList<String>assegni a un Objectriferimento e poi lo cast List<String>. I lati positivi sono che con i generici probabilmente non dovresti fare il casting, le prestazioni sono migliori senza i dati non necessari e, ovviamente, la compatibilità con le versioni precedenti.

Alcune persone si lamentano del fatto che non è possibile sovraccaricare sulla base di argomenti generici ( void fn(Set<String>)e void fn(Set<File>)). È necessario invece utilizzare nomi di metodi migliori. Si noti che questo sovraccarico non richiederebbe la reificazione, poiché il sovraccarico è un problema statico durante la compilazione.

I tipi primitivi non funzionano con i generici.

I caratteri jolly e i limiti sono piuttosto complicati. Sono molto utili Se Java preferisse l'immutabilità e le interfacce Tell-Don't-Ask, allora i generici sul lato della dichiarazione piuttosto che sul lato dell'uso sarebbero più appropriati.


"Si noti che questo sovraccarico non richiederebbe la reificazione, in quanto il sovraccarico è un problema statico in fase di compilazione." No? Come funzionerebbe senza reificazione? La JVM non dovrebbe sapere in fase di esecuzione quale tipo di set hai, al fine di sapere quale metodo chiamare?
MatrixFrog,

2
@MatrixFrog No. Come ho già detto, il sovraccarico è un problema in fase di compilazione. Il compilatore utilizza il tipo statico dell'espressione per selezionare un sovraccarico particolare, che è quindi il metodo scritto nel file di classe. Se hai una Object cs = new char[] { 'H', 'i' }; System.out.println(cs);stampa senza senso. Cambia il tipo di csa char[]e otterrai Hi.
Tom Hawtin - tackline il

10

I generici Java fanno schifo perché non puoi fare quanto segue:

public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
    proptected Adapter doInBackground(String... keywords) {
      Parser p = new Parser(keywords[0]); // this is an error
      /* some more stuff I was hoping for but couldn't do because
         the compiler wouldn't let me
      */
    }
}

Non è necessario macellare ogni classe nel codice sopra per farlo funzionare come dovrebbe se i generici fossero in realtà parametri di classe generici.


Non è necessario macellare ogni classe. Devi solo aggiungere una classe di fabbrica adatta e iniettarla in AsyncAdapter. (E fondamentalmente non si tratta di generici, ma del fatto che i costruttori non sono ereditati in Java).
Peter Taylor,

13
@PeterTaylor: una classe di fabbrica adatta inserisce tutta la piastra della caldaia in qualche altro pasticcio nascosto e non risolve ancora il problema. Ora ogni volta che voglio Parsercreare un'istanza di una nuova istanza, dovrò rendere il codice di fabbrica ancora più contorto. Considerando che se "generico" significasse veramente generico, allora non ci sarebbe bisogno di fabbriche e di tutte le altre sciocchezze che prendono il nome di modelli di progettazione. È uno dei tanti motivi per cui preferisco lavorare in linguaggi dinamici.
davidk01,

2
A volte simile in C ++, dove non è possibile passare l'indirizzo di un costruttore, quando si usano template + virtuals, si può semplicemente usare un funzione factory invece.
Mark K Cowan,

Java fa schifo perché non puoi scrivere "presunto" prima di un nome di un metodo? Povero te. Attenersi a linguaggi dinamici. E diffidare soprattutto di Haskell.
fdreger,

5
  • gli avvisi del compilatore per la mancanza di parametri generici che non servono a nulla rendono la lingua inutilmente dettagliata, ad es public String getName(Class<?> klazz){ return klazz.getName();}

  • I generici non giocano bene con gli array

  • Le informazioni perse sul tipo rendono la riflessione un casino di casting e nastro adesivo.


Mi annoio quando ricevo un avviso per l'utilizzo HashMapanziché HashMap<String, String>.
Michael K,

1
@Michael, ma in realtà dovrebbe essere usato con i generici ...
alternativa il

Il problema dell'array va alla reifbility. Vedere il libro Java Generics (Alligator) per una spiegazione.
ncmathsadist,

5

Penso che altre risposte abbiano detto questo in una certa misura, ma non molto chiaramente. Uno dei problemi con i generici sta perdendo i tipi generici durante il processo di riflessione. Quindi per esempio:

List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();

Sfortunatamente, i tipi restituiti non possono dirti che i tipi generici di arr sono String. È una differenza sottile, ma è importante. Poiché arr viene creato in fase di esecuzione, i tipi generici vengono cancellati in fase di esecuzione, quindi non è possibile capirlo. Come alcuni hanno affermato ArrayList<Integer>sembra lo stesso ArrayList<String>dal punto di vista della riflessione.

Questo potrebbe non importare all'utente di Generics, ma diciamo che volevamo creare un framework di fantasia che usasse la riflessione per capire cose fantasiose su come l'utente ha dichiarato i tipi generici concreti di un'istanza.

Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();

Diciamo che volevamo che una fabbrica generica creasse un'istanza MySpecialObjectperché quello è il tipo generico concreto che abbiamo dichiarato per questa istanza. Bene, la classe Factory non può interrogarsi per scoprire il tipo concreto dichiarato per questa istanza perché Java li ha cancellati.

Nei generici di .Net puoi farlo perché in fase di esecuzione l'oggetto sa che sono tipi generici perché il compilatore lo ha rispettato nel file binario. Con le cancellazioni Java non può farlo.


1

Potrei dire alcune cose positive sui generici, ma non era questa la domanda. Potrei lamentarmi che non sono disponibili in fase di esecuzione e non funzionano con le matrici, ma questo è stato menzionato.

Un grande fastidio psicologico: ogni tanto mi trovo in situazioni in cui i generici non possono essere fatti funzionare. (Le matrici sono l'esempio più semplice.) E non riesco a capire se i generici non possono fare il lavoro o se sono solo stupido. Odio che. Qualcosa come i generici dovrebbe funzionare sempre. Ogni altra volta che non riesco a fare quello che voglio usando Java-the-language, so che il problema sono io e so che se continuo a spingere, alla fine ci arriverò. Con i generici, se divento troppo persistente, posso perdere molto tempo.

Ma il vero problema è che i generici aggiungono troppa complessità per un guadagno troppo piccolo. Nei casi più semplici può impedirmi di aggiungere una mela a un elenco contenente automobili. Belle. Ma senza generici questo errore genererebbe una ClassCastException molto veloce in fase di esecuzione con poco tempo perso. Se aggiungo un'auto con un seggiolino per bambini con dentro un bambino, ho bisogno di un avviso in fase di compilazione che l'elenco è solo per le auto con seggiolini per bambini che contengono scimpanzé? Un elenco di semplici istanze di oggetti inizia a sembrare una buona idea.

Il codice generico può contenere molte parole e caratteri e occupare molto spazio extra e impiegare molto più tempo a leggere. Posso passare molto tempo a far funzionare tutto quel codice extra. Quando lo fa, mi sento follemente intelligente. Ho anche perso diverse ore o più del mio tempo e devo chiedermi se qualcun altro sarà mai in grado di capire il codice. Vorrei poterlo consegnare per la manutenzione a persone che sono meno intelligenti di me o che hanno meno tempo da perdere.

D'altra parte (sono rimasto un po 'ferito lì e sento il bisogno di fornire un po' di equilibrio), è bello quando si usano raccolte e mappe semplici per aggiungere, mettere e controllare quando sono scritte, e di solito non aggiunge molto complessità al codice ( se qualcun altro scrive la raccolta o la mappa) . E Java è migliore in questo rispetto a C #. Sembra che nessuna delle raccolte C # che voglio usare gestisca mai generici. (Ammetto di avere strani gusti nelle collezioni.)


Bel rant. Tuttavia, ci sono molte librerie / framework che potrebbero esistere senza generici, ad esempio Guice, Gson, Hibernate. E i generici non sono così difficili una volta che ti ci abitui. Digitare e leggere gli argomenti del tipo è una vera PITA; qui val aiuta se puoi usarlo.
maaartinus,

1
@maaartinus: l'ho scritto qualche tempo fa. Anche OP ha chiesto "problemi". Trovo i farmaci generici estremamente utili e mi piacciono molto, molto più adesso di allora. Tuttavia, se stai scrivendo la tua collezione, sono molto difficili da imparare. E la loro utilità svanisce quando il tipo di raccolta viene determinato in fase di esecuzione - quando la raccolta ha un metodo che ti dice la classe delle sue voci. A questo punto i generici non funzionano e il tuo IDE produrrà centinaia di avvertimenti insignificanti. Il 99,99% della programmazione Java non comporta questo. Ma avevo appena avuto molti problemi con questo quando ho scritto quanto sopra.
RalphChapin,

Essendo sviluppatore sia Java che C #, mi chiedo perché preferiresti le raccolte Java?
Ivaylo Slavov,

Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.Ciò dipende in realtà da quanto tempo di esecuzione si verifica tra l'avvio del programma e colpire la riga di codice in questione. Se un utente segnala l'errore e la riproduzione richiede diversi minuti (o, nel peggiore dei casi, ore o addirittura giorni), la verifica in fase di compilazione inizia a sembrare sempre migliore ...
Mason Wheeler,
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.