Ligera confusión con respecto a la anulación de las variables

Me estoy preparando para SCJP (recientemente renombrado como OCPJP por Oracle) y una pregunta en particular que obtuve mal en un examen de simulacro me ha confundido, la descripción de la respuesta no explica las cosas lo suficientemente claras.

Esta es la pregunta :

class A { int x = 5; } class B extends A { int x = 6; } public class CovariantTest { public A getObject() { return new A(); } public static void main(String[]args) { CovariantTest c1 = new SubCovariantTest(); System.out.println(c1.getObject().x); } } class SubCovariantTest extends CovariantTest { public B getObject() { return new B(); } } 

La respuesta es 5 , pero elegí 6 .

Entiendo que la anulación se aplica a los métodos en tiempo de ejecución, y no a las variables, pero la forma en que mi mente interpretó que println era:

  1. llama a getObject en c1
  2. c1 es en realidad un objeto SubCovariantTest y tiene una anulación válida para getObject() , por lo tanto, utilice el método reemplazado
  3. La anulación devuelve B, así que toma x de B, que es 6

¿Es un caso de la JVM ignorando la parte getObject() , y tomando siempre x de c1 como variables asociadas en tiempo de comstackción?

Aunque la anulación se realiza correctamente para SubCovariantTest, la respuesta es 5 debido a la forma en que se declara la variable c1. Se declara como prueba covariante y no como prueba subcovariante.

Cuando se ejecuta c1.getObject () .x, no se sabe que es un SubCovariantTest (no se utilizó el casting). Esta es la razón por la cual 5 se devuelve de CovariantTest y no 6 de SubCovariantTest.

Si cambias

 System.out.println(c1.getObject().x); 

a

 System.out.println(((SubCovariantTest) c1).getObject().x); 

Obtendrás 6 como esperabas.

Editar: Como se señaló en los comentarios

“los campos no son polimórficos en Java. Solo los métodos son. La x en la subclase oculta la x en la clase base. No la anula”. (Gracias a JB Nizet)

El término técnico para lo que está sucediendo aquí es “ocultar”. Los nombres de variables en Java se resuelven por el tipo de referencia, no por el objeto al que hacen referencia.

  • Un objeto tiene una variable Axe.
  • El objeto B tiene variables Ax y Bx.

Sin embargo, los métodos de instancia con la misma firma se “reemplazan” y no “ocultan”, y no se puede acceder a la versión de un método que se anula desde el exterior.

Tenga en cuenta que la ocultación también se aplica a los métodos estáticos con la misma firma.

Su pregunta simulada en una forma simplificada (sin anulación):

 class A { int x = 5; } class B extends A { int x = 6; } public class CovariantTest { public static void main(String[] args) { A a = new B(); B b = new B(); System.out.println(ax); // prints 5 System.out.println(bx); // prints 6 } } 

Está llamando al método desde c1 : System.out.println(c1.getObject().x);

El tipo de referencia c1 es:

 public class CovariantTest { public A getObject() { return new A(); } public static void main(String[]args) { CovariantTest c1 = new SubCovariantTest(); System.out.println(c1.getObject().x); } } 

entonces para esto: el tipo de retorno c1.getObject() es A de A usted obtiene directamente el método de no atributo, ya que menciona que java no anula los atributos, por lo que está tomando x de A

De acuerdo, sé que es un poco tarde para responder a esta pregunta, pero mi amigo y yo tuvimos el mismo problema y las respuestas que obtuvimos aquí no nos lo aclararon. Así que solo voy a decir qué problema tuve y cómo tiene sentido ahora 🙂

Ahora entiendo que los campos no se anulan, sino que se ocultan como miller.bartek señaló y también entiendo que la anulación es por métodos y no campos como señala Scott.

El problema que tuve sin embargo fue esto. Según yo,

 c1.getObject().x 

Esto debe transformarse en:

 new B().x // and not newA().x since getObject() gets overrided 

Y eso evalúa a 6.

Y no pude entender por qué la variable de clase A (superclase) está siendo invocada por un objeto de clase B (subclase) sin haber pedido explícitamente tal comportamiento.

Y adivinando por la redacción de la pregunta, creo que OP tenía la misma pregunta / duda en mente.


Mi respuesta:

Recibes una pista de la respuesta de Elbek. Coloque las siguientes líneas en el método principal e intente comstackr el código:

 A a = c1.getObject(); //line 1 B b = c1.getObject(); //line 2 

Notarás que la línea 1 es completamente legal mientras que la línea 2 da un error de comstackción.

Entonces, cuando se llama a la función getObject (), la función CovariantTest (super) IS queda anulada por la función SubCovariantTest (sub), ya que es una invalidación válida en el código y c1.getObject () VOLVERÁ B ().

Sin embargo, dado que la superfunción devuelve una referencia de clase A, incluso después de sobrescribirse, debe devolver una referencia de clase A, a menos que, por supuesto, la hayamos emitido. Y aquí, la clase B es una clase A (debido a la herencia).

Prácticamente, lo que estamos obteniendo de c1.getObject () no es

 new B() 

pero esto:

 (A) new B() 

Es por eso que la salida sale a ser 5 aunque se devuelva un objeto de clase B y la clase B tenga un valor de x como 6.

Cuando se anulan los métodos, se invocan los métodos de las subclases, y cuando se invalidan las variables se usan las variables de las superclases

Cuando las clases secundaria y secundaria tienen una variable con el mismo nombre, la variable de la clase secundaria oculta la variable de la clase principal y esto se denomina ocultación variable.

Mientras que la ocultación variable parece anular una variable similar al método que reemplaza, pero no lo es, la Anulación solo se aplica a los métodos mientras se ocultan las variables aplicables.

En el caso de anulación de método, los métodos reemplazados reemplazan completamente a los métodos heredados, de modo que cuando intentamos acceder al método desde la referencia principal manteniendo el objeto del niño, se llama al método de la clase hija.

Pero en la ocultación variable, la clase secundaria oculta las variables heredadas en lugar de reemplazarlas, de modo que cuando intentamos acceder a la variable desde la referencia principal manteniendo el objeto del niño, se accederá desde la clase principal.

Cuando una variable de instancia en una subclase tiene el mismo nombre que una variable de instancia en una superclase, entonces la variable de instancia se elige del tipo de referencia.

Puede leer más sobre Qué es el Sombreado Variable y Ocultar en Java.