O que �?

Saiu recentemente a nova vers�o LTS (Long Term Support) do Java. Essas vers�es s�o as mais importantes no calend�rio de lan�amentos, uma vez que possuem oito anos de suporte.

Desta vez, al�m de melhorias em geral e de performance, um novo recurso chamado sealed classes foi adicionado � linguagem. Ele impactar� nossa forma de usar a heran�a.

Por que � �til?

Heran�a � uma solu��o eficaz para resolver problemas de modelagem. Por�m, como Joshua Bloch apontou em Effective Java, se uma classe deve ser herdada ela precisa ser escrita e bem documentada para tal. Do contr�rio, o c�digo pode n�o funcionar como esperado.

Atualmente em Java a heran�a � controlada de forma bin�ria: uma classe pode ou n�o ser estendida por outras classes. Sealed classes torna esse controle mais granular, permitindo ao autor de uma superclasse descrever quais subclasses devem estend�-la.

Caracter�sticas

  • Introduz as palavras-chave sealed e permits para serem usadas com classes ou interfaces

Como utilizar: sintaxe

Para criar uma sealed class ou interface introduzimos a palavra sealed.

Por exemplo, vamos criar a rela��o de heran�a apresentada no diagrama da Figura 1.

Diagrama que representa a cria��o de heran�a
Figura 1. Diagrama que representa a cria��o de heran�a

O C�digo 1 apresenta a sealed interface Pagavel, descrevendo que apenas PagavelPorDinheiro e PagavelPorCartaoDeCredito devem estend�-la. Tanto classes quanto interfaces podem ser sealed.


  public sealed interface Pagavel 
  permits PagavelPorDinheiro, PagavelPorCartaoDeCredito {}
  
C�digo 1. Interface Pagavel

Agora, ambas as subclasses devem ser declaradas como final, como mostra o C�digo 2.


  public final class PagavelPorCartaoDeCredito implements Pagavel {}
   
  public final class PagavelPorDinheiro implements Pagavel {}
C�digo 2. Classe PagavelPorCartaoDeCredito/PagavelPorDinheiro implementando a interface Pagavel

Dessa forma, temos uma rela��o de heran�a entre as classes documentada pelo c�digo. Somente as classes PagavelPorDinheiro e PagavelPorCartaoDeCredito podem estender a interface Pagavel.

Exemplo pr�tico

Veja agora alguns exemplos pr�ticos de como criar sealed classes.

Exemplo 1 - Nested Classes

No C�digo 3 vemos como declarar uma interface como sealed. Esse c�digo n�o apresenta novidades em rela��o ao anterior, exceto que dessa vez declaramos as classes que implementam a interface, PagavelPorDinheiro e PagavelPorCartaoDeCredito, dentro da pr�pria interface.


  public sealed interface Pagavel {
    final class PagavelPorDinheiro implements Pagavel {}
    
    final class PagavelPorCartaoDeCredito implements Pagavel {}
  } 
 
C�digo 3. Declarando uma interface como sealedl

Essa sintaxe, chamada nested classes, tem sido cada vez mais comum, dada a chegada das sealed classes. Com ela, n�o precisamos colocar as subclasses de Pagavel em arquivos separados.

Exemplo 2 - Classes Abstratas

Classes abstratas tamb�m podem ser sealed. O C�digo 4 mostra como isso pode ser feito.


  public abstract sealed class Pagavel 
      permits PagavelPorCartaoDeCredito, PagavelPorDinheiro {}
 
C�digo 4. Classe abstrata

Exemplo 3 - Usando permits

No Exemplo 1 mostramos como usar nested classes para declarar, dentro da superclasse, as subclasses que a implementam ou estendem. �s vezes, isso n�o � poss�vel, por exemplo, quando a superclasse e a subclasse est�o em pacotes diferentes.

Em casos assim, declarar todas as classes no mesmo arquivo n�o � poss�vel, uma vez que em Java um pacote corresponde a uma estrutura de pastas em disco. Como alternativa, podemos usar a palavra-chave permits para descrever quais podem ser as subclasses de uma superclasse. O C�digo 5 apresenta como usar permits.


  public sealed interface Pagavel 
  permits PagavelPorDinheiro, PagavelPorCartaoDeCredito {}
  
C�digo 5. Usando permits para descrever quais podem ser as subclasses de uma superclasse

Ao usar permits devemos ter cuidado, pois se alguma subclasse for omitida na lista de classes que podem estender ou implementar a superclasse um erro ser� emitido pelo compilador.

Por exemplo, se declararmos uma classe chamada PagavelPorBoleto (C�digo 6), que n�o est� listada na cl�usula permits de Pagavel (C�digo 5), o erro "PagavelPorBoleto is not allowed in the sealed hierarchy" da Figura 2 ser� emitido pelo compilador.


public final class PagavelPorBoleto implements Pagavel {}
C�digo 6. Classe PagavelPorBoleto n�o listada em permits
Erro gerado porque PagavelPorBoleto n�o est�
listada na cl�usula permits
Figura 2. Erro gerado porque PagavelPorBoleto n�o est� listada na cl�usula permits

Exemplo 4 - instanceof

Continuando o exemplo anterior, considere que agora temos uma classe chamada PagavelPorCartaoDeCredito (C�digo 7), que tamb�m n�o � uma subclasse de Pagavel.

 
 public final class PagavelPorCartaoDeCredito {}
C�digo 7. Classe PagavelPorCartaoDeCredito n�o listada em permits

Com isso, em tempo de compila��o se pode saber que PagavelPorCartaoDeCredito e Pagavel n�o s�o compat�veis entre si. Conseguir identificar isso em tempo de compila��o modifica o comportamento do operador instanceof, que checa a compatibilidade entre um tipo e um valor.

Por exemplo, no C�digo 8 tentamos checar se PagavelPorCartaoDeCredito � uma inst�ncia de Pagavel. Antes, esse c�digo compilaria e instanceof retornaria false em tempo de execu��o. Agora, ele falhar� em tempo de compila��o, pois o compilador sabe que PagavelPorCartaoDeCredito e Pagavel s�o incompat�veis.

 
PagavelPorCartaoDeCredito pagavelPorCartaoDeCredito = new PagavelPorCartaoDeCredito();

if(pagavelPorCartaoDeCredito instanceof Pagavel) {

 System.out.println("Pag�vel por cart�o de cr�dito");

} 

PagavelPorBoleto pagavelPorBoleto = new PagavelPorBoleto();


if(pagavelPorBoleto instanceof Pagavel) {

  System.out.println("Pag�vel por boleto");

}
C�digo 8. instanceof

Com isso ser� emitido o erro Inconvertible types; cannot cast 'PagavelPorCartaoDeCredito' to 'Pagavel', uma vez que � imposs�vel que PagavelPorCartaoDeCredito seja convertida automaticamente para Pagavel, j� que todas as subclasses de Pagavel s�o conhecidas e PagavelPorCartaoDeCredito n�o � uma delas.

Exemplo 5 - Switch

O switch como express�o, que vemos no C�digo 9, ainda est� em preview, ou seja, embora possamos utiliz�-la n�o h� garantias de que ela sequer existir� em uma vers�o futura.

Com as sealed classes, ao usar switch como express�o, n�o precisamos de uma cl�usula default quando todos os subtipos s�o usados. Isso porque o compilador sabe quais s�o todas as possibilidades que podem ser usadas para uma determinada classe e, caso n�o se use todas elas, o c�digo falhar� em tempo de compila��o.



Pagavel pagavel = new PagavelPorCartaoDeCredito();

switch (pagavel) {

 case PagavelPorCartaoDeCredito c -> System.out.println("Pag�vel por cart�o de cr�dito");

 case PagavelPorDinheiro d -> System.out.println("Pag�vel por dinheiro");

}
C�digo 9. Usando switch como express�o

Nesse caso, switch funciona como uma alternativa mais concisa a instanceof. Se pagavel for do subtipo PagavelPorCartaoDeCredito o texto "Pag�vel por cart�o de cr�dito" ser� impresso. Caso contr�rio, se Pagavel for do subtipo PagavelPorDinheiro "Pag�vel por dinheiro" ser� impresso.

Conclus�o

Sealed classes, assim como diversas outras funcionalidades do Java, passaram por diversas JEPs (Java Enhacement Proposal). A que corresponde a vers�o final � a JEP 409: Sealed Classes, que pode ser estudada mais a fundo no site do OpenJDK.