CodeGym /Blogue Java /Random-PT /Exemplos de herança de classes aninhadas
John Squirrels
Nível 41
San Francisco

Exemplos de herança de classes aninhadas

Publicado no grupo Random-PT
Oi! Hoje veremos um mecanismo importante: herança em classes aninhadas. Você já pensou no que faria se precisasse fazer uma classe aninhada herdar alguma outra classe? Se não, acredite: essa situação pode ser confusa, pois são muitas nuances.
  1. Estamos fazendo uma classe aninhada herdar alguma classe? Ou estamos fazendo alguma classe herdar uma classe aninhada?
  2. A classe filha/pai é uma classe pública comum ou também é uma classe aninhada?
  3. Finalmente, que tipo de classes aninhadas usamos em todas essas situações?
Existem tantas respostas possíveis para todas essas perguntas que sua cabeça vai girar :) Como você sabe, podemos resolver um problema complexo dividindo-o em partes mais simples. Vamos fazer isso. Vamos considerar cada grupo de classes aninhadas de duas perspectivas: quem pode herdar cada tipo de classe aninhada e quem pode herdar. Vamos começar com classes aninhadas estáticas.

Classes aninhadas estáticas

Exemplos de herança de classes aninhadas - 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 herdar:
  • uma classe comum
  • uma classe aninhada estática que é declarada em uma classe externa ou em seus ancestrais
Lembre-se de um exemplo de nossa lição 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 Drawingclasse aninhada estática e 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 até retirar a Drawingclasse e torná-la uma classe pública comum em vez de uma classe aninhada estática — 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;
       }
   }
}
Nós entendemos isso. Mas quais classes podem herdar uma classe aninhada estática? Praticamente qualquer! Aninhado/não aninhado, estático/não estático — não importa. Aqui fazemos a Boeing737Drawingclasse interna herdar a Drawingclasse aninhada 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;
       }
   }
}
Você pode criar uma instância Boeing737Drawingassim:

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 Boeing737Drawingclasse herde uma classe estática, ela não é estática! Como resultado, sempre precisará de uma instância da classe externa. Podemos remover a Boeing737Drawingclasse da Boeing737classe e torná-la uma classe pública simples. Nada muda. Ele ainda pode herdar a Drawingclasse aninhada 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;
   
}
O único ponto importante é que neste caso precisamos tornar maxPassengersCountpública a variável estática. Se permanecer privado, uma classe pública comum não terá acesso a ele. Nós descobrimos classes estáticas! :) Agora vamos para as aulas internas. Eles vêm em 3 tipos: classes internas simples, classes locais e classes internas anônimas. Exemplos de herança de classes aninhadas - 3Mais uma vez, vamos do simples ao complexo :)

Classes internas anônimas

Uma classe interna anônima não pode herdar outra classe. Nenhuma outra classe pode herdar 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, isso acontece dentro de algum método da classe externa. Logicamente, apenas outras classes locais dentro do mesmo método (ou bloco de código) podem herdar 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 {
          
          
       }

       // ...number validation code
   }
}
Este é o código da nossa lição sobre classes locais. Nossa classe de validador de número tem uma PhoneNumberclasse local. Se precisarmos que represente duas entidades distintas, por exemplo, um número de celular e um número de telefone fixo, só podemos fazer isso dentro do mesmo método. A razão é simples: o escopo de uma classe local é limitado ao método (bloco de código) onde ela é declarada. Como resultado, não poderemos usá-lo externamente (inclusive para herança de classe). No entanto, as possibilidades de herança dentro da própria classe local são muito mais amplas! Uma classe local pode herdar:
  1. Uma aula comum.
  2. Uma classe interna que é declarada na mesma classe que a classe local ou em um de seus ancestrais.
  3. Outra classe local declarada no mesmo método (bloco de código).
O primeiro e o terceiro pontos parecem óbvios, mas o segundo é um pouco confuso :/ Vejamos dois exemplos. Exemplo 1 — "Fazendo uma classe local herdar uma classe interna declarada na mesma classe da 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);
           }
       }

       // ...number validation code
   }
}
Aqui removemos a PhoneNumberclasse do validatePhoneNumber()método e a tornamos uma classe interna em vez de uma classe local. Isso não nos impede de fazer nossas 2 classes locais herdá-lo. Exemplo 2 — "... ou nos ancestrais desta classe." Agora isso já é mais interessante. Podemos subir PhoneNumberainda mais na cadeia de herança. Vamos declarar uma AbstractPhoneNumberValidatorclasse abstrata, que se tornará o ancestral da nossa PhoneNumberValidatorclasse:

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ós não apenas o declaramos - também movemos a PhoneNumberclasse interna para ele. Porém, em seu descendente PhoneNumberValidator, classes locais declaradas em métodos podem herdar PhoneNumbersem nenhum 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
   }
}
Devido ao relacionamento de herança, as classes locais dentro de uma classe descendente "vêem" as classes internas dentro de um ancestral. E por fim, vamos para o último grupo :)

classes internas

Uma classe interna declarada na mesma classe externa (ou em sua descendente) pode herdar outra classe interna. Vamos explorar isso usando nosso exemplo com bicicletas da lição sobre classes 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
   }
}
Aqui declaramos a Seatclasse interna dentro da Bicycleclasse. Um tipo especial de assento de corrida, SportSeat, é herdado. Porém, poderíamos criar um tipo separado de "bicicleta de corrida" e colocá-lo em uma classe 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 também é uma opção. A classe interna do descendente ( SportBicycle.SportSeat) "vê" as classes internas do ancestral e pode herdá-las. Herdar classes internas tem uma característica muito importante! Nos dois exemplos anteriores, nossa SportSeatclasse era uma classe interna. Mas e se decidirmos criar SportSeatuma classe pública comum que simultaneamente herde a Seatclasse 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!");
   }
}
Ocorreu um erro! Você consegue adivinhar por quê? :) É tudo simples. Quando falamos sobre a Bicycle.Seatclasse interna, mencionamos que uma referência a uma instância da classe externa é passada implicitamente para o construtor da classe interna. Isso significa que você não pode criar um Seatobjeto sem criar um Bicycleobjeto. Mas e a criação de um SportSeat? Ao contrário de Seat, ele não possui esse mecanismo integrado para passar implicitamente ao construtor uma referência a uma instância da classe externa. Ainda assim, sem um Bicycleobjeto, não podemos criar um SportSeatobjeto, assim como no caso de Seat. Portanto, só nos resta uma coisa a fazer — passar explicitamente para o SportSeatconstrutor uma referência a um Bicycleobjeto. Veja como fazer:

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!");
   }
}
Chamamos o construtor da superclasse usando super(); Agora, se quisermos criar um SportSeatobjeto, 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! Esta lição foi bastante longa :) Mas você aprendeu muito! Agora é hora de resolver algumas tarefas! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION