¿Cómo puede una clase derivada invocar el método privado de la clase base?

public class PrivateOverride { private void f() { System.out.println("private f()"); } } public class Derived extends PrivateOverride { public void f() { //this method is never run. System.out.println("public f()"); } } public static void main(String[] args) { // instantiate Derived and assign it to // object po of type PrivateOverride. PrivateOverride po = new Derived(); // invoke method f of object po. It // chooses to run the private method of PrivateOveride // instead of Derived po.f(); } } 

Entonces, el resultado de este código es private f() . Ahora, la pregunta surge en mi mente: ¿cómo puede po que es un objeto de Derived Class llamar a un método privado de PrivateOverride que es su clase base?

Porque definió el método principal en la clase PrivateOverride . Si coloca el método principal en la clase Derivada, no se comstackría, porque .f() no sería visible allí.

La llamada po.f () en la clase PrivateOverride no es un polymorphism, porque la clase f() en PrivateOverride es private , por lo que f() en la clase Derived no se anula.

Realmente no veo el problema. Ese método se llama “” dentro de “” la clase, esto es bastante esperado. Este método no está excluido en absoluto, sino que está sombreado por otro.

Los métodos en Java se envían según el tipo de receptor estático, que en este caso es PrivateOverride . No se deje confundir por el hecho de que la variable po , al examinar el código, solo puede contener una instancia Derived en esa línea: solo la statement importa cuando se buscan los métodos disponibles.

Y, dicho sea de paso, la llamada a f() ni siquiera se traduce en una llamada virtual en el bytecode final, porque cuando el comstackdor busca los métodos potencialmente aplicables en la clase PrivateOverride , solo encuentra métodos Object y f() definición, que solo es visible porque el método main () se define en PrivateOverride (ver JLS 15.12 )

Acabo de revisar el código de bytes de la versión comstackda de la clase anterior y obtuve el Opcodeespecífico. Este código de operación fue suficiente para decir la razón por la cual la salida real es obvia. Invokespecial se utiliza en tres situaciones en las que se debe invocar un método de instancia en función del tipo de referencia, no de la clase del objeto. Las tres situaciones son:

1) invocación de métodos de inicialización de instancias ()

2) invocación de métodos privados

3) invocación de métodos usando la palabra clave super

El ejemplo anterior se encuentra dentro del segundo escenario en el que tenemos invocación de métodos privados. Entonces, el método fue invocado en función del tipo de referencia, es decir, PrivateOverride en lugar del tipo de clase, es decir, Derived

Entonces, ahora surge la pregunta ¿por qué invokespecial? Tenemos otro Opcode como invokevirtual que se invoca para el método sobre la base de classtype en lugar de tipo de referencia. Así que veamos por qué invokespecial Opcode se usa para métodos privados. Pero debemos saber la diferencia entre invokevirtual e invokespecial. Invokespecial difiere de invokevirtual principalmente en que invokespecial selecciona un método basado en el tipo de referencia en lugar de la clase del objeto. En otras palabras, hace enlace estático en lugar de enlace dynamic. En cada una de las tres situaciones en las que se usa invokespecial, la vinculación dinámica no arrojaría el resultado deseado.

Cuando se invoca un método, la JVM tiene que descubrir qué parte del código ejecutar: a veces esto se hace en tiempo de ejecución (por ejemplo, cuando se reemplazan los métodos); a veces esto se hace en tiempo de comstackción (por ejemplo, al sobrecargar métodos). Una vez que la JVM resuelve qué bit de código está ejecutando, la instancia real a la que se refiere no es en realidad más importante que cualquier otro parámetro.

El código de ejemplo proporcionado establece un escenario que puede parecer una anulación de método pero no lo es, por lo que el método termina siendo enlazado en tiempo de comstackción. El modificador de visibilidad private no se viola porque la invocación no toca ninguno de los códigos de Derived .

Ver el bytecode (que el código de Java está comstackdo a través de javac ) es instructivo:

Digamos que modificamos ligeramente el código original para:

 public class PrivateOverride { private void f() { System.out.println("private f()"); } public static void main(String[] args) { PrivateOverride po = new Derived(); po.f(); Derived d = new Derived(); df(); } } class Derived extends PrivateOverride { public void f() { System.out.println("public f()"); } } 

El método principal se comstack para (corregido por brevedad):

 public static main([Ljava/lang/String;)V NEW Derived DUP INVOKESPECIAL Derived.()V ASTORE 1 ALOAD 1 INVOKESPECIAL PrivateOverride.f()V NEW Derived DUP INVOKESPECIAL Derived.()V ASTORE 2 ALOAD 2 INVOKEVIRTUAL Derived.f()V RETURN 

Observe que en cada caso el método se invoca en el tipo de tiempo de comstackción. Observe también que la segunda llamada de f () usa la instrucción INVOKEVIRTUAL. Esto es lo que le dice a la JVM que verifique el tipo de tiempo de ejecución y decida a qué llamar en función de eso.

Se comporta de esta manera porque así es como se definió la JVM para comportarse en esos casos.

La parte difícil es entender qué está pasando y por qué.

Invocaste el método privado desde dentro de la clase en la que era privado. Entonces, el caballo de Troya está dentro del castillo, él puede jugar con las variables privadas. Saca el troyano del castillo y el método privado ya no es visible.

Este ejemplo podría aclarar las cosas, considere este progtwig:

 public class Bicycle { private void getCost() { System.out.println("200"); } public static void main(String[] args) { Bicycle ACME_bike = new ACME_bike(); ACME_bike.getCost(); Bicycle mybike = new Bicycle(); mybike.getCost(); ACME_bike acme_bike = new ACME_bike(); acme_bike.getCost(); //ACME_bike foobar = new Bicycle(); //Syntax error: Type mismatch: //cannot convert from //Bicycle to ACME_bike } } class ACME_bike extends Bicycle { public void getCost(){ System.out.println("700"); } } 

Este progtwig imprime:

 200 200 700 

Si cambia el modificador de acceso de getCost dentro de Bicycle a public , protected o package private (sin modificador), entonces imprime esto:

 700 200 700 

Hay cuatro niveles de visibilidad en Java, a nivel de paquete (implícito al no usar ninguna visibilidad), público (todos pueden llamarlo), privado (solo I, y clases internas que ven mis funciones como globales, pueden llamarlo), y protegido (Yo, y cualquier subclase, puedo llamar esto).

Puede hacer que su segunda clase sea una clase interna y luego no anular, sino que simplemente llama a la función como si fuera global (en lo que respecta a la clase interna), pero entonces no puede crear instancias donde quiera porque es una clase que puede ser utilizada exclusivamente por el propietario (por lo tanto, si desea utilizarla en otro lugar, el propietario debería ser una fábrica y devolverla como si fuera la clase base) o puede proteger el método, y luego extender la clase puede llamar al método.