Herencia: ¿El código de la superclase está virtualmente * copiado * a la subclase, o es * referido por la subclase *?

10

La clase Subes una subclase de clase Sup. ¿Qué significa eso prácticamente? O, en otras palabras, ¿cuál es el significado práctico de "herencia"?

Opción 1: el código de Sup se copia virtualmente en Sub. (como en 'copiar y pegar', pero sin el código copiado visualmente visto en la subclase).

Ejemplo: methodA()es un método originalmente en Sup. Sub extiende Sup, por lo que methodA()se copia (virtualmente) en Sub. Ahora Sub tiene un método llamado methodA(). Es idéntico a Sup methodA()en cada línea de código, pero pertenece completamente a Sub, y no depende de Sup o está relacionado con Sup de ninguna manera.

Opción 2: el código de Sup no se copia en realidad en Sub. Todavía está solo en la superclase. Pero se puede acceder a ese código a través de la subclase y puede ser utilizado por la subclase.

Ejemplo: methodA()es un método en Sup. Sub extiende Sup, por lo que ahora methodA()se puede acceder a través de sub modo: subInstance.methodA(). Pero eso en realidad invocará methodA()en la superclase. Lo que significa que el método A () operará en el contexto de la superclase, incluso si fue llamado por la subclase.

Pregunta: ¿Cuál de las dos opciones es realmente cómo funcionan las cosas? Si ninguno de ellos lo es, entonces describa cómo funcionan realmente estas cosas.

Aviv Cohn
fuente
Esto es fácil de probar por sí mismo: escriba el código, examine los archivos de clase (incluso una suma de comprobación lo haría), modifique la superclase, compile nuevamente, mire los archivos de clase nuevamente. También puede encontrar que la lectura del Capítulo 3. Compilación de la máquina virtual Java de la especificación JVM es útil para comprender (especialmente la sección 3.7).
@MichaelT " prácticamente copiado" es la palabra clave. Además, incluso si el código se estuviera copiando literalmente, esto podría suceder solo después de cargar la clase.
@delnan sería curioso si Hotspot (u otros optimizadores de tiempo de ejecución) alinearían el código en algún momento, pero eso se convierte en un detalle de implementación de la JVM que puede diferir de una JVM a otra y, por lo tanto, no se pudo responder correctamente. Lo mejor que se puede hacer es mirar el bytecode compilado (y el uso de código de operación especial invook que describe lo que realmente sucede)

Respuestas:

13

Opcion 2.

El bytecode se referencia dinámicamente en tiempo de ejecución: esta es la razón por la cual, por ejemplo, se producen LinkageErrors .

Por ejemplo, suponga que compila dos clases:

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

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

Ahora modifique y vuelva a compilar la clase principal sin modificar o volver a compilar la clase secundaria :

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

Finalmente, ejecute un programa que use la clase secundaria. Recibirá un NoSuchMethodError :

Se lanza si una aplicación intenta llamar a un método específico de una clase (ya sea estática o instancia), y esa clase ya no tiene una definición de ese método.

Normalmente, este error es detectado por el compilador; Este error solo puede ocurrir en tiempo de ejecución si la definición de una clase ha cambiado de manera incompatible.


fuente
7

Comencemos con dos clases simples:

package com.michaelt.so.supers;

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

y entonces

package com.michaelt.so.supers;

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

Compilando el método A y mirando el código de bytes se obtiene:

  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

Y puede ver allí con el método invokespecial que realiza la búsqueda contra el método de clase SupA ().

El código operativo invokespecial tiene la siguiente lógica:

  • Si C contiene una declaración para un método de instancia con el mismo nombre y descriptor que el método resuelto, se invocará este método. El procedimiento de búsqueda termina.
  • De lo contrario, si C tiene una superclase, este mismo procedimiento de búsqueda se realiza de forma recursiva utilizando la superclase directa de C. El método a invocar es el resultado de la invocación recursiva de este procedimiento de búsqueda.
  • De lo contrario, se genera un AbstractMethodError.

En este caso, no existe un método de instancia con el mismo nombre y descriptor en su clase, por lo que la primera viñeta no se disparará. Sin embargo, la segunda viñeta sí: hay una superclase e invoca el método A de la super.

El compilador no incluye esto y no hay una copia de la fuente de Sup en la clase.

Sin embargo, la historia aún no ha terminado. Este es solo elcódigo compilado . Una vez que el código llega a la JVM, HotSpot puede involucrarse.

Desafortunadamente, no sé mucho al respecto, por lo que recurriré a la autoridad sobre este asunto e iré a Inlining in Java, donde se dice que HotSpot puede incorporar métodos en línea (incluso métodos no finales).

Al ir a los documentos , se observa que si una llamada a un método en particular se convierte en un punto caliente en lugar de hacer esa búsqueda cada vez, esta información se puede insertar, copiando efectivamente el código del método Sup A () en el método Sub A ().

Esto se realiza en tiempo de ejecución, en la memoria, según el comportamiento de la aplicación y las optimizaciones necesarias para acelerar el rendimiento.

Como se indica en HotSpot Internals para OpenJDK "Los métodos a menudo están en línea. Las invocaciones estáticas, privadas, finales y / o" especiales "son fáciles de incorporar".

Si profundiza en las opciones para la JVM , encontrará una opción de -XX:MaxInlineSize=35(35 es el valor predeterminado) que es el número máximo de bytes que se pueden insertar. Señalaré que esta es la razón por la que a Java le gusta tener muchos métodos pequeños, porque se pueden insertar fácilmente. Esos pequeños métodos se vuelven más rápidos cuando se los llama más porque pueden estar en línea. Y aunque uno puede jugar con ese número y hacerlo más grande, puede causar que otras optimizaciones sean menos efectivas. (Pregunta SO relacionada: estrategia de línea HotSpot JIT que señala una serie de otras opciones para echar un vistazo a las partes internas de la línea que está haciendo HotSpot).

Entonces, no, el código no está en línea en el momento de la compilación. Y sí, el código podría muy bien insertarse en tiempo de ejecución si las optimizaciones de rendimiento lo justifican.

Y todo lo que he escrito sobre la incorporación de HotSpot solo se aplica a HotSpot JVM distribuido por Oracle. Si nos fijamos en la lista de máquinas virtuales Java de Wikipedia, hay muchos más que solo HotSpot y la forma en que esas JVM manejan la alineación puede ser completamente diferente de lo que he descrito anteriormente. Apache Harmony, Dalvik, ART: las cosas pueden funcionar de manera diferente allí.

Comunidad
fuente
0

el código no se copia, se accede por referencia:

  • la subclase hace referencia a sus métodos y a la superclase
  • la superclase hace referencia a sus métodos

los compiladores pueden optimizar cómo se representa / ejecuta esto en la memoria, pero esa es básicamente la estructura

Steven A. Lowe
fuente