JavaRush /Blog Java /Random-ES /Herencia de clases anidadas

Herencia de clases anidadas

Publicado en el grupo Random-ES
¡Hola! Hoy veremos el funcionamiento de un mecanismo importante: la herencia en clases anidadas. No sé si alguna vez has pensado en lo que harás cuando necesites heredar una clase anidada de otra. Si no, créanme: esta situación puede resultar confusa, porque aquí hay muchos matices:
  1. ¿Heredamos una clase anidada de alguna clase o heredamos otra clase de una anidada?
  2. ¿El sucesor/heredado es una clase pública normal o también es una clase anidada?
  3. Finalmente, ¿qué tipo exacto de clases anidadas estamos usando en todas estas situaciones?
Si respondes a todas estas preguntas, habrá tantas respuestas posibles que tu cabeza dará vueltas :) Como sabes, para resolver un problema complejo, debes dividirlo en partes más simples. Eso es lo que haremos. Veamos cada grupo de clases anidadas desde dos perspectivas: quién puede heredar de este tipo de clase anidada y de quién puede heredar. Comencemos con clases anidadas estáticas.

clases anidadas estáticas

Ejemplos de herencia de clases internas - 2Sus reglas de herencia son las más simples. Aquí puedes hacer casi cualquier cosa que tu corazón desee. Una clase anidada estática se puede heredar de:
  • clase regular
  • una clase anidada estática que se declara en la clase externa o en sus antecesores
Recordemos el ejemplo de la conferencia sobre clases anidadas estáticas.
public class Boeing737 {

   private int manufactureYear;
   private static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Drawing {

       public static int getMaxPassengersCount() {

           return maxPassengersCount;
       }
   }
}
Intentemos cambiar el código y crear una clase anidada estática Drawingy su descendiente: Boeing737Drawing.
public class Boeing737 {

   private int manufactureYear;
   private static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Drawing {

   }

   public static class Boeing737Drawing extends Drawing {

       public static int getMaxPassengersCount() {

           return maxPassengersCount;
       }
   }
}
Como puedes ver, no hay problema. Podemos eliminar la clase por completo Drawingy convertirla en una clase pública normal en lugar de una anidada estática; nada cambiará.
public class Drawing {

}

public class Boeing737 {

   private int manufactureYear;
   private static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Boeing737Drawing extends Drawing {

       public static int getMaxPassengersCount() {

           return maxPassengersCount;
       }
   }
}
Eso está solucionado. ¿Y qué clases pueden heredar de una anidada estática? ¡Casi cualquiera! Anidado/regular, estático/no estático: no importa. Aquí heredamos la clase interna Boeing737Drawingde la anidada estática Drawing:
public class Boeing737 {

   private int manufactureYear;
   private static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Drawing {

   }

   public class Boeing737Drawing extends Drawing {

       public int getMaxPassengersCount() {

           return maxPassengersCount;
       }
   }
}
Puedes crear una instancia Boeing737Drawingcomo esta:
public class Main {

   public static void main(String[] args) {

      Boeing737 boeing737 = new Boeing737(1990);
      Boeing737.Boeing737Drawing drawing = boeing737.new Boeing737Drawing();
      System.out.println(drawing.getMaxPassengersCount());

   }

}
Aunque nuestra clase Boeing737Drawinghereda de una clase estática, ¡no es estática en sí misma! Por lo tanto, siempre necesitará una instancia de la clase externa. Podemos sacar la clase Boeing737Drawingde la clase Boeing737y convertirla simplemente en una clase pública. Nada cambiará; también puede heredar de un archivo static anidado Drawing.
public class Boeing737 {

   private int manufactureYear;
   public static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Drawing {

   }
}

public class Boeing737Drawing extends Boeing737.Drawing {

   public int getMaxPassengersCount() {

       return Boeing737.maxPassengersCount;

}
El único punto importante: en este caso necesitamos hacer maxPassengersCountpública la variable estática. Si sigue siendo privado, la clase pública normal no tendrá acceso a él. ¡Hemos resuelto las clases estáticas! :) Ahora pasemos a las clases internas. Como recordarás, hay 3 tipos: simplemente clases internas, clases locales y clases internas anónimas. Ejemplos de herencia de clases internas - 3Nuevamente, pasemos de lo simple a lo complejo :)

Clases internas anónimas

Una clase interna anónima no puede heredar de otra clase. Ninguna otra clase puede heredar de una clase anónima. ¡No podría ser más sencillo! :)

clases locales

Las clases locales (en caso de que lo hayas olvidado) se declaran dentro de un bloque de código de otra clase. La mayoría de las veces, dentro de algún método de esta clase externa. Es lógico que sólo otras clases locales dentro del mismo método (o bloque) puedan heredar de una clase local. He aquí un ejemplo:
public class PhoneNumberValidator {

   public void validatePhoneNumber(final String number) {

       class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }

           public String getPhoneNumber() {
               return phoneNumber;
           }

           public void setPhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

       class CellPhoneNumber extends PhoneNumber {

       }

       class LandlinePhoneNumber extends PhoneNumber {


       }

       //...código валидации номера
   }
}
Este es el código de nuestra conferencia sobre clases locales. Dentro de la clase del validador de números tenemos una clase local PhoneNumber: el número de teléfono. Si para nuestros propósitos necesitamos extraer dos entidades separadas, por ejemplo, un número de teléfono móvil y un número de teléfono fijo, solo podemos hacerlo con el mismo método. La razón es simple: el alcance de una clase local está dentro del método (bloque) donde se declara. Por lo tanto, no podremos usarlo de alguna manera externamente (incluso para herencia). ¡Sin embargo, la propia clase local tiene mayores posibilidades de herencia! Una clase local puede heredar de:
  1. Clase regular.
  2. Una clase interna que se declara en la misma clase que la clase local o en sus antecesores.
  3. De otra clase local declarada en el mismo método (bloque).
El primer y tercer punto parecen obvios, pero el segundo es un poco confuso :/ Veamos dos ejemplos. Ejemplo 1: "heredar una clase local de una clase interna que fue declarada en la misma clase que la clase local":
public class PhoneNumberValidator {

   class PhoneNumber {

       private String phoneNumber;

       public PhoneNumber(String phoneNumber) {
           this.phoneNumber = phoneNumber;
       }

       public String getPhoneNumber() {
           return phoneNumber;
       }

       public void setPhoneNumber(String phoneNumber) {
           this.phoneNumber = phoneNumber;
       }
   }

   public void validatePhoneNumber(final String number) {

       class CellPhoneNumber extends PhoneNumber {

           public CellPhoneNumber(String phoneNumber) {
               super(number);
           }
       }

       class LandlinePhoneNumber extends PhoneNumber {

           public LandlinePhoneNumber(String phoneNumber) {
               super(number);
           }
       }

       //...código валидации номера
   }
}
Aquí hemos sacado la clase PhoneNumberdel método validatePhoneNumber()y la hemos hecho interna en lugar de local. Esto no nos impide heredar nuestras 2 clases locales. Ejemplo 2: "...o en los antepasados ​​de esta clase". Aquí es donde se vuelve más interesante. Podemos llevarlo PhoneNumberaún más arriba en la cadena de herencia. Declaremos una clase abstracta AbstractPhoneNumberValidatorque se convertirá en nuestro antepasado PhoneNumberValidator:
public abstract class AbstractPhoneNumberValidator {

   class PhoneNumber {

       private String phoneNumber;

       public PhoneNumber(String phoneNumber) {
           this.phoneNumber = phoneNumber;
       }

       public String getPhoneNumber() {
           return phoneNumber;
       }

       public void setPhoneNumber(String phoneNumber) {
           this.phoneNumber = phoneNumber;
       }
   }

}
Como puede ver, no solo lo declaramos, sino que también le incorporamos la clase interna PhoneNumber. Sin embargo, en su clase descendiente, PhoneNumberValidatorlas clases locales en los métodos pueden heredar de PhoneNumber!
public class PhoneNumberValidator extends AbstractPhoneNumberValidator {

   public void validatePhoneNumber(final String number) {

       class CellPhoneNumber extends PhoneNumber {

           public CellPhoneNumber(String phoneNumber) {
               super(number);
           }
       }

       class LandlinePhoneNumber extends PhoneNumber {

           public LandlinePhoneNumber(String phoneNumber) {
               super(number);
           }
       }

       //...código валидации номера
   }
}
Gracias a la conexión a través de la herencia, las clases locales dentro de una clase descendiente "ven" las clases internas dentro del antepasado. Y finalmente, pasemos al último grupo :)

clases internas

Una clase interna puede ser heredada por otra clase interna declarada en la misma clase externa (o su descendiente). Veamos esto usando nuestro ejemplo de bicicleta de la conferencia sobre clases internas.
public class Bicycle {

   private String model;
   private int mawWeight;

   public Bicycle(String model, int mawWeight) {
       this.model = model;
       this.mawWeight = mawWeight;
   }

   public void start() {
       System.out.println("¡Ir!");
   }

   class Seat {

       public void up() {

           System.out.println("Сидение поднято выше!");
       }

       public void down() {

           System.out.println("Сидение опущено ниже!");
       }
   }

   class SportSeat extends Seat {

       //...métodos
   }
}
BicycleAquí declaramos una clase interna dentro de la clase Seat- asiento. De él se heredó un subtipo especial de asientos de carreras: SportSeat. Sin embargo, podríamos crear un tipo separado de "bicicletas de carreras" y ponerlo en una clase separada:
public class SportBicycle extends Bicycle {

   public SportBicycle(String model, int mawWeight) {
       super(model, mawWeight);
   }


   class SportSeat extends Seat {

       public void up() {

           System.out.println("Сидение поднято выше!");
       }

       public void down() {

           System.out.println("Сидение опущено ниже!");
       }
   }
}
Esto también es posible. La clase interna del niño ( SportBicycle.SportSeat) “ve” las clases internas del antepasado y puede heredar de ellas. ¡La herencia de clases internas tiene una característica muy importante! En los dos ejemplos anteriores teníamos SportSeatinternal. Pero ¿qué pasa si decidimos convertirla SportSeaten una clase pública regular, que también hereda de la clase interna Seat?
//ошибка! No inclosing instance of  type 'Bicycle' is in scope
class SportSeat extends Bicycle.Seat {

   public SportSeat() {

   }

   public void up() {

       System.out.println("Сидение поднято выше!");
   }

   public void down() {

       System.out.println("Сидение опущено ниже!");
   }
}
¡Tenemos un error! ¿Puedes adivinar con qué está conectado? :) Es sencillo. Cuando hablamos de la clase interna Bicycle.Seat, mencionamos que el constructor de la clase interna pasa implícitamente una referencia a un objeto de la clase externa. Por lo tanto, sin crear un objeto, Bicycleno se puede crear un objeto Seat. ¿Qué pasa con la creación SportSeat? No tiene el mismo mecanismo incorporado para pasar implícitamente una referencia a un objeto de clase externa en el constructor como en Seat. Sin embargo, sin un objeto Bicycle, al igual que en el caso de Seat, no podemos crear un objeto SportSeat. Por lo tanto, sólo nos queda una cosa por hacer: pasar explícitamente SportSeatuna referencia al objeto al constructor. BicycleAsí es como se hace:
class SportSeat extends Bicycle.Seat {

   public SportSeat(Bicycle bicycle) {

       bicycle.super();
   }

   public void up() {

       System.out.println("Сидение поднято выше!");
   }

   public void down() {

       System.out.println("Сидение опущено ниже!");
   }
}
Para esto usamos una palabra especial. super(); Ahora bien, si queremos crear un objeto SportSeat, nada nos impedirá hacerlo:
public class Main {

   public static void main(String[] args) {

       Bicycle bicycle = new Bicycle("Peugeot", 120);
       SportSeat peugeotSportSeat = new SportSeat(bicycle);

   }
}
Uf, la conferencia resultó ser bastante importante :) ¡Pero aprendiste muchas cosas nuevas! ¡Ahora es el momento de resolver algunos problemas! :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION