¡Hola! Hoy veremos un mecanismo importante: la herencia en clases anidadas. ¿Alguna vez ha pensado en lo que haría si necesitara hacer que una clase anidada herede otra clase? Si no, créame: esta situación puede ser confusa, porque hay muchos matices.
- ¿Estamos haciendo que una clase anidada herede alguna clase? ¿O estamos haciendo que alguna clase herede una clase anidada?
- ¿La clase hijo/padre es una clase pública normal o también es una clase anidada?
- Finalmente, ¿qué tipo de clases anidadas usamos en todas estas situaciones?
Clases estáticas anidadas
Sus reglas de herencia son las más simples. Aquí puedes hacer casi cualquier cosa que tu corazón desee. Una clase anidada estática puede heredar:- una clase ordinaria
- una clase anidada estática que se declara en una clase externa o sus ancestros
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 Drawing
clase anidada estática y 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. Incluso podemos extraer la Drawing
clase y convertirla en una clase pública ordinaria en lugar de una clase 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;
}
}
}
Entendemos esto. Pero, ¿qué clases pueden heredar una clase anidada estática? ¡Prácticamente cualquiera! Anidado/no anidado, estático/no estático: no importa. Aquí hacemos que la Boeing737Drawing
clase interna herede la Drawing
clase anidada estática:
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 de Boeing737Drawing
como 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 Boeing737Drawing
clase hereda una clase estática, ¡no es estática en sí misma! Como resultado, siempre necesitará una instancia de la clase externa. Podemos eliminar la Boeing737Drawing
clase de la Boeing737
clase y convertirla en una clase pública simple. Nada cambia. Todavía puede heredar la Drawing
clase anidada estática.
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 es que en este caso necesitamos hacer maxPassengersCount
pública la variable estática. Si sigue siendo privado, una clase pública ordinaria no tendrá acceso a él. ¡Hemos descubierto las clases estáticas! :) Ahora pasemos a las clases internas. Vienen en 3 tipos: clases internas simples, clases locales y clases internas anónimas. Nuevamente, pasemos de lo simple a lo complejo :)
Clases internas anónimas
Una clase interna anónima no puede heredar otra clase. Ninguna otra clase puede heredar 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, esto sucede dentro de algún método de la clase externa. Lógicamente, solo otras clases locales dentro del mismo método (o bloque de código) pueden heredar una clase local. Aquí hay 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 {
}
// ...number validation code
}
}
Este es el código de nuestra lección sobre clases locales. Nuestra clase de validador de números tiene una PhoneNumber
clase local. Si necesitamos que represente dos entidades distintas, por ejemplo, un número de teléfono móvil y un número de teléfono fijo, solo podemos hacerlo dentro del mismo método. La razón es simple: el alcance de una clase local está limitado al método (bloque de código) donde se declara. Como resultado, no podremos usarlo externamente (incluso para la herencia de clases). Sin embargo, las posibilidades de herencia dentro de la propia clase local son mucho más amplias. Una clase local puede heredar:
- Una clase ordinaria.
- Una clase interna que se declara en la misma clase que la clase local o en uno de sus ancestros.
- Otra clase local declarada en el mismo método (bloque de código).
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);
}
}
// ...number validation code
}
}
Aquí eliminamos la PhoneNumber
clase del validatePhoneNumber()
método y la convertimos en una clase interna en lugar de una clase local. Esto no nos impide hacer que nuestras 2 clases locales lo hereden. Ejemplo 2: "... o en los antepasados de esta clase". Ahora esto ya es más interesante. Podemos avanzar PhoneNumber
aún más en la cadena de herencia. Declaremos una AbstractPhoneNumberValidator
clase abstracta, que se convertirá en el ancestro de nuestra PhoneNumberValidator
clase:
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 trasladamos la PhoneNumber
clase interna a él. Sin embargo, en su descendiente PhoneNumberValidator
, las clases locales declaradas en los métodos pueden heredar PhoneNumber
sin ningún problema.
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);
}
}
// ...number validation code
}
}
Debido a la relación de herencia, las clases locales dentro de una clase descendiente "ven" las clases internas dentro de un ancestro. Y finalmente, pasemos al último grupo :)
clases internas
Una clase interna declarada en la misma clase externa (o en su descendiente) puede heredar otra clase interna. Exploremos esto usando nuestro ejemplo con bicicletas de la lección sobre clases internas.
public class Bicycle {
private String model;
private int maxWeight;
public Bicycle(String model, int maxWeight) {
this.model = model;
this.maxWeight = maxWeight;
}
public void start() {
System.out.println("Let's go!");
}
class Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
class SportSeat extends Seat {
// ...methods
}
}
Aquí declaramos la Seat
clase interna dentro de la Bicycle
clase. Un tipo especial de asiento de carreras, SportSeat
lo hereda. Pero, podríamos crear un tipo de "bicicleta de carreras" separado y ponerlo en una clase separada:
public class SportBicycle extends Bicycle {
public SportBicycle(String model, int maxWeight) {
super(model, maxWeight);
}
class SportSeat extends Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
}
Esta también es una opción. La clase interna del descendiente ( SportBicycle.SportSeat
) "ve" las clases internas del antepasado y puede heredarlas. ¡Heredar clases internas tiene una característica muy importante! En los dos ejemplos anteriores, nuestra SportSeat
clase era una clase interna. Pero, ¿y si decidimos hacer SportSeat
una clase pública ordinaria que herede simultáneamente la Seat
clase interna?
// Error! No enclosing instance of type 'Bicycle' is in scope
class SportSeat extends Bicycle.Seat {
public SportSeat() {
}
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
¡Tenemos un error! ¿Puedes adivinar por qué? :) Todo es sencillo. Cuando hablamos de la Bicycle.Seat
clase interna, mencionamos que una referencia a una instancia de la clase externa se pasa implícitamente al constructor de la clase interna. Esto significa que no puede crear un Seat
objeto sin crear un Bicycle
objeto. Pero, ¿qué pasa con la creación de un SportSeat
? A diferencia de Seat
, no tiene este mecanismo incorporado para pasar implícitamente al constructor una referencia a una instancia de la clase externa. Aun así, sin un Bicycle
objeto, no podemos crear un SportSeat
objeto, como en el caso de Seat
. Por lo tanto, solo nos queda una cosa por hacer: pasar explícitamente al SportSeat
constructor una referencia a un Bicycle
objeto. Aquí está cómo hacerlo:
class SportSeat extends Bicycle.Seat {
public SportSeat(Bicycle bicycle) {
bicycle.super();
}
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
Llamamos al constructor de la superclase usando super();
Now, si queremos crear un SportSeat
objeto, 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! Esta lección fue bastante larga :) ¡Pero aprendiste mucho! ¡Ahora es el momento de resolver algunas tareas! :)
GO TO FULL VERSION