CodeGym /Cursos /JAVA 25 SELF /Classes internas (non-static inner)

Classes internas (non-static inner)

JAVA 25 SELF
Nível 16, Lição 0
Disponível

1. O que é uma classe interna (inner class)?

Em Java, uma classe pode ser declarada não apenas no nível superior (no arquivo), mas também dentro de outra classe. Essa classe é chamada de aninhada (nested class). Se tal classe for declarada sem o modificador static, ela é chamada de interna (ou non-static inner class, ou simplesmente inner class).

Uma classe interna é uma classe declarada dentro de outra classe e associada à instância dessa classe externa. Ela pode acessar todos os campos e métodos da classe externa, mesmo que sejam private. É como se o objeto tivesse um “assistente secreto” a quem tudo é permitido!

É mais ou menos como uma casa com cômodos. A casa é a classe externa, e o cômodo é a classe interna. O cômodo não pode existir sem a casa, mas tem acesso aos seus recursos: luz, aquecimento, móveis. Se a casa desaparecer, o cômodo também desaparece. As classes internas em Java funcionam exatamente assim!

Por que precisamos de classes internas?

  • Conexão lógica: quando uma classe é necessária apenas para trabalhar com outra classe e não faz sentido fora dela.
  • Encapsulamento: permite ocultar detalhes de implementação sem poluir o namespace do pacote.
  • Acesso a membros privados: a classe interna pode acessar campos e métodos privados da classe externa.
  • Compacidade: reduz a quantidade de “lixo” de classes no nível de pacote.

Sintaxe de declaração de uma classe interna

Declarar uma classe interna é muito simples: ela é declarada dentro do corpo de outra classe, sem o modificador static.

class Outer {
    // campos e métodos da classe externa

    class Inner {
        // campos e métodos da classe interna
        void printHello() {
            System.out.println("Hello from Inner!");
        }
    }
}

Aqui, Inner é uma classe interna em relação a Outer.

Figura: visualização

Outer
│
├─ campos/métodos
│
└─ Inner (classe interna)
      └─ seus campos/métodos

2. Como criar uma instância de uma classe interna

Aqui está uma das armadilhas mais comuns para iniciantes! Uma instância de classe interna está sempre associada a um objeto específico da classe externa.

Exemplo:

Outer outer = new Outer();             // criamos o objeto da classe externa
Outer.Inner inner = outer.new Inner(); // criamos a classe interna por meio do objeto externo!
inner.printHello();                    // Hello from Inner!

Tentar fazer apenas new Inner() levará a um erro de compilação, porque o Java não sabe a qual objeto da classe externa deve associar essa classe interna.

Por que é assim?

A classe interna pode acessar os campos e métodos da classe externa. Portanto, ela precisa “saber” com qual objeto da classe externa está trabalhando.

3. Exemplo de uso de classe interna

Vamos considerar um exemplo em que uma classe interna é realmente útil.

Exemplo 1: Modelo “Mochila e itens”

Suponha que temos a classe Backpack, que pode armazenar itens (Item). Mas queremos que a classe Item esteja disponível apenas dentro de Backpack, porque fora da mochila os itens não nos interessam.

public class Backpack {
    private String owner;

    public Backpack(String owner) {
        this.owner = owner;
    }

    // Classe interna — o item só pode existir dentro da mochila!
    class Item {
        private String name;

        public Item(String name) {
            this.name = name;
        }

        public void printInfo() {
            // Mágica! Conseguimos ver o campo private da classe externa
            System.out.println(owner + " tem o item: " + name);
        }
    }
}

Uso:

Backpack bp = new Backpack("Vasya");
Backpack.Item item = bp.new Item("Livro de Java");
item.printInfo(); // Vasya tem o item: Livro de Java

Observe: Item pode acessar o campo owner, mesmo que ele seja private!

Exemplo 2: Iterador para a sua própria coleção

Em Java, coleções geralmente implementam um iterador como classe interna. Vamos criar uma coleção simples com um iterador como classe interna.

public class IntList {
    private int[] data = new int[10];
    private int size = 0;

    public void add(int value) {
        data[size++] = value;
    }

    // Classe interna — iterador!
    class Iterator {
        private int index = 0;

        public boolean hasNext() {
            return index < size;
        }

        public int next() {
            return data[index++];
        }
    }
}

