Classes Internas em Java
Defina classes internas não estáticas em Java que mantêm uma referência implícita a uma instância da classe envolvente.
Uma classe interna é uma classe aninhada não estática — declarada dentro de outra classe sem o modificador static. A característica fundamental: cada instância de uma classe interna está vinculada a uma instância da classe envolvente e carrega uma referência implícita a ela. De dentro da classe interna, você pode ler e escrever os campos da instância externa e chamar seus métodos como se fossem seus.
Isso torna as classes internas a ferramenta certa quando uma classe secundária pequena precisa participar intimamente do estado de outra classe — mais famosamente, iteradores que percorrem os dados internos de seu contêiner.
Esta página aborda como declarar uma classe interna, como criar uma (você sempre precisa de uma instância externa), o qualificador Outer.this, o caso de uso canônico de iteradores, o problema de vazamento de memória que vem com a referência implícita e como decidir entre uma classe interna e uma classe aninhada static.
Declarando uma classe interna
Remova static de uma declaração de classe aninhada:
public class Outer {
private int x = 1;
class Inner { // no static — inner class
int get() { return x; } // reads Outer's x directly
}
}Inner não tem campos próprios e ainda assim get() retorna 1. O x simples resolve para Outer.this.x por meio da referência implícita.
Criando uma instância
Como cada instância de classe interna está vinculada a uma instância externa, você precisa de uma instância externa para criar uma instância interna. Existem duas maneiras:
Outer o = new Outer();
Outer.Inner i = o.new Inner(); // bind explicitly to o…ou de dentro de um método não estático de Outer:
public class Outer {
void demo() {
Inner i = new Inner(); // implicitly bound to this
}
}A sintaxe o.new Inner() é rara e surpreendente — a maioria dos códigos cria instâncias de classes internas de dentro dos próprios métodos da classe externa, onde o vínculo é implícito.
Outer.this — acessando além de uma colisão de nomes
Quando a classe interna declara um campo com o mesmo nome de um na classe externa, o interno o sombreia. Para acessar o externo, qualifique-o com Outer.this:
public class Outer {
int x = 1;
class Inner {
int x = 2;
void demo() {
System.out.println(x); // 2 — Inner's x
System.out.println(this.x); // 2 — Inner's x
System.out.println(Outer.this.x); // 1 — Outer's x
}
}
}this em uma classe interna refere-se à instância interna; Outer.this refere-se à instância externa envolvente.
O caso de uso canônico — iteradores
O motivo clássico para usar uma classe interna é implementar um iterador sobre os internos privados de um contêiner:
public class IntList {
private int[] data;
private int size;
// ... constructors, add, ...
public Iterator<Integer> iterator() {
return new InnerIterator();
}
private class InnerIterator implements Iterator<Integer> {
private int i = 0;
public boolean hasNext() { return i < size; }
public Integer next() { return data[i++]; }
}
}InnerIterator acessa data e size diretamente por meio da referência externa implícita. Nenhum setter, nenhum acessor necessário — a classe interna faz parte da implementação de IntList.
Note private class InnerIterator. De fora, os chamadores veem a interface pública Iterator<Integer>; eles não sabem que InnerIterator existe. Esse é o benefício de encapsulamento do aninhamento.
Classes internas mantêm uma referência — e mantêm o externo vivo
Um problema sutil. Enquanto uma instância de classe interna for acessível, a JVM não pode coletar lixo da instância externa à qual ela está vinculada. Retornar uma instância de classe interna para código de longa vida (por exemplo, instalando um listener em algum lugar) pode manter grafos de objetos inteiros vivos por mais tempo do que o esperado.
public class Window {
Listener installListener() {
return new Listener(); // returned to whoever calls this
}
class Listener { ... } // holds a Window reference forever
}Se installListener() for armazenado em um registro estático, o Window vive até que o registro seja limpo. A correção geralmente é tornar a classe aninhada static e passar os dados necessários explicitamente, quebrando a referência implícita.
Esta é a razão mais comum pela qual as equipes optam por classes aninhadas static por padrão e só mudam para classes internas quando precisam especificamente do vínculo.
Membros estáticos em classes internas
Durante a maior parte da história do Java, classes internas não podiam declarar membros static (campos static, métodos ou classes aninhadas). O Java 16 relaxou isso — classes internas agora podem ter membros estáticos. Mesmo assim, se você se encontrar querendo usá-los, isso é geralmente um sinal de que a classe quer ser static em si mesma.
static vs interna — a escolha
Uma regra útil: torne-a static a menos que você precise ativamente da referência externa.
- Classe aninhada estática: mais simples, mais leve, não mantém o externo vivo.
- Classe interna: açúcar conveniente sobre o acesso
outerInstance.fieldquando o relacionamento é genuíno.
Se a única coisa que você faz é Outer.this.field, basta receber um Outer como parâmetro do construtor e tornar a classe estática.
Um exemplo prático
O que vem a seguir
O próximo tipo de classe aninhada é a versão inline e de uso único: classes anônimas, usadas para situações rápidas de subclassificação e instanciação em uma única expressão. Continue para classes anônimas.