2013-03-02 8 views
5

Ho i seguenti codici.L'operazione bytecode Java 'invokevirtual' non mantiene la coerenza per i metodi ereditati dall'oggetto

public class Parent { 

    @Override 
    public int hashCode() { 
     return 0; 
    } 

} 

public class Child extends Parent { 

    public void test() { 
     this.toString(); 
     this.hashCode(); 
    } 

} 

Come si vede nei codici di cui sopra, figlio eredita toString() da Object e hashCode() da Parent. L'operazione Bytecode del test N. figlio è la seguente.

ALOAD 0: this 
INVOKEVIRTUAL Object.toString() : String 
ALOAD 0: this 
INVOKEVIRTUAL Child.hashCode() : int 
RETURN 

penso che se invokevirtual chiama Object.toString(), si dovrebbe chiamare Parent.hashCode() per la coerenza. oppure, chiamato Child.hashCode(), quindi deve essere chiamato Child.toString().

Tuttavia, invokevirtual non mantiene la coerenza se e solo se il metodo di destinazione è ereditato da Object.

Solo quel caso, metodo chiamate invokevirtual nell'oggetto. Per altri casi, il metodo invokevirtual chiama nella classe corrente.

Voglio sapere perché questo accade.

+5

Dato che il metodo effettivo eseguito * sarà * sempre basato sul tipo effettivo al momento dell'esecuzione, perché questo è importante? Sono equivalenti, sicuramente? –

+1

Forse il compilatore è un'ereditarietà di casing speciale da Object, perché è un caso molto comune. –

+0

E come impatta la tua logica? – CuriousMind

risposta

4

È corretto che il compilatore si comporti non logico. Ma l'effetto di questo codice è identico all'effetto di entrambe le varianti che hai suggerito. Quindi questo probabilmente non è un comportamento intenzionale, ma il risultato di una lunga evoluzione del codice del compilatore. Altri compilatori possono produrre codice diverso.

+0

La tua risposta non copre la domanda principale "Perché questo accade?". Hai detto solo che: * sì, succede, perché succede *. – Andremoniy

+1

In realtà, questa risposta si avvicina di più alla verità. Il risultato finale potrebbe non avere alcuna motivazione dietro di esso; sembra proprio che sia così perché non ha molta importanza. –

+0

@Marko Topolnik So che il risultato finale potrebbe essere lo stesso per tutti i casi. Comunque penso che ci possano essere delle ragioni per progettare invokevirtual in quel modo. Per l'ottimizzazione, possono rendere invokevirtual come 'Object.toString()' o 'Parent.hashCode()' perché in questo modo, JVM può dirigere l'implementazione del metodo di riferimento reale senza trovarla. Tuttavia, non lo fecero. Penso che ci possano essere delle ragioni. – Hozard

5

Secondo JVM specification p. 3.7:

Il compilatore non conosce la disposizione interna di un'istanza di classe. Invece, genera riferimenti simbolici ai metodi di un'istanza , che sono memorizzati nel pool costante di runtime. Questi elementi del pool costante vengono risolti in fase di esecuzione per determinare l'effettiva posizione del metodo .

significa che tutti questi simbolico Child.hashCode() sono solo costanti, che non si specifica una sorta di come JVM chiama questo metodo. Sembra, che per toString() metodo compilatore sa, che questo metodo ha la sua implementazione di base in Object classe, in modo che mette costante simbolica per Object classe nella piscina costante - questa è una sorta di ottimizzazione, il che rende compilatore per JVM:

Constant pool: 
const #2 = Method #24.#25; // java/lang/Object.toString:()Ljava/lang/String; 
... 
const #24 = class #34; // java/lang/Object 
const #25 = NameAndType #35:#36;// toString:()Ljava/lang/String; 
+0

Non dovrebbe il compilatore dare lo stesso trattamento a 'hashCode'? Conosce le stesse cose riguardo a 'toString' e le sue prestazioni sono, se possibile, ancora più critiche. –

+0

@MarkoTopolnik 'hashCode' è stato sovrascritto nella classe' Parent', quindi questa ottimizzazione non funziona in questo caso. Il compilatore lascia la risoluzione di riferimento di questo metodo per JVM – Andremoniy

+0

No, la risoluzione del metodo sarà esattamente la stessa in entrambi i casi. 'toString' può ottenere un'implementazione in' Parent' senza dover ricompilare 'Child' per riflettere questo, e il runtime deve ancora funzionare correttamente. –

1

La mia teoria: toString() viene utilizzata molto frequentemente, quindi javac utilizza il comune Object.toString() per salvare voci di pool costanti.

Per esempio se il codice contiene foo.toString() and bar.toString(), la piscina contant necessita di un solo Object.toString, invece di due voci Foo.toString and Bar.toString

Javac probabilmente difficile codificato questa ottimizzazione, invece di analizzare il codice per vedere se è veramente necessario.

+0

quindi potrebbe salvare le voci del pool costante per tutti i metodi della classe 'Object', incluso' hashCode'. –

+0

hashCode() non viene spesso richiamato. mentre l'invocazione 'toString()' esiste quasi in ogni classe. – irreputable

+0

Questo suona molto convincente, +1 per una così bella ipotesi :) Sì, il rapporto tra siti di chiamata 'toString' e' hashCode' è piuttosto ampio. Questa potrebbe essere stata un'ottimizzazione mirata guidata dalla valutazione dei pool costanti effettivi. La confusione 'toString' avrebbe potuto essere identificata come una vittoria chiara, in cui lo stesso non era stato identificato per' hashCode'. BTW 'javac' non è in grado di chiamare questi colpi; solo la JVM ha l'intero classpath da valutare. –

Problemi correlati