Uso:

IntList list = new IntList();
list.add(10);
list.add(20);

IntList.Iterator it = list.new Iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

4. Características das classes internas

Acesso aos membros da classe externa

A classe interna pode acessar qualquer campo e método da classe externa (inclusive private). Isso é conveniente, mas exige cautela: se a classe externa mudar, a interna pode de repente “quebrar”.

class Outer {
    private int secret = 42;

    class Inner {
        void showSecret() {
            System.out.println("Segredo: " + secret);
        }
    }
}

A classe interna não pode conter membros estáticos

Uma classe interna (non-static inner) não pode conter campos ou métodos estáticos, exceto constantes static final (por exemplo, public static final int MY_CONST = 123;). Isso se deve ao fato de que a classe interna está sempre “vinculada” a uma instância da classe externa.

Se você precisa declarar uma classe aninhada estática, use o modificador static (falaremos disso na próxima aula).

Visibilidade da classe interna

Uma classe interna pode ser private, protected, public ou ter visibilidade de pacote (package-private).

public class Outer {
    private class Inner { /* ... */ }
}

5. Nuances úteis

Quando usar classes internas

Classes internas não são para “embelezar”, e sim para organização lógica do código. Use-as quando:

  • A classe é necessária apenas em um lugar (por exemplo, um iterador auxiliar, um handler, parte de uma estrutura complexa).
  • A classe é fortemente acoplada à classe externa e não faz sentido fora dela.
  • Você quer ocultar detalhes de implementação de outras classes do pacote.

Não use classes internas apenas por moda! Às vezes é melhor mover a classe para o nível superior, se ela puder ser útil em outros lugares.

Classe interna e this

Dentro de uma classe interna, você pode acessar os campos e métodos da classe externa usando OuterClassName.this.

class Car {
    private String model = "Tesla";

    class Engine {
        void printModel() {
            // Acessamos explicitamente o objeto externo
            System.out.println("Modelo: " + Car.this.model);
        }
    }
}

Essa sintaxe é especialmente útil quando as classes interna e externa têm campos com os mesmos nomes.

Quando NÃO usar classes internas

Não use se:

  • A classe interna não acessa campos/métodos da classe externa
  • A classe pode ser útil em outras partes do programa
  • A classe externa se torna grande e complexa demais

Nesses casos, é melhor:

  • Tornar a classe aninhada estática (static class)
  • Mover a classe para um arquivo separado
  • Usar métodos comuns em vez de uma classe inteira

6. Erros típicos ao trabalhar com classes internas

Erro nº 1: tentativa de criar uma classe interna sem um objeto da classe externa.
Se você escrever new Inner(), o compilador emitirá o erro: "No enclosing instance of type Outer is accessible". Sempre crie a classe interna por meio do objeto da classe externa: outer.new Inner().

Erro nº 2: tentativa de declarar campos ou métodos estáticos em uma classe interna.
Uma classe interna não pode conter membros estáticos (exceto constantes). Se precisar — use uma classe aninhada estática, ou seja, declare a classe com o modificador static.

Erro nº 3: uso excessivo de classes internas.
Se a classe interna não usa campos/métodos da classe externa, provavelmente ela deveria ser static ou até mesmo extraída para fora. Não crie classes internas “por via das dúvidas”.

Erro nº 4: confusão ao acessar os campos da classe externa.
Se as classes interna e externa têm campos com os mesmos nomes, use OuterClassName.this.field para acessar explicitamente o campo da classe externa.

1
Tarefa
JAVA 25 SELF, nível 16, lição 0
Bloqueado
Controle de estoque: rotulagem de caixas 📦
Controle de estoque: rotulagem de caixas 📦
1
Tarefa
JAVA 25 SELF, nível 16, lição 0
Bloqueado
Assistente pessoal: saudação cortês 🗣️
Assistente pessoal: saudação cortês 🗣️
1
Tarefa
JAVA 25 SELF, nível 16, lição 0
Bloqueado
Biblioteca Mágica: catalogação de pergaminhos 📜
Biblioteca Mágica: catalogação de pergaminhos 📜
1
Tarefa
JAVA 25 SELF, nível 16, lição 0
Bloqueado
Casa inteligente: endereçamento dos cômodos 🏠
Casa inteligente: endereçamento dos cômodos 🏠
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION