Di tanto in tanto ho sentito che con i generici Java non funzionava correttamente. (riferimento più vicino, qui )
Perdonate la mia inesperienza, ma cosa li avrebbe resi migliori?
Di tanto in tanto ho sentito che con i generici Java non funzionava correttamente. (riferimento più vicino, qui )
Perdonate la mia inesperienza, ma cosa li avrebbe resi migliori?
Risposte:
Male:
List<byte>
è supportato da un byte[]
e non è richiesta la boxe)Buona:
Il problema più grande è che i generici Java sono solo una cosa in fase di compilazione e puoi sovvertirlo in fase di esecuzione. C # è elogiato perché esegue più controlli in fase di esecuzione. Ci sono alcune discussioni davvero interessanti in questo post e si collega ad altre discussioni.
Class
oggetti in giro.
Il problema principale è che Java in realtà non ha generici in fase di esecuzione. È una funzionalità in fase di compilazione.
Quando crei una classe generica in Java, usano un metodo chiamato "Type Erasure" per rimuovere effettivamente tutti i tipi generici dalla classe e sostanzialmente sostituirli con Object. La versione più alta dei generics è che il compilatore inserisce semplicemente i cast nel tipo generico specificato ogni volta che appare nel corpo del metodo.
Questo ha molti aspetti negativi. Uno dei più grandi, IMHO, è che non puoi usare la riflessione per ispezionare un tipo generico. I tipi non sono effettivamente generici nel codice byte e quindi non possono essere ispezionati come generici.
Ottima panoramica delle differenze qui: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) porta a comportamenti molto strani. Il miglior esempio a cui riesco a pensare è. Assumere:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
quindi dichiara due variabili:
MyClass<T> m1 = ...
MyClass m2 = ...
Adesso chiama getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
Il secondo ha il suo argomento di tipo generico rimosso dal compilatore perché è un tipo grezzo (il che significa che il tipo parametrizzato non viene fornito) anche se non ha nulla a che fare con il tipo parametrizzato.
Citerò anche la mia dichiarazione preferita del JDK:
public class Enum<T extends Enum<T>>
A parte i caratteri jolly (che è un miscuglio) penso solo che i generici .Net siano migliori.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
può sembrare strano / ridondante all'inizio, ma in realtà è piuttosto interessante, almeno entro i limiti di Java / i suoi generici. Gli enum hanno un values()
metodo statico che fornisce un array dei loro elementi digitati come enum, not Enum
, e quel tipo è determinato dal parametro generico, il che significa che vuoi Enum<T>
. Ovviamente, la digitazione ha senso solo nel contesto di un tipo enumerato e tutte le enumerazioni sono sottoclassi di Enum
, quindi lo desideri Enum<T extends Enum>
. Tuttavia, a Java non piace mescolare tipi grezzi con generici, quindi Enum<T extends Enum<T>>
per coerenza.
Butto fuori un'opinione davvero controversa. I generici complicano la lingua e complicano il codice. Ad esempio, diciamo che ho una mappa che mappa una stringa in un elenco di stringhe. Ai vecchi tempi, potevo dichiararlo semplicemente come
Map someMap;
Ora, devo dichiararlo come
Map<String, List<String>> someMap;
E ogni volta che lo passo in qualche metodo, devo ripetere di nuovo quella grande e lunga dichiarazione. A mio parere, tutta quella digitazione in più distrae lo sviluppatore e lo porta fuori "dalla zona". Inoltre, quando il codice è pieno di molti cruft, a volte è difficile tornarci più tardi e setacciare rapidamente tutto il cruft per trovare la logica importante.
Java ha già una cattiva reputazione per essere uno dei linguaggi più prolissi di uso comune, e i generici si aggiungono semplicemente a questo problema.
E cosa compri veramente per tutta quella verbosità extra? Quante volte hai davvero avuto problemi in cui qualcuno ha inserito un numero intero in una raccolta che dovrebbe contenere stringhe o in cui qualcuno ha cercato di estrarre una stringa da una raccolta di numeri interi? Nei miei 10 anni di esperienza lavorativa nella creazione di applicazioni Java commerciali, questa non è mai stata una grande fonte di errori. Quindi, non sono davvero sicuro di cosa stai ottenendo per la verbosità extra. Mi sembra davvero un bagaglio burocratico extra.
Ora diventerò davvero controverso. Quello che vedo come il problema più grande con le raccolte in Java 1.4 è la necessità di typecast ovunque. Considero quei typecast come cruft extra e dettagliati che hanno molti degli stessi problemi dei generici. Quindi, ad esempio, non posso semplicemente fare
List someList = someMap.get("some key");
devo fare
List someList = (List) someMap.get("some key");
La ragione, ovviamente, è che get () restituisce un Object che è un supertipo di List. Quindi l'assegnazione non può essere eseguita senza un typecast. Di nuovo, pensa a quanto ti fa veramente guadagnare quella regola. Dalla mia esperienza, non molto.
Penso che Java sarebbe stato molto meglio se 1) non avesse aggiunto i generici ma 2) invece avesse consentito il casting implicito da un supertipo a un sottotipo. Consentire il rilevamento di cast errati in fase di esecuzione. Allora avrei potuto avere la semplicità di definire
Map someMap;
e successivamente
List someList = someMap.get("some key");
tutto il cruft sarebbe sparito e non credo proprio che introdurrei una nuova grande fonte di bug nel mio codice.
Un altro effetto collaterale del fatto che sono in fase di compilazione e non in fase di esecuzione è che non è possibile chiamare il costruttore del tipo generico. Quindi non puoi usarli per implementare una fabbrica generica ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
I generici Java vengono controllati per verificarne la correttezza in fase di compilazione e quindi tutte le informazioni sul tipo vengono rimosse (il processo è chiamato cancellazione del tipo . Pertanto, il generico List<Integer>
sarà ridotto al suo tipo grezzo , non generico List
, che può contenere oggetti di classe arbitraria.
Ciò si traduce nella possibilità di inserire oggetti arbitrari nell'elenco in fase di esecuzione, così come ora è impossibile dire quali tipi sono stati usati come parametri generici. Quest'ultimo a sua volta si traduce in
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Vorrei che questo fosse un wiki in modo da poter aggiungere ad altre persone ... ma ...
I problemi:
<
? Estende MyObject >
[], ma non sono autorizzato)Ignorando l'intero tipo di disordine di cancellazione, i generici come specificato semplicemente non funzionano.
Questo compila:
List<Integer> x = Collections.emptyList();
Ma questo è un errore di sintassi:
foo(Collections.emptyList());
Dove foo è definito come:
void foo(List<Integer> x) { /* method body not important */ }
Quindi il controllo di un tipo di espressione dipende dal fatto che venga assegnato a una variabile locale o a un parametro effettivo di una chiamata al metodo. Quanto è pazzo?
L'introduzione dei generici in Java è stato un compito difficile perché gli architetti stavano cercando di bilanciare funzionalità, facilità d'uso e compatibilità con il codice legacy. Abbastanza prevedibilmente, è stato necessario scendere a compromessi.
Alcuni ritengono inoltre che l'implementazione di Java dei generici abbia aumentato la complessità del linguaggio a un livello inaccettabile (vedere " Generics Considered Harmful " di Ken Arnold ). Le domande frequenti sui generici di Angelika Langer danno un'idea abbastanza chiara di quanto possano diventare complicate le cose.
Java non applica i generics in fase di esecuzione, ma solo in fase di compilazione.
Ciò significa che puoi fare cose interessanti come aggiungere i tipi sbagliati a raccolte generiche.
I generici Java sono solo in fase di compilazione e vengono compilati in codice non generico. In C #, l'effettivo MSIL compilato è generico. Ciò ha enormi implicazioni per le prestazioni perché Java esegue ancora il cast durante il runtime. Vedi qui per ulteriori informazioni .
Se ascolti Java Posse # 279 - Intervista con Joe Darcy e Alex Buckley , parlano di questo problema. Questo si collega anche a un post sul blog di Neal Gafter intitolato Reified Generics per Java che dice:
Molte persone non sono soddisfatte delle restrizioni causate dal modo in cui i generici vengono implementati in Java. In particolare, non sono contenti che i parametri di tipo generico non vengano reificati: non sono disponibili in fase di esecuzione. I generici vengono implementati utilizzando la cancellazione, in cui i parametri di tipo generico vengono semplicemente rimossi in fase di esecuzione.
Quel post del blog fa riferimento a una voce precedente, Puzzling Through Erasure: answer section , che sottolineava il punto sulla compatibilità della migrazione nei requisiti.
L'obiettivo era fornire la retrocompatibilità del codice sorgente e dell'oggetto e anche la compatibilità della migrazione.