JavaRush /Blogue Java /Random-PT /Herança de classes aninhadas

Herança de classes aninhadas

Publicado no grupo Random-PT
Olá! Hoje veremos a operação de um mecanismo importante - herança em classes aninhadas. Não sei se você já pensou no que fará quando precisar herdar uma classe aninhada de outra. Se não, acredite: esta situação pode ser confusa, porque há muitas nuances aqui:
  1. Herdamos uma classe aninhada de alguma classe ou herdamos outra classe de uma classe aninhada?
  2. O sucessor/herdado é uma classe pública regular ou também é uma classe aninhada?
  3. Finalmente, que tipo exato de classes aninhadas estamos usando em todas essas situações?
Se você responder a todas essas perguntas, haverá tantas respostas possíveis que sua cabeça vai girar :) Como você sabe, para resolver um problema complexo é preciso dividi-lo em partes mais simples. Isso é o que faremos. Vejamos cada grupo de classes aninhadas de duas perspectivas: quem pode herdar desse tipo de classe aninhada e de quem ela pode herdar. Vamos começar com classes aninhadas estáticas.

Classes aninhadas estáticas

Exemplos de herança de classes internas – 2Suas regras de herança são as mais simples. Aqui você pode fazer quase tudo que seu coração desejar. Uma classe aninhada estática pode ser herdada de:
  • aula normal
  • uma classe estática aninhada que é declarada na classe externa ou em seus ancestrais
Vamos lembrar o exemplo da palestra sobre classes aninhadas 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;
       }
   }
}
Vamos tentar alterar o código e criar uma classe estática aninhada Drawinge seu descendente - 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 você pode ver, não há problema. Podemos remover completamente a classe Drawinge torná-la uma classe pública regular em vez de uma classe estática aninhada - nada mudará.
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;
       }
   }
}
Isso está resolvido. E quais classes podem herdar de uma classe aninhada estática? Quase qualquer um! Aninhado/regular, estático/não estático - não importa. Aqui, herdamos a classe interna Boeing737Drawingda classe aninhada 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;
       }
   }
}
Você pode criar uma instância 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());

   }

}
Embora nossa classe Boeing737Drawingherde de uma classe estática, ela mesma não é estática! Portanto sempre precisará de uma instância da classe externa. Podemos tirar a classe Boeing737Drawingda classe Boeing737e torná-la apenas uma classe pública. Nada mudará - ele também pode herdar de um aninhado estático 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;

}
O único ponto importante: neste caso precisamos tornar maxPassengersCountpública a variável estática. Se permanecer privado, a classe pública normal não terá acesso a ele. Classificamos as classes estáticas! :) Agora vamos passar para as aulas internas. Como você se lembra, existem 3 tipos: classes internas simplesmente, classes locais e classes internas anônimas. Exemplos de herança de classes internas – 3Mais uma vez, vamos passar do simples ao complexo :)

Classes internas anônimas

Uma classe interna anônima não pode herdar de outra classe. Nenhuma outra classe pode herdar de uma classe anônima. Não poderia ser mais simples! :)

Aulas locais

Classes locais (caso você tenha esquecido) são declaradas dentro de um bloco de código de outra classe. Na maioria das vezes - dentro de algum método desta classe externa. É lógico que apenas outras classes locais dentro do mesmo método (ou bloco) possam herdar de uma classe local. Aqui está um exemplo:
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 {


       }

       //...code валидации номера
   }
}
Este é o código da nossa palestra sobre aulas locais. Dentro da classe validadora de número temos uma classe local PhoneNumber- número de telefone. Se para os nossos propósitos precisarmos separar dele duas entidades distintas, por exemplo, um número de telemóvel e um número de telefone fixo, só o poderemos fazer dentro do mesmo método. O motivo é simples: o escopo de uma classe local está dentro do método (bloco) onde ela é declarada. Portanto, não poderemos usá-lo externamente de alguma forma (inclusive para herança). Contudo, a própria classe local tem possibilidades mais amplas de herança! Uma classe local pode herdar de:
  1. Aula normal.
  2. Uma classe interna declarada na mesma classe que a classe local ou em seus ancestrais.
  3. De outra classe local declarada no mesmo método (bloco).
O primeiro e o terceiro pontos parecem óbvios, mas o segundo é um pouco confuso :/ Vejamos dois exemplos. Exemplo 1 – “herdando uma classe local de uma classe interna que foi declarada na mesma classe que a classe 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);
           }
       }

       //...code валидации номера
   }
}
Aqui retiramos a classe PhoneNumberdo método validatePhoneNumber()e a tornamos interna em vez de local. Isso não nos impede de herdar dele nossas 2 classes locais. Exemplo 2 – “...ou nos ancestrais desta classe.” É aqui que fica mais interessante. Podemos PhoneNumbersubir ainda mais na cadeia de herança. Vamos declarar uma classe abstrata AbstractPhoneNumberValidatorque se tornará nossa ancestral 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 você pode ver, não apenas o declaramos, mas também movemos a classe interna para ele PhoneNumber. No entanto, em sua classe descendente - PhoneNumberValidator- classes locais em métodos podem herdar 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);
           }
       }

       //...code валидации номера
   }
}
Graças à conexão por herança, as classes locais dentro de uma classe descendente “vêem” as classes internas dentro do ancestral. E por fim, vamos passar para o último grupo :)

Aulas internas

Uma classe interna pode ser herdada por outra classe interna declarada na mesma classe externa (ou sua descendente). Vejamos isso usando nosso exemplo de bicicleta da palestra sobre classes 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("Go!");
   }

   class Seat {

       public void up() {

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

       public void down() {

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

   class SportSeat extends Seat {

       //...methods
   }
}
BicycleAqui declaramos uma classe interna dentro da classe Seat- assento. Um subtipo especial de assentos de corrida foi herdado dele - SportSeat. No entanto, poderíamos criar um tipo separado de “bicicletas de corrida” e colocá-lo em uma classe 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("Сидение опущено ниже!");
       }
   }
}
Isto também é possível. A classe interna do filho ( SportBicycle.SportSeat) “vê” as classes internas do ancestral e pode herdar delas. A herança de classes internas tem uma característica muito importante! Nos dois exemplos anteriores SportSeattivemos internal. Mas e se decidirmos torná-la SportSeatuma classe pública regular, que também herda da classe 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("Сидение опущено ниже!");
   }
}
Tivemos um erro! Você consegue adivinhar com o que isso está conectado? :) É simples. Quando falamos sobre a classe interna Bicycle.Seat, mencionamos que o construtor da classe interna passa implicitamente uma referência a um objeto da classe externa. Portanto, sem criar um objeto, Bicyclevocê não pode criar um objeto Seat. E a criação SportSeat? Ele não possui o mesmo mecanismo interno para passar implicitamente uma referência a um objeto de classe externa no construtor como em Seat. Porém, sem um objeto Bicycle, assim como no caso de Seat, não podemos criar um objeto SportSeat. Portanto, só nos resta uma coisa a fazer - passar explicitamente SportSeatuma referência ao objeto para o construtor. BicycleVeja como isso é feito:
class SportSeat extends Bicycle.Seat {

   public SportSeat(Bicycle bicycle) {

       bicycle.super();
   }

   public void up() {

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

   public void down() {

       System.out.println("Сидение опущено ниже!");
   }
}
Para isso usamos uma palavra especial super(); Agora, se quisermos criar um objeto SportSeat, nada nos impedirá de fazer isso:
public class Main {

   public static void main(String[] args) {

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

   }
}
Ufa, a palestra acabou sendo bem grande :) Mas você aprendeu muitas coisas novas! Agora é a hora de resolver alguns problemas! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION