W3docs

Classes Seladas em Java

Restrinja quais classes podem estender ou implementar um tipo em Java usando classes seladas e a cláusula permits.

Uma classe ou interface sealed restringe quem pode estendê-la ou implementá-la a uma lista fixa e nomeada de subtipos. final diz "ninguém pode me estender." sealed diz "apenas essas classes específicas podem." Isso fornece uma hierarquia fechada — o compilador conhece toda a família antecipadamente, o que viabiliza switch exaustivo e modelagem disciplinada de "um entre N" formatos.

Sem o selamento, uma abstract class Shape fica aberta para o mundo: qualquer pessoa com acesso ao tipo pode escrever class Banana extends Shape. Com sealed, o autor de Shape declara exatamente quais subtipos existem, e adicionar um requer editar o pai.

A sintaxe básica

Uma classe selada lista seus subtipos permitidos com permits:

public sealed class Shape
    permits Circle, Square, Triangle {
  // common state and behavior
}

Cada subtipo permitido deve declarar o que faz com o selamento — uma das opções: final, sealed (com sua própria lista de permits) ou non-sealed:

public final     class Circle   extends Shape { /* leaf */ }
public final     class Square   extends Shape { /* leaf */ }
public non-sealed class Triangle extends Shape { /* re-opens the door */ }
  • final — sem mais subclasses; este é um nó folha na hierarquia.
  • sealed — estende o mesmo modelo; possui sua própria lista permits.
  • non-sealed — volta atrás; qualquer um pode agora estender Triangle. Útil quando se deseja uma família de nível superior fechada com um ramo aberto.

Um tipo selado sem modificador em um subtipo é um erro de compilação — o compilador força você a escolher.

Interfaces seladas

As interfaces seguem as mesmas regras e geralmente são a escolha mais natural para modelar famílias de casos:

public sealed interface Result<T>
    permits Success, Failure {}

public record Success<T>(T value) implements Result<T> {}
public record Failure<T>(String message) implements Result<T> {}

Combinado com records, você obtém algo próximo ao "sum type" ou "tagged union" de linguagens funcionais — uma lista fechada de alternativas nomeadas, cada uma com seus próprios dados.

Mesmo módulo, mesmo pacote (ou permits explícito)

Os subtipos permitidos precisam ser acessíveis à declaração selada em tempo de compilação. A configuração mais simples é colocar a classe selada e seus subtipos permitidos no mesmo arquivo-fonte — então você pode até omitir permits, pois o compilador o infere:

public sealed interface Tree {
  record Leaf(int value)               implements Tree {}
  record Node(Tree left, Tree right)   implements Tree {}
}

Se estiverem em arquivos separados, devem estar no mesmo pacote (ou, em um projeto modular, no mesmo módulo), e a cláusula permits é obrigatória.

A recompensa: switch exaustivo

O compilador conhece todos os subtipos possíveis de um tipo selado. Isso permite que switch imponha exaustividade sem um default:

double area(Shape s) {
  return switch (s) {
    case Circle   c -> Math.PI * c.radius() * c.radius();
    case Square   q -> q.side() * q.side();
    case Triangle t -> 0.5 * t.base() * t.height();
  };
}

Se você adicionar posteriormente um Hexagon permitido, esse switch para de compilar em todo lugar onde aparece, até que você trate o novo caso. Essa é exatamente a rede de segurança que default destruiria silenciosamente.

Regras que o compilador impõe

Algumas restrições são fáceis de ignorar por descuido:

  • Todo subtipo permitido deve estender ou implementar diretamente o tipo selado. Você não pode listar um neto em permits — apenas subtipos imediatos.
  • Cada subtipo permitido deve escolher um modificador: final, sealed ou non-sealed. Esquecer um é erro de compilação.
  • Os tipos permitidos devem ser encontráveis em tempo de compilação — mesmo arquivo, mesmo pacote ou o mesmo módulo nomeado. Um tipo selado não pode permitir uma classe em um módulo não relacionado.
  • Records são implicitamente final, portanto um record pode ser um subtipo permitido sem escrever final você mesmo. É por isso que sealed-interface-mais-records é uma combinação tão limpa.

O modificador non-sealed é a saída deliberada. Use-o quando a maior parte de uma hierarquia deve permanecer fechada, mas um ramo é um ponto de extensão intencional:

public sealed interface Vehicle permits Car, Truck, CustomBuild {}

public record Car(int doors)   implements Vehicle {}
public record Truck(double tons) implements Vehicle {}

// Re-opened: third parties may extend this branch.
public non-sealed interface CustomBuild extends Vehicle {}

Como CustomBuild é non-sealed, um switch sobre Vehicle ainda precisa de um fallback para ele — o compilador não pode mais provar que esse ramo é exaustivo.

Quando selar

Use o selamento quando a abstração é genuinamente um conjunto fechado de casos:

  • Nós de AST ou expressão (Literal, Add, Multiply...).
  • Resultados de domínio que são "sucesso ou uma destas falhas."
  • Hierarquias de comando/evento onde todo consumidor downstream precisa tratar cada caso.

Não sele tipos que são pontos de extensão — interfaces de plugin, hooks de framework, qualquer coisa que os chamadores devem subtipar. Selar esses tipos derrota seu propósito.

Um exemplo completo

java— editable, runs on the server

O que vem a seguir

O selamento limita a lista de subtipos. O próximo capítulo trata de perguntar, em tempo de execução, qual subtipo você realmente tem — o operador instanceof e sua forma moderna de correspondência de padrões, que é o que torna o switch acima tão conciso. Continue em Operador instanceof em Java.

Prática

Prática
O que o fato de declarar uma classe como `sealed` realiza?
O que o fato de declarar uma classe como `sealed` realiza?
Was this page helpful?