Java 17: confira as novidades da nova versão do Java

Neste artigo você vai ver:

Em setembro houve uma boa notícia para profissionais de desenvolvimento Java e clientes que usam Java sob uma licença comercial: o Oracle JDK estaria disponível gratuitamente a partir do JDK 17. Em outras palavras, chegou a vez e a hora do Java 17.

Donald Smith, Diretor Sênior de Gerenciamento de Produtos, deu essa notícia por meio de uma postagem no Oracle Blog em 14 de setembro de 2021, apresentando a licença livre do Java. Nos próximos parágrafos a gente traz os principais pontos deste post e muito mais. ? 

Um breve resumo do post de lançamento do Java 17

Esta licença gratuita inclui o JDK e as atualizações de segurança trimestrais também para uso comercial e de produção. A nova licença é a licença “Oracle No-Fee Terms and Conditions” (NFTC) e permite o uso gratuito para todos os usuários, mesmo para uso comercial e de produção. A redistribuição é permitida, desde que não seja cobrada uma taxa.

Profissionais de desenvolvimento e organizações agora podem facilmente baixar, usar, compartilhar e redistribuir o Oracle JDK de forma simples. A Oracle fornecerá essas versões e atualizações gratuitas começando com o Oracle JDK 17 e continuará por um ano inteiro após a próxima versão do LTS (Suporte de Longo Prazo).

Versões anteriores não são afetadas por essa alteração. A Oracle continuará fornecendo versões do Oracle OpenJDK sob a GPL nas mesmas versões e cronograma que tem desde o Java 9. 

Em setembro de 2017, a Oracle anunciou planos para distribuir o JDK sob a GPL como “Oracle OpenJDK”, bem como o Oracle JDK sob a licença “Oracle Technology Network” (OTN).

Fornecer construções Oracle OpenJDK sob a GPL foi altamente bem-vindo, mas o feedback de profissionais de desenvolvimento, academia e empresas era que eles também queriam o Oracle JDK, visto como confiável e sólido, sob uma licença livre, equivalente à GPL. 

Você ainda pode seguir a cadência de lançamento JDK semestral para se beneficiar do acesso mais rápido a novos recursos, melhorias de desempenho e outros aprimoramentos, é claro. Mas agora você também tem o tempo necessário para migrar de um Oracle JDK LTS para o próximo, se esse é o modelo que você prefere.

A assinatura Oracle Java SE continua a fornecer recursos de valor agregado, como o Java Management Service, Advanced Management Console e GraalVM Enterprise sem custo incremental.

O que você precisa saber sobre o Java 17

O Java 17 oferece milhares de atualizações de desempenho, estabilidade e segurança, bem como 14 JEPs (JDK Enhancement Proposals), que melhoram ainda mais a linguagem e a plataforma Java para trazer mais produtividade para profissionais de desenvolvimento.

O Java 17 é o mais recente lançamento de Suporte de Longo Prazo (LTS) sob a cadência de lançamento de seis meses do Java e é fruto de uma ampla colaboração dos times de engenharia da Oracle e outros membros da comunidade mundial de profissionais de desenvolvimento Java, por meio da OpenJDK Community e do Java Community Process (JCP). 

Desde o lançamento do JDK 11 LTS há três anos, mais de 70 JEPs foram implementados. 

Sealed Classes (JEP 409

Este é provavelmente um dos mais notáveis e importantes aprimoramentos em Java 17. As Sealed Classes foram propostas pelo JEP 360 e entregues no JDK 15 como um recurso de pré-visualização. Elas foram propostas novamente, com alguns refinamentos, e entregues no JDK 16 como, ainda, um recurso de pré-visualização. Agora no JDK 17, as Sealed Classes estão sendo finalizadas sem alterações em relação ao JDK 16.

Mas o que é uma Sealed Class

“Selar” uma classe ou interface significa definir um contrato claro sobre quais outras classes podem se estender (no caso de selar uma classe) ou implementar (no caso de selar uma interface) o tipo selado. 

A propósito, posso me referir à sealed classes ou sealed interfaces, intercambiavelmente como tipos selados, porque você pode selar uma classe (tanto, normal e abstrata), e você pode selar uma interface. Além disso, todo mundo sabe, que classes e interfaces são ambas classes (sabemos, né?)

Você pode definir uma Sealed Class da seguinte forma: 

Trecho de Código 1 – Sealed Class

package com.example.geometry;

public abstract sealed class Forma
    permits Circulo, Retangulo, Quadraddo { ... }

public final class Circulo extends Forma { ... }

public sealed class Retangulo extends Forma 
    permits RetanguloTransparente, RetanguloPreenchido { ... }

public final class RetanguloTransparente extends Retangulo { ... }
public final class RetanguloPreenchido extends Retangulo { ... }

public final class Quadrado extends Forma { ... }

Isto pode ser lido como: “A classe Forma só permite que as classes Circulo, Retangulo e Quadrado a estendam, e nenhuma outra classe pode estender a classe Forma. Da mesma forma, a classe Retangulo somente permite que RetanguloTranparente e RetanguloPreenchido a estendam, nenhuma outra classe poderá estender a classe Retangulo.”

2.1 Restrições e notas importantes sobre Sealed Classes:

  • permits é uma nova palavra-chave reservada (introduzida no JDK 15), utilizada para listar classes que deseja-se permitir que sejam subtipos de sua classe selada (Círculo e Quadrado são apenas tipos permitidos para estender Forma no exemplo acima);
  • o tipo sealed deve ter um ou mais subtipos, ou seja, deve declarar pelo menos um tipo que permite ser o subtipo. Deixa eu acrescentar mais um detalhe: sempre que você selar um tipo, permitindo que apenas subtipos específicos se estendam, o que quer que você permita ser um subtipo do seu tipo selado, este deve ser o subtipo direto do seu tipo selado (o subtipo deve estar estendendo diretamente sua classe selada ou implementando diretamente sua interface selada);
  • Quaisquer cláusulas extends ou implements que sua sealed class possa declarar, devem ir antes da cláusula de permits;
  • tipos selados e seus tipos permitidos devem estar no mesmo módulo ou no mesmo pacote;
  • o modificador no-access pode preceder ou seguir a palavra-chave class;

Regra importante: O tipo selado só pode ser subtipado (subclassificado ou implementado) pelos tipos sealed, final ou non-sealed.

Portanto, se você selar o tipo Forma e declarar que ele permite Quadrado, então

Quadrado deve cumprir um destes três pontos:

  1. A classe Quadrado deve ser final, o que significa que você não poderá estendê-la;
  2. A classe Quadrado também pode ser sealed, ou seja, deve permitir que alguns tipos sejam extensores/implementadores da classe Quadrado. Isso, por sua vez, significa que as mesmas regras se aplicam a essas classes netas;
  3. A classe Quadrado deve ser non-sealed, essa é outra palavra-chave nova no Java 17 (disponível como um recurso de visualização desde o Java 15).

Dos objetivos das Sealed Classes

Isso é praticamente tudo que você precisa saber sobre sealed classes. Você até pode se perguntar: “Por que nós precisamos disso, afinal?”. Podemos nos referir aos objetivos oficiais do JEP e ver, que as sealed classes:

  • Permitem que o autor de uma classe ou interface controle qual código é responsável por implementá-lo.
  • Fornecem uma maneira mais declarativa do que os modificadores de acesso para restringir o uso de uma superclasse.
  • Apoiem direções futuras em pattern matching, fornecendo uma base para a análise exaustiva dos padrões.

Basicamente, é mais um nível de granularidade de acesso para tipos em Java. É uma maneira fina de controlar o nível de acesso na hierarquia de herança de nossas classes. 

3. Pattern Matching para Switch (Preview Feature) (JEP 406)

Em 2020, o Java 14 introduziu o Pattern Matching para o instanceof como recurso de pré-visualização (JEP 305). O JEP 305 foi então substituído pelo JEP 375, uma segunda prévia, que havia sido proposta para re-visualizar o recurso, novamente, no JDK 15, sem alterações em relação ao JEP 305, no Java 14, e a razão era simples — observar esse recurso mais profundamente, além de tentar obter e coletar feedback adicional sobre este recurso.

Finalmente, o JEP 394 finalizou e padronizou o Pattern Matching, por exemplo, com algumas pequenas atualizações e foi entregue no Java 16. O Java 17 introduz uma abordagem mais recente no uso do Pattern Matching e propõe usá-lo com a instrução do Switch, como um recurso de pré-visualização, por enquanto.

Entendendo as peculiaridades do Switch 

O problema com o Switch é que ele é muito limitado quando se trata de tipos. Você só pode utilizar o Switch com valores de poucos tipos – numéricos, enum e string, a saber. Além disso, você só pode testar a igualdade exata entre constantes.

Podemos querer usar padrões para testar uma mesma variável, em relação ao número de possibilidades, realizando uma ação específica em cada uma, mas como o Switch existente não suporta isso, acabamos com uma cadeia de if-else como, por exemplo:

Trecho de Código 2 – If-Else usando pattern instanceof expressions 

static String formatter(Object o) {
 String formatted = "unknown";
 if (o instanceof Integer i) {
     formatted = String.format("int %d", i);
 } else if (o instanceof Long l) {
     formatted = String.format("long %d", l);
 } else if (o instanceof Double d) {
     formatted = String.format("double %f", d);
 } else if (o instanceof String s) {
     formatted = String.format("String %s", s);
 }
 return formatted;
}

Ter longos blocos if-else é considerado boilerplate e repetitivo. Com o Switch, no entanto, agora podemos usar Patterns e fazer:

Trecho de Código 3 – Switch Pattern Matching

static String formatterPatternSwitch(Object o) {
 return switch (o) {
     case Integer i -> String.format("int %d", i);
     case Long l -> String.format("long %d", l);
     case Double d  -> String.format("double %f", d);
     case String s  -> String.format("String %s", s);
     default     -> o.toString();
 };
}

Isso é Pattern Matching (correspondência de padrões) para Switch. Switch expressions são declarações Switch com melhor sintaxe e funcionalidade.

Exemplo prático de Pattern Matching para Switch

Vamos imprimir se um determinado dia é um dia de semana ou um fim de semana. Se fôssemos fazer isso usando instruções switch, faríamos como a seguir:

Trecho de Código 4 – Switch statements

DayOfWeek dayOfWeek = DayOfWeek.MONDAY; // Assign here the value
     switch (dayOfWeek) {
         case SUNDAY:
         case SATURDAY:
             System.out.println("Weekend");
             break;
         case FRIDAY:
         case THURSDAY:
         case WEDNESDAY:
         case TUESDAY:
         case MONDAY:
             System.out.println("Weekday");
             break;
         default:
             System.out.println("Unknown Day!");
     }

Usando as novas switch expressions, podemos reescrever o código acima da seguinte maneira:

Trecho de Código 5 – Switch expressions

DayOfWeek day = DayOfWeek.MONDAY; // Assign here the value
     System.out.println(switch (day) {
         case SUNDAY, SATURDAY -> "Weekend";
         case FRIDAY, THURSDAY, WEDNESDAY, TUESDAY, MONDAY -> "Weekday";
     });

Quais são as diferenças?

  • Agora podemos definir mais de uma condição de correspondência no mesmo caso.
  • Não precisamos usar a palavra-chave “break” para parar a execução. Com expressões Switch, apenas o lado direito do caso correspondente é executado se você usar a sintaxe de rótulo de seta (->).
  • Uma vez que as instruções Switch se tornam expressões Switch, e as expressões avaliam um valor, expressões Switch podem retornar valor.
  • Se um bloco de código precisa ser executado, ele também é suportado da seguinte maneira:

Trecho de Código 6 – Switch expressions com bloco de código

System.out.println(switch (day) {
         case SUNDAY, SATURDAY -> "Weekend";
         case FRIDAY, THURSDAY, WEDNESDAY, TUESDAY, MONDAY -> {
             // do work
             yield "Weekday";
         }
     });

Basta abrir um bloco, fazer o trabalho e, finalmente, retornar um valor usando a palavra-chave yield.

  • As expressões Switch são exaustivas. Se você se esquecer de cobrir um caso em uma expressão Switch, receberá um erro em tempo de compilação. Se você cobrir todos os casos, não precisa ter um caso “default”.

Trecho de Código 7 – Completude de casos em Switch expressions

static int coverage(Object o) {
    return switch (o) {         // Error - incomplete
        case String s -> s.length();
};
}
java: the switch expression does not cover all possible input values

Esse é um benefício significativo das sealed classes que será realizado no JEP 406, que propõe estender o switch com pattern matching. Em vez de inspecionar uma instância de uma classe selada com cadeias if-else, o código do usuário será capaz de usar um switch aprimorado com patterns. O uso de sealed classes permitirá que o compilador Java verifique se os padrões são completos.

Por exemplo, considere este código usando a hierarquia de sealed classes declarada anteriormente:

Forma rotate(Forma forma, double angulo) {
     if (forma instanceof Circulo) return forma;
     else if (forma instanceof Retangulo) return forma;
     else if (forma instanceof Quadrado) return forma;
     else throw new IncompatibleClassChangeError();
}

O compilador Java não pode garantir que os testes de instância cobrem todas as subclasses permitidas de Forma. A cláusula final else é realmente inacessível, mas isso não pode ser verificado pelo compilador. Mais importante, nenhuma mensagem de erro em tempo de compilação seria emitida se o teste instanceof Retangulo fosse omitido.

Em contraste, com Pattern Matching para Switch (JEP 406), o compilador pode confirmar que todas as subclasses permitidas de Forma são cobertas. Portanto, nenhuma cláusula default ou outro padrão total (Total Pattern) é necessário. 

Além disso, o compilador emitirá uma mensagem de erro se algum dos três casos estiver faltando:

Forma rotate(Forma forma, double angulo) {
return switch (forma) {   // pattern matching switch
     case Circulo c -> c;
     case Retangulo r -> forma.rotate(angulo);
     case Quardado s -> forma.rotate(angulo);
     // não é necessário implementar default!
}
}

Outras features do Java 17

Como podemos notar, o Java 17 oferece quatorze melhorias/alterações (conhecidas como JDK Enhancement Proposals – JEPs), incluindo três módulos de incubadora e um recurso de visualização.

Os Módulos Incubadores permitem colocar APIs não finais e ferramentas não finais nas mãos de profissionais de desenvolvimento e usuários para coletar feedback que acabará por melhorar a qualidade da plataforma Java.

Da mesma forma, os recursos de pré-visualização são totalmente especificados e totalmente implementados em linguagem ou recursos de VM da plataforma Java SE. Eles são disponibilizados em releases de recursos JDK para permitir o feedback do dev com base em usos do mundo real, antes que se tornem permanentes em um lançamento futuro. Isso também oferece aos fornecedores de ferramentas a oportunidade de trabalhar para oferecer suporte aos recursos antes de serem finalizados no Java SE Standard.

A seguir destacamos alguns JEPs entregues com o Java 17:

JEP 356: Gerador de número pseudo-aleatório aprimorado

Atualizações para o java.util.random melhora a interoperabilidade de diferentes PRNGs (geradores de números pseudo-aleatórios) e torna mais fácil solicitar um algoritmo com base em requisitos, em vez de codificar uma implementação específica. 

As mudanças incluem novos tipos de interface e implementações para geradores de números pseudo-aleatórios (PRNGs), incluindo PRNGs puláveis e uma classe adicional de algoritmos PRNG divisíveis (LXM) e uma nova classe RandomGeneratorFactory.

JEP 382: Novo canal de renderização do macOS

Essa nova pipeline reduz a dependência do JDK da API Apple OpenGL obsoleta, implementando uma pipeline de renderização Java 2D para macOS usando a nova API Apple Metal.

JEP 415: Filtros de desserialização específicos do contexto

O Filtro de Dados de Serialização de Entrada, adicionado com JDK 9 (JEP 290), foi aprimorado permitindo que os aplicativos configurem filtros de desserialização específicos do contexto e selecionados dinamicamente por meio de uma fábrica de filtros em toda a JVM, que é chamada para selecionar um filtro para cada operação de desserialização individual. 

Isso possibilita tirar proveito dos filtros de desserialização sem exigir que cada criador de stream atualize seu código ou torne o filtro muito restritivo ou permissivo.

JEP 306: Restaurar a semântica de ponto flutuante sempre restrita

A linguagem de programação Java e a máquina virtual Java originalmente tinham apenas uma semântica de ponto flutuante estrita. A partir do JDK 1.2, pequenas variações nessas semânticas estritas foram permitidas por padrão para acomodar as limitações das arquiteturas de hardware atuais. Essas variações não são mais úteis ou necessárias e foram removidas pelo JEP 306.

Java 17: a evolução do Java

Espero que tenha ficado claro as principais diferenças e novidades do Java 17, a nova versão dessa linguagem de programação tão adotada no mercado.

Além deste artigo temos um episódio especial do Zupcast, o podcast da Zup, sobre o Java 17. Reunimos um time de peso para debater as principais novidades e mudanças dessa versão. Ouça hoje mesmo:

Ficou com alguma dúvida ou tem outro destaque do Java 17? Então conta para a gente nos comentários!

Capa do artigo sobre Java 17 onde vemos uma pessoa negra com dreadslocks compridos de costas, a sua frente há duas telas, um monitor e um notebook onde é possível identificar códigos de programação.
Foto de Lincon Brito
Back-end Developer
Como todo programador, sou apaixonado por tecnologia. Atualmente desenvolvo em Kotlin, mas meu coração não é só de uma linguagem. Gosto de jogar futebol, assistir filmes/séries, escutar música e, eventualmente, gosto de filosofar sobre a vida tomando uma boa cerveja.

Artigos relacionados

Capa do artigo em foto com duas pessoas escrevendo códigos em frente a dois notebooks.
Back-End
Postado em:
Capa com a foto de uma mulher de cabelos trançados de costas de frente para um computador com códigos.
Back-End
Postado em:
Imagem capa do conteúdo sobre testes unitários, onde uma pessoa branca está em pé, segurando um notebook aberto dentro de um data center.
Back-End
Postado em:

Este site utiliza cookies para proporcionar uma experiência de navegação melhor. Consulte nossa Política de Privacidade.