W3docs

Classes Aninhadas em Java

Classes aninhadas em Java: classes aninhadas estáticas, classes internas, classes locais e classes anônimas.

Java permite declarar uma classe dentro de outra classe. O termo geral é classe aninhada, e Java oferece quatro variantes, divididas por onde vivem e se carregam uma referência a uma instância envolvente:

VarianteOnde é declaradaCarrega this externo?Capítulo
Classe aninhada estáticaDentro do corpo de uma classe, com staticNãoeste capítulo
Classe internaDentro do corpo de uma classe, sem staticSimclasses internas
Classe localDentro do corpo de um métodoSim (se método não estático)classes locais
Classe anônimaUma subclasse/implementação criada no localSim (se contexto não estático)classes anônimas

O motivo para escolher uma classe aninhada em vez de uma de nível superior separada é o escopo — a classe aninhada só é útil no contexto da classe que a envolve e não deve ser exposta ao restante do código. Este capítulo é a visão geral; cada tipo recebe um tratamento mais aprofundado nos capítulos seguintes.

Por que aninhar classes?

Três motivações principais:

  • Agrupamento lógico. Um Map.Entry só faz sentido dentro de um Map. Aninhá-lo torna esse relacionamento óbvio no código.
  • Encapsulamento. Uma classe aninhada pode ser definida como private, de modo que nada fora da classe envolvente possa referenciá-la.
  • Closures sobre o estado envolvente. Uma classe interna/local/anônima pode ler os campos da instância envolvente e as variáveis locais de método, que é a base dos manipuladores de eventos, iteradores e muitos padrões de adaptadores pequenos.

Se nenhum desses casos se aplica, escreva uma classe de nível superior.

Classes aninhadas estáticas

Uma classe marcada com static dentro de outra classe é uma classe aninhada estática:

public class Outer {
  static class Inner {
    void hi() { System.out.println("hi"); }
  }
}

Outer.Inner i = new Outer.Inner();    // instantiate directly
i.hi();

Uma classe aninhada estática é apenas uma classe de nível superior que por acaso vive dentro do namespace de Outer. Ela não tem referência implícita a uma instância de Outer — você pode usá-la sem nunca criar uma. A única diferença em relação a uma classe de nível superior é o escopo: Outer.Inner é o nome qualificado.

Esta é a variante que você já viu em todas as Partes 5 e 6 — todo static class Foo {} em um exemplo RunnableJava é uma dessas. O motivo pelo qual usamos static era para poder instanciá-las a partir de um main static sem precisar de uma instância de Outer.

Classes internas (não estáticas aninhadas)

Remova o static e você obtém uma classe interna. A instância da classe interna está vinculada a uma instância da classe externa, carregando uma referência implícita a ela:

public class Outer {
  int x = 1;
  class Inner {                       // no static
    int get() { return x; }           // reads Outer's x through the implicit reference
  }
}

Outer       o = new Outer();
Outer.Inner i = o.new Inner();        // unusual syntax — bind to o
System.out.println(i.get());           // 1

A sintaxe peculiar o.new Inner() é como você cria uma instância de classe interna vinculada a uma instância externa específica. Classes internas não podem ter membros static (regra mais antiga; relaxada no Java 16+ para permitir membros static em classes internas). Abordado completamente em classes internas.

Classes locais

Uma classe declarada dentro do corpo de um método é local — com escopo restrito a esse método:

public void run() {
  class Step { int n; Step(int n) { this.n = n; } }     // visible only in run()

  Step s = new Step(5);
}

Útil para auxiliares pequenos que não merecem uma classe de nível superior ou mesmo uma aninhada no nível de classe. Elas têm acesso às variáveis final (ou efetivamente finais) do método envolvente. O capítulo de classes locais conta a história completa.

Classes anônimas

Uma classe anônima é uma subclasse ou implementação de interface de uso único definida inline no ponto de uso:

Runnable r = new Runnable() {
  @Override
  public void run() { System.out.println("hi"); }
};

Essa é uma única expressão que (1) define uma nova classe implementando Runnable, (2) cria uma instância dela, (3) atribui-a a r. A classe não tem nome — ela existe apenas aqui. Quase todos os casos de uso de classes anônimas foram substituídos por lambdas no Java moderno, mas ainda são válidas e ocasionalmente úteis. Veja classes anônimas.

Escolhendo o tipo correto

Uma árvore de decisão rápida:

  1. A classe aninhada precisa de uma referência a uma instância externa? Se não → classe aninhada estática. Se sim → alguma variante não estática.
  2. Ela é usada dentro de um único método? Se sim → local ou anônima. Se não → interna.
  3. É uma subclasse/implementação de interface de uso único com um ou dois métodos? Se sim → lambda (preferido) ou classe anônima (legado).

Classes aninhadas estáticas são de longe as mais comuns. Classes internas aparecem em adaptadores no estilo iterador. Classes locais e anônimas são mais raras no código moderno — lambdas cobrem a maioria de seus casos de uso.

Nomenclatura e acesso

Classes aninhadas podem ter qualquer modificador de acesso — public, private, protected, package-private — e as regras do modificador são as mesmas que para membros de nível superior. Map.Entry é public; um private static class Node dentro de uma implementação de LinkedList é invisível para o mundo externo.

Classes aninhadas compiladas recebem nomes separados por $ nos arquivos .class: Outer$Inner, Outer$1. Você verá esses nomes em rastreamentos de pilha e depuradores ocasionalmente.

Um exemplo prático

java— editable, runs on the server

O que vem a seguir

Os próximos três capítulos aprofundam cada um dos tipos não estáticos. Primeiro: classes internas, o mais geral — uma classe vinculada a uma instância envolvente.

Prática

Prática
Qual tipo de classe aninhada NÃO carrega uma referência implícita a uma instância da classe envolvente?
Qual tipo de classe aninhada NÃO carrega uma referência implícita a uma instância da classe envolvente?
Was this page helpful?