Oi! Hoje abordaremos um tópico importante - como as classes aninhadas funcionam em Java. Java permite criar classes dentro de outra classe:

class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}
Essas classes internas são chamadas de aninhadas. Eles são divididos em 2 tipos:
  1. Classes aninhadas não estáticas. Essas também são chamadas de classes internas.
  2. Classes aninhadas estáticas.
Por sua vez, as classes internas possuem duas subcategorias distintas. Além de uma classe interna ser simplesmente uma classe interna, ela também pode ser:
  • uma classe local
  • uma aula anônima
Confuso? :) Tudo bem. Aqui está um diagrama para maior clareza. Volte a ele durante a aula se de repente você se sentir confuso! Classes internas aninhadas - 2Na lição de hoje, discutiremos classes internas (também conhecidas como classes aninhadas não estáticas). Eles são especialmente destacados no diagrama geral para que você não se perca :) Vamos começar com a pergunta óbvia: por que eles são chamados de classes "interiores"? A resposta é bem simples: porque são criados dentro de outras classes. Aqui está um exemplo:

public class Bicycle {

   private String model;
   private int weight;

   public Bicycle(String model, int weight) {
       this.model = model;
       this.weight = weight;
   }
  
   public void start() {
       System.out.println("Let's go!");
   }

   public class Handlebar {

       public void right() {
           System.out.println("Steer right!");
       }

       public void left() {

           System.out.println("Steer left!");
       }
   }

   public class Seat {
      
       public void up() {

           System.out.println("Seat up!");
       }
      
       public void down() {

           System.out.println("Seat down!");
       }
   }
}
Aqui temos a Bicycleaula. Possui 2 campos e 1 método: start(). Classes internas aninhadas - 3Difere de uma classe comum porque contém duas classes: Handlebare Seat. Seu código é escrito dentro da Bicycleclasse. Essas são classes completas: como você pode ver, cada uma delas tem seus próprios métodos. Neste ponto, você pode ter uma pergunta: por que diabos colocaríamos uma classe dentro da outra? Por que torná-los classes internas? Bem, suponha que precisamos de classes separadas para os conceitos de guidão e assento em nosso programa. Claro, não é necessário para nós torná-los aninhados! Podemos fazer aulas normais. Por exemplo, assim:

public class Handlebar {
   public void right() {
       System.out.println("Steer right!");
   }

   public void left() {

       System.out.println("Steer left");
   }
}

public class Seat {

   public void up() {

       System.out.println("Seat up!");
   }

   public void down() {

       System.out.println("Seat down!");
   }
}
Muito boa pergunta! Claro, não estamos limitados pela tecnologia. Fazer isso é certamente uma opção. Aqui, o importante é mais o desenho correto das aulas do ponto de vista de um programa específico e sua finalidade. As classes internas são para separar uma entidade que está inextricavelmente conectada a outra entidade. Guidão, assentos e pedais são componentes de uma bicicleta. Separados da bicicleta, eles não fazem muito sentido. Se fizéssemos todos esses conceitos de classes públicas separadas, teríamos o código assim em nosso programa:

public class Main {

   public static void main(String[] args) {
       Handlebar handlebar = new Handlebar();
       handlebar.right();
   }
}
Hmm... O significado desse código é até difícil de explicar. Temos um guidão vago (por que é necessário? Não faço ideia, para ser honesto). E essa maçaneta vira para a direita... sozinha, sem uma bicicleta... por algum motivo. Ao separar o conceito de guidão do conceito de bicicleta, perdemos alguma lógica em nosso programa. Usando uma classe interna, o código parece muito diferente:

public class Main {

   public static void main(String[] args) {

       Bicycle peugeot = new Bicycle("Peugeot", 120);
       Bicycle.Handlebar handlebar = peugeot.new Handlebar();
       Bicycle.Seat seat = peugeot.new Seat();

       seat.up();
       peugeot.start();
       handlebar.left();
       handlebar.right();
   }
}
Saída do console:

Seat up! 
Let's go! 
Steer left! 
Steer right!
Agora o que vemos de repente faz sentido! :) Criamos um objeto bicicleta. Criamos dois "subobjetos" de bicicleta — um guidão e um assento. Levantamos o assento para maior conforto e lá fomos nós: pedalando e virando conforme a necessidade! :) Os métodos que precisamos são chamados nos objetos apropriados. É tudo simples e conveniente. Neste exemplo, separar o guidão e o assento melhora o encapsulamento (ocultamos os dados sobre as peças da bicicleta dentro da classe relevante) e nos permite criar uma abstração mais detalhada. Agora vamos ver uma situação diferente. Suponha que queremos criar um programa que simule uma loja de bicicletas e peças de reposição para bicicletas. Classes internas aninhadas - 4Nesta situação, nossa solução anterior não funcionará. Em uma loja de bicicletas, cada peça individual da bicicleta faz sentido mesmo quando separada de uma bicicleta. Por exemplo, precisaremos de métodos como "vender pedais a um cliente", "comprar um novo assento", etc. Seria um erro usar classes internas aqui - cada peça individual de bicicleta em nosso novo programa tem um significado que depende próprio: pode ser separado do conceito de bicicleta. É exatamente nisso que você precisa prestar atenção se estiver se perguntando se deve usar classes internas ou organizar todas as entidades como classes separadas. A programação orientada a objetos é boa porque facilita a modelagem de entidades do mundo real. Esse pode ser seu princípio orientador ao decidir se deve usar classes internas. Em uma loja de verdade, as peças sobressalentes são separadas das bicicletas - tudo bem. Isso significa que também está tudo bem ao projetar um programa. Ok, descobrimos a "filosofia" :) Agora vamos nos familiarizar com importantes recursos "técnicos" das classes internas. Aqui está o que você definitivamente precisa lembrar e entender:
  1. Um objeto de uma classe interna não pode existir sem um objeto de uma classe externa.

    Isso faz sentido: é por isso que criamos as classes internas Seate Handlebarinternas em nosso programa — para não acabarmos com guidões e assentos órfãos.

    Este código não compila:

    
    public static void main(String[] args) {
    
       Handlebar handlebar = new Handlebar();
    }
    

    Outra característica importante decorre disso:

  2. Um objeto de uma classe interna tem acesso às variáveis ​​da classe externa.

    Por exemplo, vamos adicionar uma int seatPostDiametervariável (representando o diâmetro do canote) à nossa Bicycleclasse.

    Então, na Seatclasse interna, podemos criar um displaySeatProperties()método que exibe as propriedades do assento:

    
    public class Bicycle {
    
       private String model;
       private int weight;
    
       private int seatPostDiameter;
    
       public Bicycle(String model, int weight, int seatPostDiameter) {
           this.model = model;
           this.weight = weight;
           this.seatPostDiameter = seatPostDiameter;
    
       }
    
       public void start() {
           System.out.println("Let's go!");
       }
    
       public class Seat {
    
           public void up() {
    
               System.out.println("Seat up!");
           }
    
           public void down() {
    
               System.out.println("Seat down!");
           }
    
           public void displaySeatProperties() {
    
               System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter);
           }
       }
    }
    

    E agora podemos exibir essas informações em nosso programa:

    
    public class Main {
    
       public static void main(String[] args) {
    
           Bicycle bicycle = new Bicycle("Peugeot", 120, 40);
           Bicycle.Seat seat = bicycle.new Seat();
    
           seat.displaySeatProperties();
       }
    }
    

    Saída do console:

    
    Seat properties: seatpost diameter = 40
    

    Observação:a nova variável é declarada com o modificador de acesso mais estrito ( private). E ainda a turma interna tem acesso!

  3. Um objeto de uma classe interna não pode ser criado em um método estático de uma classe externa.

    Isso é explicado pelas características específicas de como as classes internas são organizadas. Uma classe interna pode ter construtores com parâmetros ou apenas o construtor padrão. Mas independentemente disso, quando criamos um objeto de uma classe interna, uma referência ao objeto da classe externa é passada de forma invisível para o objeto criado da classe interna. Afinal, a presença de tal referência de objeto é um requisito absoluto. Caso contrário, não poderemos criar objetos da classe interna.

    Mas se um método da classe externa for estático, talvez não tenhamos um objeto da classe externa! E isso seria uma violação da lógica de funcionamento de uma classe interna. Nesta situação, o compilador irá gerar um erro:

    
    public static Seat createSeat() {
      
       // Bicycle.this cannot be referenced from a static context
       return new Seat();
    }
    
  4. Uma classe interna não pode conter variáveis ​​e métodos estáticos.

    A lógica é a mesma: métodos e variáveis ​​estáticos podem existir e ser chamados ou referenciados mesmo na ausência de um objeto.

    Mas sem um objeto da classe externa, não teremos acesso à classe interna.

    Uma clara contradição! É por isso que variáveis ​​e métodos estáticos não são permitidos em classes internas.

    O compilador irá gerar um erro se você tentar criá-los:

    
    public class Bicycle {
    
       private int weight;
    
    
       public class Seat {
          
           // An inner class cannot have static declarations
           public static void displaySeatProperties() {
    
               System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter);
           }
       }
    }
    
  5. Ao criar um objeto de uma classe interna, seu modificador de acesso é importante.

    Uma classe interna pode ser marcada com os modificadores de acesso padrão: public, private, protectede package private.

    Por que isso importa?

    Isso afeta onde podemos criar instâncias da classe interna em nosso programa.

    Se nossa Seatclasse for declarada como public, podemos criar Seatobjetos em qualquer outra classe. O único requisito é que um objeto da classe externa também exista.

    A propósito, já fizemos isso aqui:

    
    public class Main {
    
       public static void main(String[] args) {
    
           Bicycle peugeot = new Bicycle("Peugeot", 120);
           Bicycle.Handlebar handlebar = peugeot.new Handlebar();
           Bicycle.Seat seat = peugeot.new Seat();
    
           seat.up();
           peugeot.start();
           handlebar.left();
           handlebar.right();
       }
    }
    

    Obtivemos facilmente acesso à Handlebarclasse interna da Mainclasse.

    Se declararmos a classe interna como private, poderemos criar objetos apenas dentro da classe externa.

    Não podemos mais criar um Seatobjeto "do lado de fora":

    
    private class Seat {
    
       // Methods
    }
    
    public class Main {
    
       public static void main(String[] args) {
    
           Bicycle bicycle = new Bicycle("Peugeot", 120, 40);
    
           // Bicycle.Seat has private access in Bicycle
           Bicycle.Seat seat = bicycle.new Seat();
       }
    }
    

    Você provavelmente já entendeu a lógica :)

  6. Modificadores de acesso para classes internas funcionam da mesma forma que para variáveis ​​comuns.

    O protectedmodificador fornece acesso a uma variável de instância em subclasses e classes que estão no mesmo pacote.

    protectedtambém funciona para classes internas. Podemos criar protectedobjetos da classe interna:

    • na classe externa;
    • em suas subclasses;
    • em classes que estão no mesmo pacote.

    Se a classe interna não tiver um modificador de acesso ( package private), os objetos da classe interna podem ser criados:

    • na classe externa;
    • em classes que estão no mesmo pacote.

    Você está familiarizado com os modificadores há muito tempo, então não há problemas aqui.

Por enquanto é só :) Mas não relaxe! As classes internas são um tópico bastante extenso que continuaremos a explorar na próxima lição. Agora você pode refrescar a memória da aula do nosso curso sobre aulas internas . E da próxima vez, vamos falar sobre classes aninhadas estáticas.