Ereditarietà: il codice dalla superclasse è virtualmente * copiato * nella sottoclasse o * è indicato dalla sottoclasse *?


10

La classe Subè una sottoclasse di classe Sup. Cosa significa praticamente? O in altre parole, qual è il significato pratico di "eredità"?

Opzione 1: il codice da Sup viene praticamente copiato in Sub. (come in "copia-incolla", ma senza il codice copiato visualizzato visivamente nella sottoclasse).

Esempio: methodA()è un metodo originariamente in Sup. Sub estende Sup, quindi methodA()viene (praticamente) copiato e incollato su Sub. Ora Sub ha un metodo chiamato methodA(). È identico a Sup methodA()in ogni riga di codice, ma appartiene interamente a Sub - e non dipende da Sup o non è in alcun modo correlato a Sup.

Opzione 2: il codice di Sup non viene effettivamente copiato in Sub. È ancora solo nella superclasse. Ma quel codice è accessibile tramite la sottoclasse e può essere utilizzato dalla sottoclasse.

Esempio: methodA()è un metodo in Sup. Sotto si estende Sup, così ora methodA()si può accedere attraverso il sub in questo modo: subInstance.methodA(). Ma ciò invocherà effettivamente methodA()nella superclasse. Ciò significa che methodA () opererà nel contesto della superclasse, anche se è stato chiamato dalla sottoclasse.

Domanda: quale delle due opzioni è davvero come funzionano le cose? Se nessuno di loro lo è, allora descrivi come funzionano effettivamente queste cose.


È facile testarlo da soli: scrivere il codice, esaminare i file di classe (anche un checksum lo farebbe), modificare la superclasse, compilare di nuovo, guardare di nuovo i file di classe. Potresti anche trovare utile leggere il capitolo 3. Compilare la Java Virtual Machine della specifica JVM (in particolare la sezione 3.7).

@MichaelT " virtualmente copiato" è la parola chiave. Inoltre, anche se il codice veniva letteralmente copiato, ciò potrebbe accadere solo dopo il caricamento della classe.

@delnan sarebbe curioso se Hotspot (o altri ottimizzatori di runtime) incorporassero il codice ad un certo punto, ma questo diventa un dettaglio di implementazione della JVM che può differire da una JVM a un'altra e quindi non è possibile rispondere correttamente. Il meglio che si può fare è guardare il bytecode compilato (e l' uso del codice operativo invocespecial che descrive ciò che effettivamente accade)

Risposte:


13

Opzione 2.

Il bytecode viene referenziato dinamicamente in fase di esecuzione: ecco perché, ad esempio, si verificano LinkageErrors .

Ad esempio, supponi di compilare due classi:

public class Parent {
  public void doSomething(String x) { ... }
}

public class Child extends Parent {
  @Override
  public void doSomething(String x) {
    super.doSomething(x);
    ...
  }
}

Ora modifica e ricompila la classe genitore senza modificare o ricompilare la classe figlio :

public class Parent {
  public void doSomething(Collection<?> x) { ... }
}

Infine, esegui un programma che utilizza la classe figlio. Riceverai un NoSuchMethodError :

Viene generato se un'applicazione tenta di chiamare un metodo specificato di una classe (statica o di istanza) e tale classe non ha più una definizione di quel metodo.

Normalmente, questo errore viene rilevato dal compilatore; questo errore può verificarsi solo in fase di esecuzione se la definizione di una classe è stata modificata in modo incompatibile.


7

Iniziamo con due semplici classi:

package com.michaelt.so.supers;

public class Sup {
    int methodA(int a, int b) {
        return a + b;
    }
}

e poi

package com.michaelt.so.supers;

public class Sub extends Sup {
    @Override
    int methodA(int a, int b) {
        return super.methodA(a, b);
    }
}

Metodo di compilazione A e guardando il codice byte si ottiene:

  methodA(II)I
   L0
    LINENUMBER 6 L0
    ALOAD 0
    ILOAD 1
    ILOAD 2
    INVOKESPECIAL com/michaelt/so/supers/Sup.methodA (II)I
    IRETURN
   L1
    LOCALVARIABLE this Lcom/michaelt/so/supers/Sub; L0 L1 0
    LOCALVARIABLE a I L0 L1 1
    LOCALVARIABLE b I L0 L1 2
    MAXSTACK = 3
    MAXLOCALS = 3

E puoi vedere proprio lì con il metodo invokespecial che fa la ricerca contro la classe Sup methodA ().

Il codice operativo invokespecial ha la seguente logica:

  • Se C contiene una dichiarazione per un metodo di istanza con lo stesso nome e lo stesso descrittore del metodo risolto, questo metodo verrà invocato. La procedura di ricerca termina.
  • Altrimenti, se C ha una superclasse, questa stessa procedura di ricerca viene eseguita in modo ricorsivo utilizzando la superclasse diretta di C. Il metodo da invocare è il risultato dell'invocazione ricorsiva di questa procedura di ricerca.
  • Altrimenti, viene generato un AbstractMethodError.

In questo caso, non esiste un metodo di istanza con lo stesso nome e lo stesso descrittore nella sua classe, quindi il primo proiettile non sparerà. Il secondo proiettile comunque sarà: c'è una superclasse e invoca il metodo del superA.

Il compilatore non lo incorpora e non esiste una copia del sorgente di Sup nella classe.

Tuttavia la storia non è ancora finita. Questo è solo ilcodice compilato . Una volta che il codice ha raggiunto la JVM, HotSpot può essere coinvolto.

Sfortunatamente, non ne so molto, quindi farò appello all'autorità su questo argomento e andrò a Inlining in Java dove si dice che HotSpot può incorporare metodi (anche metodi non finali).

Andando ai documenti si nota che se una particolare chiamata di metodo diventa un hot spot invece di fare quella ricerca ogni volta, queste informazioni possono essere incorporate - copiando efficacemente il codice da Sup methodA () in Sub methodA ().

Ciò viene eseguito in fase di esecuzione, in memoria, in base al comportamento dell'applicazione e alle ottimizzazioni necessarie per accelerare le prestazioni.

Come affermato in HotSpot Internals per OpenJDK "I metodi sono spesso incorporati. Le invocazioni statiche, private, finali e / o" speciali "sono facili da incorporare."

Se scavi nelle opzioni per la JVM troverai un'opzione di -XX:MaxInlineSize=35(35 è il valore predefinito) che è il numero massimo di byte che possono essere incorporati. Sottolineerò che questo è il motivo per cui a Java piace avere molti piccoli metodi, perché possono essere facilmente incorporati. Quei piccoli metodi diventano più veloci quando vengono chiamati di più perché possono essere incorporati. E mentre si può giocare con quel numero e aumentarlo, si possono rendere meno efficaci altre ottimizzazioni. (domanda SO correlata: strategia di integrazione di HotSpot JIT che evidenzia una serie di altre opzioni per sbirciare gli interni di inline che HotSpot sta facendo).

Quindi no: il codice non è integrato al momento della compilazione. E sì: il codice potrebbe benissimo essere integrato durante l'esecuzione se le ottimizzazioni delle prestazioni lo giustificano.

E tutto ciò che ho scritto sull'instradamento di HotSpot si applica solo a HotSpot JVM distribuito da Oracle. Se guardi l'elenco delle macchine virtuali Java di wikipedia, ce ne sono molte di più oltre a HotSpot e il modo in cui tali JVM gestiscono l'inline può essere completamente diverso da quello che ho descritto sopra. Apache Harmony, Dalvik, ART - le cose potrebbero funzionare diversamente lì.


0

il codice non viene copiato, si accede per riferimento:

  • la sottoclasse fa riferimento ai suoi metodi e alla superclasse
  • la superclasse fa riferimento ai suoi metodi

i compilatori possono ottimizzare il modo in cui questo viene rappresentato / eseguito in memoria, ma questa è sostanzialmente la struttura

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.