2013-08-09 17 views
11

Ho notato qualcosa mentre stavo scherzando con i generici. Nell'esempio che segue, doStuff1 compila ma doStuff2 non lo fa:Java: getClass() del tipo limitato

public <T extends Foo> void doStuff1(T value) { 
    Class<? extends Foo> theClass = value.getClass(); 
} 

public <T extends Foo> void doStuff2(T value) { 
    Class<? extends T> theClass = value.getClass(); 
} 

Così, ho guardato la documentazione per Object.getClass() e abbiamo trovato questo:

Il tipo di risultato effettivo è di classe <? estende | X | > dove | X | è la cancellazione del tipo statico dell'espressione su cui viene chiamato getClass.

Questo mi ha reso un po 'curioso. Perché lo getClass() è progettato in questo modo? Riesco a capire i tipi di conversione nelle loro classi raw, se applicabili, ma non vedo ragioni ovvie perché debbano necessariamente farlo uccidere anche T. Esiste una ragione specifica per cui si sbarazza anche di questo, o è solo un approccio generale "liberiamoci di tutto solo perché è più facile, chi avrebbe mai bisogno comunque"?

+1

Capire questo mi farà male alla testa; Odio calcolare i limiti di tipo generico anche quando non è così tardi. Suggerisco di partire da [il JLS per 'Object'] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.3.2) e guardare la ricerca regole; questo è probabilmente un artefatto di come la JVM fa la sua magia sorta-statica per riflettere sulla classe di runtime dell'oggetto. – chrylis

risposta

7

Se getClass() restituisce Class<? extends X>, non può accadere nulla di veramente brutto;

L'unico problema è che non è teoricamente corretto: se un oggetto è un ArrayList<String>, il suo class non può essere Class<ArrayList<String>> - non esiste una tale classe, c'è solo un Class<ArrayList>.

Questo in realtà non è correlato alla cancellazione. Se un giorno Java ottiene i tipi pieni reificati, getClass() deve ancora restituire Class<? extends |X|>; tuttavia, dovrebbe esserci un nuovo metodo, ad esempio getType() che può restituire un valore più dettagliato Type<? extends X>. (Anche se, getType può entrare in conflitto con un sacco di classi esistenti con i propri getType metodi)

Per l'essere tempistica, dal momento che Class<? extends X> potrebbe essere utile in molti casi, siamo in grado di progettare il nostro proprio metodo che lo fa

static <X> Class<? extends X> myGetClass(X x){ ... } 

ma è comprensibile che non avrebbero messo questo tipo di hack in lib standard.

+1

Cosa metteresti al posto di "' ... '"? Un cast non controllato? – 5gon12eder

+0

@ 5gon12eder - sì. il metodo è fondamentalmente pericoloso; il chiamante deve assicurarsi che abbia senso in particolari casi d'uso. – ZhongYu

-1

Non si può essere sicuri che value è in realtà un T, potrebbe anche essere una sottoclasse di T. getClass() fa tornare la classe "reale" di value, ma non si può essere sicuri che sia T, therfore deve leggere Class<? extends T>! Questo non ha nulla a che fare con il fatto che T è un parametro di tipo.

[Modifica] Ok, non proprio, non mi sono reso conto che method2 non viene compilato. Penso che questo sia un problema del compilatore. Ottengo

Type mismatch: cannot convert from Class<capture#1-of ? extends Foo> to Class<? extends T> 

Penso che il compilatore si accorga solo che l'oggetto T è un Foo. Il compilatore dovrebbe sapere che getClass() restituisce un po 'di Class<? extends T> ma sembra solo sapere che restituisce alcuni Class<? extends Foo>. Significa che Foo è lo erasure of the static type di T?

+1

Uh, e il tuo punto?'Classe ' è lo stesso esempio che ho usato del codice che penso che _should_ sia valido ma non lo è. La domanda riguarda perché getClass() è (apparentemente) iperprotettivo del tipo emesso. – Smallhacker

+0

Uh, il mio male non ho letto abbastanza attentamente per vedere che il tuo secondo metodo non viene compilato. In qualche modo ho pensato che la domanda riguardasse perché il valore di ritorno è Classe anziché Classe ... Penso che sia davvero strano, ora capisco la domanda. Immagino che abbia qualcosa a che fare con il compilatore e niente con getClass(). Forse il compilatore sta cancellando T to Object, ma sa nel primo caso che il tipo statico deve essere un po 'Foo? Quindi nel secondo caso leggerei Classe = FooObject.getClass(), che sembra sbagliato. ?! – kutschkem

-1

Ottima domanda.

L'intero problema con java generics è che non esistevano nelle vecchie macchine virtuali. Sun ha deciso di aggiungere supporto generico mentre la macchina virtuale esistente non li supportava.

Questo potrebbe essere un grosso problema, perché quando rilasciano il nuovo Java, le vecchie macchine virtuali NON sarebbero in grado di eseguire alcun codice scritto per la versione più recente di Java e non sarebbero stati in grado di supportare legacy vecchio macchine virtuali.

Quindi, hanno deciso di implementare i generici in modo che potessero essere eseguiti anche su vecchie macchine virtuali. Hanno deciso di CANCELLARLE DA BYTECODE.

Quindi l'intera storia riguarda il back-support di jvms.

EDIT: Ma questo forse non aveva nulla a che fare con la domanda. Vedi la risposta di kutschkem!

Inoltre, la mia ipotesi è che nel secondo caso, il cast è Class<? extends List> a Class<? extends T>. Questo cast è possibile solo se List estende direttamente T (dato che è un cast implicito). Ma è il contrario, T estende List.

Penso proprio la stessa cosa che dire:

String s = "s"; 
Object o = s; 

Nel caso di cui sopra, il cast è possibile perché String estende oggetto. Per l'inverso, avresti bisogno di un cast esplicito.

Se si aggiunge un cast esplicito al vostro programma, si compila:

public <T extends List> void doStuff2(T value) { 
    Class<? extends T> theClass = (Class<? extends T>) value.getClass(); 
} 
+1

C'è un caso da considerare che questa compatibilità con le versioni precedenti ha causato più problemi di quanto sarebbe stata la rottura, ma è quello che ha deciso Sun. ;-) – chrylis

+0

100% d'accordo. Questa è probabilmente l'unica cosa che odio davvero su Java ^^ – Lake

+0

Questa domanda non riguarda la cancellazione dei tipi, riguarda le gerarchie di classi. guarda la mia risposta Anche se T non era un parametro di tipo, ma qualsiasi classe non finale, allora getClass() restituisce ancora una classe kutschkem

-2

Gli guava reflection strumenti forniscono alcuni strumenti per aggirare questa limitazione e vi consentono di ottenere il tipo (in modo classe) di un tipo limitato .

Come detto nella prima riga di questa pagina spiegazione:

causa di cancellazione del tipo, non si può passare intorno agli oggetti classe generica al fase di esecuzione - si potrebbe essere in grado di lanciare loro e far finta che sono generici, ma in realtà non lo sono.

Guava fornisce TypeToken, che utilizza trucchi basati sulla riflessione per consentire a di manipolare e interrogare tipi generici, anche in fase di esecuzione.

L'idea è di ottenere (o creare) un TypeToken del tipo limitato e quindi è possibile chiedere il suo tipo.

Ho capito che questa risposta è un po 'fuori tema, perché non risponde direttamente alla domanda (la parte "perché"), ma può risolvere il metodo non compilabile, può portare a un codice in grado di risolvere il problema domanda e per darti la risposta alla parte "perché" e aiuterà certamente gli altri a cercare "Java: - how -getClass() del tipo limitato"

Problemi correlati