Apresentacao

Evolução

Descubra como a linguagem Java foi se aprimorando ao longo dos anos.

O Java é uma das linguagens de programação mais utilizadas e influentes no mundo da tecnologia e tem passado por uma evolução contínua desde o seu lançamento em 1996. Este artigo visa explorar essa notável trajetória desde o Java 1.0 até as inovações mais recentes da versão Java 21. Vamos mergulhar nas características revolucionárias que moldaram o Java ao longo dos anos, incluindo a orientação a objetos, o tratamento de exceções, a manipulação de threads e a rica biblioteca de desenvolvimento de aplicativos. Além disso, vamos discutir as melhorias e adições de funcionalidades em cada versão subsequente, destacando como essas mudanças contribuíram para tornar o Java uma plataforma robusta, eficiente e versátil para desenvolvimento de software.

Antes de começar a abordar as melhorias de cada versão do Java, vamos detalhar como a nomenclatura das versões se alterou ao longo do tempo. A convenção de nomenclatura das versões do Java mudou devido a várias razões, incluindo marketing, desenvolvimento e evolução da linguagem. Inicialmente, as versões do Java seguiam um esquema de numeração 1.x, onde 'x' era um número que indicava uma nova versão. Por exemplo, Java 1.0 seguido por Java 1.1, Java 1.2, etc. Essas versões iniciais representavam etapas significativas no desenvolvimento da linguagem e da plataforma Java. A partir do Java 1.2, a Sun Microsystems (a empresa que originalmente desenvolveu o Java) começou a se referir à linguagem como Java 2. Apesar dessa mudança no marketing, o nome da versão interna continuou seguindo o formato 1.x (como Java 1.2, Java 1.3, Java 1.4, etc.). Isso criou uma certa confusão, pois a nomenclatura "Java 2" era mais uma estratégia de marketing do que uma indicação precisa da versão da linguagem.

A mudança significativa veio com a introdução do Java SE 5. A Sun Microsystems decidiu abandonar o esquema 1.x em favor de numerar as versões com base no número da atualização principal. Portanto, o que teria sido Java 1.5 tornou-se Java SE 5. Essa mudança foi feita para refletir mais precisamente o nível de desenvolvimento e as melhorias significativas que cada nova versão trazia. Desde então, a nomenclatura seguiu um formato sequencial simples sem o prefixo "1.", resultando em Java 6, Java 7, Java 8, e assim por diante. A Oracle Corporation, que adquiriu a Sun Microsystems em 2010, continuou essa prática e segue adotando a sequência numérica simples para as versões mais recentes, como Java 17, Java 18, até a atual Java 21.

Histórico

VersãoData
Disponíveis...
Java 1.0janeiro/1996
Java 1.1fevereiro/1997
Java 1.2dezembro/1998
Java 1.3maio/2000
Java 1.4fevereiro/2002
Java 5.0setembro/2004
Java 6dezembro/2006
Java 7julho/2011
Java 8 LTSmarço/2014
Java 9setembro/2017
Java 10março/2018
Java 11 LTSsetembro/2018
Java 12março/2019
Java 13setembro/2019
Java 14março/2020
Java 15setembro/2020
Java 16março/2021
Java 17LTS setembro/2021
Java 18março/2022
Java 19setembro/2022
Java 20março/2023
Java 21 LTSsetembro/2023
Em breve...
Java 22março/2024
Java 23setembro/2024
Java 24março/2025
Java 25LTS setembro/2025

Java 1.0 (janeiro/1996)

Vamos começar com a versão 1.0 do Java que também vamos chamar de Java 0. Essa versão foi lançada em janeiro de 1996, introduzindo uma série de características revolucionárias que moldaram a linguagem ao longo dos anos. Como principais características do Java 1.0 temos

O conceito de "Write Once, Run Anywhere" (WORA), destacando a portabilidade do código Java em diferentes plataformas.

Ofereceu recursos básicos de programação orientada a objetos, como classes e herança, os quais possuem características (atributos) e comportamentos (métodos).

Introduziu a JVM (Java Virtual Machine), permitindo que o código Java seja executado em qualquer sistema que tenha uma JVM instalada.

public class Pessoa {
    //atributos da classe
    private String nome;
    private int idade;

    //método construtor para criar e definir as características do objeto
    public Pessoa(String nome, int idade){
        this.nome = nome;
        this.idade = idade;
    }
    
    //método para obter valor do atributo privado
    public String getNome(){
        return nome;
    }

    //método para definir o valor de um atributo, ver parâmetros
    public void setIdade(int idade){
        this.idade = idade;
    }
}

Java 1.1 (fevereiro/1997)

Adicionou suporte para manipulação de eventos AWT (Abstract Window Toolkit).

Introduziu a API de Reflection, permitindo aos programadores examinar e modificar a estrutura de classes em tempo de execução.

Java 1.2 (dezembro/1998)

Também conhecido como Java 2 ou J2SE 1.2

Introduziu a API Collections, fornecendo estruturas de dados mais eficientes e flexíveis.

Adicionou suporte para a plataforma de gráficos 2D (Java 2D) e a API de Som (Java Sound).

Incluiu a máquina virtual Java HotSpot para melhorar o desempenho.

Inclusão do pacote Swing GUI como uma API principal. Swing é o kit de ferramentas de UI avançado do Java com recursos que excedem em muito os antigos AWTs.

Java 1.3 (maio/2000)

Introduziu o Java Platform Debugger Architecture (JPDA), permitindo depuração remota.

Adicionou suporte para Java Naming and Directory Interface (JNDI) para acesso a diretórios e serviços de diretório.

Aumentou a performance e estabilidade.

Java 1.4 (fevereiro/2002)

Incluiu a API de Logging (java.util.logging) para registro de mensagens.

Introduziu a API de XML Parsing (JAXP) para processamento de XML.

Adicionou suporte para Assertions, permitindo aos desenvolvedores testar suposições em seus códigos.

Introduziu a máquina virtual Java Native Interface (JNI) para integrar código nativo com código Java.

Java 5.0 (setembro/2004)

Generics

Java 5 introduziu generics, que permitiam aos desenvolvedores especificar os tipos de coleções e outras estruturas de dados em tempo de compilação. Esse recurso melhorou a segurança de tipo e reduziu a necessidade de conversão explícita.

    // Criando uma lista de Strings
    List<String> stringList = new ArrayList<>();

    // Adicionando elementos à lista
    stringList.add("Hello");
    stringList.add("World");

    // Criando uma lista de Integers
    List<Integer> integerList = new ArrayList<>();

    // Adicionando elementos à lista
    integerList.add(10);
    integerList.add(20);

Autoboxing e Unboxing

Java 5 introduziu Autoboxing e Unboxing, que permitiam a conversão automática entre tipos primitivos e suas classes wrapper correspondentes. Esse recurso simplificou o código e melhorou a legibilidade.

    // Autoboxing: convertendo um tipo primitivo em um objeto
    Integer intObj = 10; // Autoboxing de int para Integer

    // Unboxing: convertendo um objeto em um tipo primitivo
    int intPrit = intObj; // Unboxing de Integer para int

Enumerations

Java 5 introduziu Enumerações, que permitiam aos desenvolvedores definir um conjunto de constantes nomeadas. As enumerações melhoraram a legibilidade do código e a segurança de tipo, restringindo os valores que poderiam ser atribuídos às variáveis.

Annotations

Java 5 introduziu anotações, que permitiam aos desenvolvedores anexar metadados a elementos Java como classes, métodos e variáveis. As anotações melhoraram a legibilidade e a manutenção do código e facilitaram a geração de código.

As Annotations do Java 5.0 simplificaram a configuração de projetos ao reduzir a dependência em arquivos XML de configuração, que eram comuns no Java 1.4.

Varargs

Varargs(Variable Arguments) permitiu aos desenvolvedores definir métodos que poderiam aceitar um número variável de argumentos do mesmo tipo, simplificou chamadas de método e melhorou a legibilidade do código. Isso é feito usando reticências (...) após o tipo do parâmetro na declaração do método. Esses argumentos são tratados como um array dentro do método, proporcionando flexibilidade na chamada e simplificando o código em comparação com abordagens anteriores que exigiam arrays explícitos.

public class Main {
    // Método que calcula a soma de uma lista de inteiros
    public static int soma(int... numeros) {
        int somaTotal = 0;
        for (int numero : numeros) {
            somaTotal += numero;
        }
        return somaTotal;
    }

    public static void main(String[] args) {
        // Chamando o método soma com diferentes quantidades de argumentos
        System.out.println("Soma: " + soma(1, 2, 3, 4, 5)); // Soma: 15
        System.out.println("Soma: " + soma(10, 20, 30)); // Soma: 60
        System.out.println("Soma: " + soma(2, 4)); // Soma: 6
        System.out.println("Soma: " + soma()); // Soma: 0
    }
}

For-Each Loop

O For-Each Loop permitiu aos desenvolvedores iterar em coleções e arrays com mais facilidade, simplificou o código e melhorou a legibilidade.

    // Criando uma lista de strings
    List<String> listaDeNomes = new ArrayList<>();
    listaDeNomes.add("João");
    listaDeNomes.add("Maria");
    listaDeNomes.add("José");

    // Iterando sobre a lista usando o for-each loop
    for (String nome : listaDeNomes) {
        System.out.println(nome);
    }

Concurrency Utilities

O Java 5 introduziu novos utilitários de simultaneidade, como a classe Executors e as interfaces Callable e Future, que melhoraram a facilidade de uso e a funcionalidade dos recursos de simultaneidade do Java.

API de log Java e extensões de gerenciamento Java (JMX) 1.2

Java 5 introduziu a API Java Logging e Java Management Extensions (JMX) 1.2, que forneceu suporte aprimorado para registro e gerenciamento de aplicativos Java.

Java 6 (dezembro/2006)

Suporte a linguagem de script (JSR 223)

Java 6 adicionou suporte para linguagens de script como JavaScript, Python, Ruby e outras. Esse recurso permite que os desenvolvedores incorporem linguagens de script diretamente em seus aplicativos Java, permitindo mais flexibilidade e facilidade de desenvolvimento.

API do compilador Java (JSR 199)

Java 6 introduziu uma nova API do compilador Java (JSR 199) que permitiu aos desenvolvedores compilar código Java programaticamente em tempo de execução. Esse recurso possibilitou o desenvolvimento de ferramentas e frameworks que pudessem gerar e compilar código Java dinamicamente.

Melhorias no Swing

Java 6 introduziu várias melhorias no kit de ferramentas Swing GUI, como novas opções de aparência, melhor manipulação de fontes e novos componentes como as classes JLayer e JXLayer, que tornaram mais fácil personalizar a aparência dos componentes Swing.

JDBC4.0

Java 6 introduziu o JDBC 4.0, que proporcionou melhor integração com bancos de dados, melhor desempenho e novos recursos como carregamento automático de driver e encadeamento de exceções SQL.

A versão 4.3 do JDBC introduzida com o Java 9 (2017) é a atual versão estavel.

Java 7 (julho/2011)

Introdução de switches com strings

No Java 7, agora você pode usar um objeto String como o seletor em uma declaração switch. Isso pode ser útil ao lidar com opções especificadas em argumentos de linha de comando, que normalmente são strings.

String day = "SAT";
switch (day) {
   case "MON": System.out.println("Segunda-feira"); break;
   case "TUE": System.out.println("Terça-feira"); break;
   case "WED": System.out.println("Quarta-feira"); break;
   case "THU": System.out.println("Quinta-feira"); break;
   case "FRI": System.out.println("Sexta-feira"); break;
   case "SAT": System.out.println("Sábado"); break;
   case "SUN": System.out.println("Domingo"); break;
   default: System.out.println("Inválido");
}

Underlines em literais numéricos

O Java 7 também introduziu a capacidade de inserir underlines (_) entre os dígitos em um literal numérico (literais integrais e de ponto flutuante) para melhorar a legibilidade.

int numero1 = 0b01010000101000101101000010100010;
int numero2 = 0b10101000_01010001_01101000_01010001;
int numero3 = 2_123_456;
double numero4 = 3.1415_9265;
float numero5 = 3.14_15_92_65f;

Multi-catch block

No Java 7, você pode capturar múltiplas exceções em um único bloco catch, separadas por uma barra vertical (|).

try {
   ......
} catch(ClassNotFoundException|SQLException ex) {
   ex.printStackTrace();
}

Dynamic Method Invocation API

A API de Invocação de Método Dinâmico, introduzida no Java 7, oferece uma maneira simplificada e eficiente de executar métodos em tempo de execução. Por meio de classes como MethodHandle e MethodHandles.Lookup, os desenvolvedores podem localizar, ligar e invocar métodos dinamicamente, sem a sobrecarga de reflexão tradicional. Isso proporciona melhor desempenho, código mais claro e legível, e facilita a execução de operações em cenários dinâmicos, como na implementação de frameworks e bibliotecas. Com essa funcionalidade, o Java 7 ampliou as possibilidades de programação, oferecendo uma ferramenta poderosa para lidar com invocações de métodos em tempo de execução de forma segura e eficiente.

try-with-resources

O recurso try-with-resources, introduzido no Java 7, oferece uma maneira elegante e eficiente de gerenciar recursos que precisam ser fechados explicitamente, como arquivos ou conexões de banco de dados. Ao usar o bloco try-with-resources, os recursos são automaticamente fechados ao final do bloco, sem a necessidade de código adicional para manipular o fechamento dos recursos. Isso ajuda a evitar vazamentos de recursos e torna o código mais claro e legível. O recurso try-with-resources é uma adição valiosa ao Java 7, simplificando a gestão de recursos em aplicações Java.

  BufferedReader leitor = null;
  try {
      leitor = new BufferedReader(new FileReader("arquivo.txt"));
      String linha;
      while ((linha = leitor.readLine()) != null) {
          System.out.println(linha);
      }
  } catch (IOException e) {
      System.out.println("Erro ao ler o arquivo: " + e.getMessage());
  } finally {
      if (leitor != null) {
          try {
              leitor.close();
          } catch (IOException e) {
              System.out.println("Erro ao fechar o leitor: " + e.getMessage());
          }
      }
  }
  try (BufferedReader leitor = new BufferedReader(new FileReader("arquivo.txt"))) {
      String linha;
      while ((linha = leitor.readLine()) != null) {
          System.out.println(linha);
      }
  } catch (IOException e) {
      System.out.println("Erro ao ler o arquivo: " + e.getMessage());
  }

Neste exemplo, o bloco try-with-resources é utilizado para abrir e ler um arquivo de texto chamado "arquivo.txt". O recurso BufferedReader é automaticamente fechado ao final do bloco try, garantindo que os recursos sejam liberados corretamente, mesmo em caso de exceção durante a leitura do arquivo. Isso simplifica o código e garante uma gestão segura e eficiente dos recursos.

Java 8 LTS (março/2014)

Lambdas

Java 8 introduziu a capacidade de escrever funções anônimas de forma mais concisa e expressiva, facilitando a programação funcional em Java.

As expressões lambda são definidas usando o operador "->".

Usando expressões lambda para filtrar uma lista de inteiros: Antes do Java 8, para filtrar uma lista de inteiros, seria necessário criar uma implementação personalizada.

  List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  List<Integer> evenNumbers = new ArrayList<>();

  for (Integer number : numbers) {
      if (number % 2 == 0) {
          evenNumbers.add(number);
      }
  }
  List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

  List<Integer> evenNumbers = numbers.stream()
      .filter(number -> number % 2 == 0)
      .collect(Collectors.toList());

Introdução da Streams API

A API Java Stream é um novo recurso poderoso introduzido no Java 8 que fornece uma maneira de processar coleções de dados em um estilo funcional. A API Stream permite que você escreva código mais conciso, legível e de fácil manutenção para tarefas como filtragem, classificação e mapeamento de coleções.

Aqui está um exemplo de como usar a API Java Stream para filtrar uma lista de números e calcular a média:

    List<Integer> numeros = Arrays.asList(5, 3, 1, 4, 2);

    double media = numeros.stream()
                            .filter(number -> number % 2 == 0)
                            .mapToInt(Integer::intValue)
                            .average()
                            .orElse(0);

    System.out.println("A média dos números pares é: " + media);

Neste exemplo, estamos usando a API Stream para filtrar os números pares da lista de números e calcular a média.

O método filter() é usado para filtrar os números pares, e o método mapToInt() é usado para converter o fluxo filtrado em um IntStream.

O método Average() é então usado para calcular a média dos números pares.

O método orElse() é usado para fornecer um valor padrão caso o fluxo esteja vazio. Nesse caso, estamos fornecendo um valor padrão de 0.

Este código é equivalente ao seguinte código:

List<Integer> numeros2 = Arrays.asList(5, 3, 1, 4, 2);

int soma = 0;
int quantidade = 0;
for (Integer numero : numeros2) {
    if (numero % 2 == 0) {
        soma += numero;
        quantidade++;
    }
}
double media2;
if (quantidade > 0) {
    media2 = (double) soma / quantidade;
} else {
    media2 = 0.0;
}
System.out.println("A média dos números pares é: " + media2);

Adição de java.time package para manipulação de data e hora

A API Java Date and Time é um novo recurso introduzido no Java 8 que fornece uma maneira abrangente e flexível de lidar com datas, horas e durações. A nova API inclui classes como LocalDate, LocalTime, LocalDateTime, ZonedDateTime e Duration, que fornecem uma maneira mais intuitiva e segura de trabalhar com datas e horas.

Aqui estão alguns dos principais recursos da API Java Date and Time:

Objetos imutáveis: todos os objetos na API Java Date and Time são imutáveis, o que significa que não podem ser modificados depois de criados. Isso garante a segurança do thread e simplifica o uso da API.

Interface Fluente: A API Java Date and Time fornece uma interface fluente, o que significa que os métodos são encadeados para criar um código mais legível e expressivo.

Fusos horários: A API Java Date and Time fornece suporte para fusos horários, o que permite trabalhar com datas e horas em diferentes partes do mundo.

Formatação e análise: a API Java Date and Time fornece suporte para formatação e análise de datas e horas, o que permite exibir datas e horas em um formato amigável.

Cálculos de data e hora: A API Java Date and Time fornece suporte para cálculos de data e hora, o que permite realizar operações como adicionar ou subtrair dias, horas, minutos e segundos.

Aqui está um exemplo de como usar a API Java Date and Time para formatar uma data:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class ExemploData {
    public static void main(String[] args) {
        LocalDate data = LocalDate.now();

        // Definindo o idioma para português do Brasil
        Locale brasil = new Locale("pt", "BR");

        // Definindo o formato da data com o idioma específico
        DateTimeFormatter formatador = DateTimeFormatter.ofPattern("dd 'de' MMMM 'de' yyyy", brasil);
        String dataFormatada = data.format(formatador);

        System.out.println("A data de hoje é: " + dataFormatada);
    }
}

Como você pode ver, a API Java Date and Time fornece uma maneira mais intuitiva e expressiva de trabalhar com datas e horas do que a classe Date nas versões anteriores.

Métodos Default

Antes do Java 8, uma interface Java só poderia conter assinaturas de métodos, mas não sua implementação. Com a introdução dos métodos default, agora é possível fornecer implementações padrão para métodos em interfaces.

public interface MinhaInterface {
    // Método default com implementação padrão
    default void metodoDefault() {
        System.out.println("Implementação padrão de um método default");
    }

    // Método regular sem implementação
    void outroMetodo();
}

As classes que implementam MinhaInterface podem optar por sobrescrever metodoDefault() se desejarem uma implementação personalizada. Se não o fizerem, a implementação padrão será utilizada.

Java 11 LTS (setembro/2018)

Local Variable Type Inference (Java 10 preview / Java 11 release)

No JDK 10 e versões posteriores, você pode declarar variáveis locais com inicializadores não nulos com o identificador var, o que pode ajudá-lo a escrever código mais fácil de ler.

Considere o seguinte exemplo, que parece redundante:

  URL url = new URL("http://www.oracle.com/"); 
  URLConnection conn = url.openConnection(); 
  Reader reader = new BufferedReader(
  new InputStreamReader(conn.getInputStream()));

Você pode reescrever este exemplo declarando as variáveis locais com o identificador var. O tipo das variáveis ​​é inferido do contexto:

  var url = new URL("http://www.oracle.com/"); 
  var conn = url.openConnection(); 
  var reader = new BufferedReader(
  new InputStreamReader(conn.getInputStream()));

Java 17 LTS (setembro/2021)

Switch Expressions (Java 12 preview / Java 14 release)

Como todas as expressões, as expressões switch são avaliadas como um único valor e podem ser usadas em instruções.

Eles podem conter rótulos "case L ->" que eliminam a necessidade de instruções break para evitar falhas.

Considere a seguinte instrução switch que imprime o número de letras de um dia da semana:

public enum Dia { DOMINGO, SEGUNDA, TERÇA,
    QUARTA, QUINTA, SEXTA, SÁBADO; }

public static void main(String[] args) {

    int numLetras = 0;
    Dia dia = Dia.QUARTA;
    switch (dia) {
        case TERÇA:
        case SEXTA:
            numLetras = 5;
            break;
        case QUARTA:
        case QUINTA:
        case SÁBADO:
            numLetras = 6;
            break;
        case SEGUNDA:
        case DOMINGO:
            numLetras = 7;
            break;
        default:
            throw new IllegalStateException("Dia inválido: " + dia);
    }
    System.out.println(numLetras);     
}

Seria melhor se você pudesse "retornar" o tamanho do nome do dia em vez de armazená-lo na variável numLetras; você pode fazer isso com uma expressão switch. Além disso, seria melhor se você não precisasse de instruções break para evitar falhas; eles são trabalhosos de escrever e fáceis de esquecer. Você pode fazer isso com um novo tipo de etiqueta de caixa.

A seguir está uma expressão switch que usa o novo tipo de rótulo case para imprimir o número de letras de um dia da semana:

public enum Dia { DOMINGO, SEGUNDA, TERÇA,
    QUARTA, QUINTA, SEXTA, SÁBADO; }

public static void main(String[] args) {

    int numLetras = 0;
    Dia dia = Dia.QUARTA;

    System.out.println(
        switch (dia) {
            case TERÇA, SEXTA         -> 5;
            case QUARTA,QUINTA,SÁBADO -> 6;
            case SEGUNDA, DOMINGO     -> 7;
            default -> throw new IllegalStateException("Dia inválido: " + dia);
        }
    );        
        
    // OU também pode ser

    numLetras = 0;
    dia = Dia.DOMINGO;
    switch (dia) {
        case TERÇA, SEXTA         -> numLetras = 5;
        case QUARTA,QUINTA,SÁBADO -> numLetras = 6;
        case SEGUNDA, DOMINGO     -> numLetras = 7;
        default -> throw new IllegalStateException("Dia inválido: " + dia);
    }
    System.out.println(numLetras);      
}

Quando o Java runtime corresponde a qualquer um dos rótulos à esquerda da seta, ele executa o código à direita da seta e não necessita do break; ele não executa nenhum outro código na expressão (ou instrução) switch. Se o código à direita da seta for uma expressão, o valor dessa expressão será o valor da expressão switch.

Text Blocks (Java 13 preview / Java 15 release)

O principal objetivo de um text block é fornecer clareza, minimizando a sintaxe Java necessária para renderizar uma string que abrange várias linhas.

Nas versões anteriores do JDK, a incorporação de trechos de código multilinha exigia uma confusão de terminadores de linha explícitos, concatenações de strings e delimitadores. Os blocos de texto eliminam a maioria dessas obstruções, permitindo incorporar trechos de código e sequências de texto mais ou menos como estão.

Um bloco de texto é uma forma alternativa de representação de string Java que pode ser usada em qualquer lugar onde uma string literal tradicional entre aspas duplas possa ser usada. Por exemplo:

System.out.println("""
            Esta é a primeira linha
            Esta é a segunda linha
            Esta é a terceira linha
            """);   

String sJava = "write once run anywhere";

String tbJava = """
                write once run anywhere""";

if(sJava.equals(tbJava))
    System.out.println("o mesmo conteúdo das String");

na linha do primeiro """ não deve conter mais nenhum caractere, o texto começa na linha seguinte.

Record Classes (Java 14 preview / Java 16 release)

As classes record, são um tipo especial de classe, ajudam a modelar dados simples com menos cerimônia do que as classes normais.

Uma declaração de record especifica em um cabeçalho uma descrição do seu conteúdo, os métodos apropriados de acesso, construtor, equals, hashCode e toString são criados automaticamente.

Por exemplo, uma classe record com dois campos:

record Rectangle(double length, double width) { }

Essa declaração concisa de um retângulo é equivalente à seguinte classe normal:

public final class Rectangle {
    private final double length;
    private final double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    double length() { return this.length; }
    double width()  { return this.width; }

    // Implementação de equals() e hashCode(), 
    // que especificam que dois objetos de registro são iguais 
    // se forem do mesmo tipo e contiverem valores de campo iguais.
    public boolean equals...
    public int hashCode...

    // Uma implementação de toString() 
    // que retorna uma representação em string de todos os campos da classe, incluindo seus nomes.
    public String toString() {...}
}

O Record é utilizado da mesma forma que uma classe através do new:

Rectangle r = new Rectangle(4,5);

Pattern Matching for instanceof (Java 14 preview / Java 16 release)

A correspondência de padrões (Pattern matching) envolve testar se um objeto tem uma estrutura específica e, em seguida, extrair dados desse objeto, se houver uma correspondência. Você já pode fazer isso com Java. No entanto, a correspondência de padrões introduz novos aprimoramentos de linguagem que permitem extrair condicionalmente dados de objetos com código mais conciso e robusto.

A correspondência de padrões permite remover a etapa de conversão alterando o segundo operando do operador instanceof por um padrão de tipo, tornando seu código mais curto e fácil de ler.

public class Rectangle implements Shape {
    final double length;
    final double width;    
}

public class Circle implements Shape {
    final double radius;
}
  public static double getPerimeter(Shape shape) throws IllegalArgumentException {
      if (shape instanceof Rectangle) {
          Rectangle r = (Rectangle) shape;
          return 2 * r.length() + 2 * r.width();
      } else if (shape instanceof Circle) {
          Circle c = (Circle) shape;
          return 2 * c.radius() * Math.PI;
      } else {
          throw new IllegalArgumentException("Unrecognized shape");
      }
  }
  public static double getPerimeter(Shape shape) throws IllegalArgumentException {
      if (shape instanceof Rectangle r) {
          return 2 * r.length() + 2 * r.width();
      } else if (shape instanceof Circle c) {
          return 2 * c.radius() * Math.PI;
      } else {
          throw new IllegalArgumentException("Unrecognized shape");
      }
  }

Sealed Classes (Java 15 preview / Java 17 release)

Classes seladas em Java são uma nova característica introduzida a partir do Java 15, que permite que você restrinja quais classes podem ser subclasses de uma classe específica. Essa restrição é feita por meio do uso da palavra-chave sealed na declaração da classe, seguida por uma lista de permissões das subclasses. As classes seladas promovem uma hierarquia de classes mais controlada e segura, impedindo que outras classes estendam uma classe de forma não autorizada.

Classe selada

public sealed class Shape
    permits Circle, Rectangle {
}

Circle pode estender Shape

public final class Circle extends Shape {
    public float radius;
}

Square não pode estender Shape, erro para compilar

public final class Square extends Shape {
   public double side;
}

Java 21 LTS (setembro/2023)

Record Patterns (Java 19 preview / Java 21 release)

Records foram adicionados ao Java 14 para criar objetos imutáveis e compactos, eliminando o boilerplate. Antes, desestruturar um record exigia obter cada uma de suas propriedades, tornando o código mais extenso. Agora, com os Record Patterns, isso ficou muito mais simples.

  record Point(int x, int y) {}

  static void printSum(Object obj) {
      if (obj instanceof Point p) {
          int x = p.x();
          int y = p.y();
          System.out.println(x+y);
      }
  }
  record Point(int x, int y) {}

  static void printSum(Object obj) {
      if (obj instanceof Point(int x, int y)) {
          System.out.println(x+y);
      }
  }

Pattern Matching for switch Expressions and Statements (Java 17 preview / Java 21 release)

Uma instrução switch transfere o controle para uma das diversas instruções ou expressões, dependendo do valor de sua expressão seletora. Em versões anteriores, a expressão do seletor deve ser avaliada como um número, string ou constante enum, e os rótulos de caso devem ser constantes. No entanto, nesta versão, a expressão do seletor pode ser qualquer tipo de referência. Consequentemente, uma instrução ou expressão switch pode testar se sua expressão seletora corresponde a um padrão, o que oferece mais flexibilidade e expressividade em comparação a testar se sua expressão seletora é exatamente igual a uma constante.

interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
  public static double getPerimeter(Shape s) throws IllegalArgumentException {
      if (s instanceof Rectangle r) {
          return 2 * r.length() + 2 * r.width();
      } else if (s instanceof Circle c) {
          return 2 * c.radius() * Math.PI;
      } else {
          throw new IllegalArgumentException("Unrecognized shape");
      }
  }
  public static double getPerimeter(Shape s) throws IllegalArgumentException {
      return switch (s) {
          case Rectangle r -> 2 * r.length() + 2 * r.width();
          case Circle c    -> 2 * c.radius() * Math.PI;
          default          -> throw new IllegalArgumentException("Unrecognized shape");
      };
  }
  public static double getPerimeter(Shape s) throws IllegalArgumentException {
      switch (s) {
          case Rectangle r: return 2 * r.length() + 2 * r.width();
          case Circle c:    return 2 * c.radius() * Math.PI;
          default:          throw new IllegalArgumentException("Unrecognized shape");
      }
  }

String Templates (Java 21 preview)

String templates complementam as String e Text Block existentes em Java, ao acoplar texto literal com expressões embutidas.

Uma expressão embutida é uma expressão Java, exceto que possui uma sintaxe adicional para diferenciá-la do texto literal no modelo de string.

Um processador de modelo combina o texto literal no modelo com os valores das expressões embutidas para produzir um resultado.

String nome     = "Glayson";
String telefone = "(99) 99999-9999";
String endereco = "Teresina/PI";

String json = STR."""
{
    "Nome":    "\{nome}",
    "Telefone":   "\{telefone}",
    "Endereço": "\{endereco}"
}
""";

Saida:

{
    "Nome":    "Glayson",
    "Telefone":   "(99) 99999-9999",
    "Endereço": "Teresina/PI"
}

Unnamed Patterns and Variables (Java 21 preview)

Unnamed patterns eliminam o fardo de ter que escrever um tipo e nome de uma variável padrão que não é necessária no código subsequente. Variáveis sem nome são variáveis que podem ser inicializadas, mas não usadas. Você denota ambos com o caractere sublinhado (_).

    record Point(double x, double y) {}
    enum Color { RED, GREEN, BLUE }
    record ColoredPoint(Point p, Color c) {}
  double getDistance(Object obj1, Object obj2) {
      if (obj1 instanceof ColoredPoint(Point p1, Color c1) &&
          obj2 instanceof ColoredPoint(Point p2, Color c2)) {
      return java.lang.Math.sqrt(
          java.lang.Math.pow(p2.x - p1.x, 2) +
          java.lang.Math.pow(p2.y - p1.y, 2));
      } else {
          return -1;
      }
  }
  double getDistance(Object obj1, Object obj2) {
      if (obj1 instanceof ColoredPoint(Point p1, _) &&
          obj2 instanceof ColoredPoint(Point p2, _)) {
      return java.lang.Math.sqrt(
          java.lang.Math.pow(p2.x - p1.x, 2) +
          java.lang.Math.pow(p2.y - p1.y, 2));
      } else {
          return -1;
      }
  }

O exemplo permite você pode omitir as declarações Cor c1 e Cor c2 com o (_):

Unnamed Classes and Instance Main Methods (Java 21 preview)

A adição de métodos principais de instância e classes sem nome à linguagem Java permite que os alunos escrevam declarações simplificadas para programas de classe única e, em seguida, expandam seus programas posteriormente para usar recursos mais avançados à medida que suas habilidades aumentam.

  public class HelloWorld {
      public static void main(String[] args) {
          System.out.println("Hello, World!");
      }
  }
  class HelloWorld { 
      void main() { 
          System.out.println("Hello, World!");
      }
  }
  String greeting() { return "Hello, World!"; }

  void main() {
      System.out.println(greeting());
  }
  String greeting = "Hello, World!";

  void main() {
      System.out.println(greeting);
  }

Os: Ao escrever este material as IDE’s ainda não mostravam compatibilidade com esta alteração e para copilar e executar um arquivo por linha de comando é necessário a opção --enable-preview

javac --enable-preview --release 12 Teste21.java

java --enable-preview Teste21

Unnamed Classes

Sequenced Collections (Java 21 release)

Uma coleção sequenciada é uma coleção cujos elementos possuem uma ordem definida. Possui o primeiro, o último elemento, e os elementos entre eles têm sucessores e predecessores, oferece suporte a operações comuns em ambas as extremidades, do primeiro ao último e do último ao primeiro.

  arrayList.get( arrayList.size() - 1 );
  arrayList.getLast();
  interface SequencedCollection<E> extends Collection<E> {
      SequencedCollection<E> reversed();
      void addFirst(E);
      void addLast(E);
      E getFirst();
      E getLast();
      E removeFirst();
      E removeLast();
  }
  interface SequencedMap<K,V> extends Map<K,V> {
      SequencedMap<K,V> reversed();
      SequencedSet<K> sequencedKeySet();
      SequencedCollection<V> sequencedValues();
      SequencedSet<Entry<K,V>> sequencedEntrySet();
      V putFirst(K, V);
      V putLast(K, V);
      Entry<K, V> firstEntry();
      Entry<K, V> lastEntry();
      Entry<K, V> pollFirstEntry();
      Entry<K, V> pollLastEntry();
  }

Virtual Threads

Threads virtuais são threads leves que reduzem drasticamente o esforço de gravação, manutenção e observação de aplicativos simultâneos de alto rendimento.

Threads virtuais não são threads mais rápidos; eles não executam código mais rápido que threads de plataforma. Eles existem para fornecer escala (maior rendimento), não velocidade (menor latência).

Conclusão

A jornada do Java ao longo das últimas três décadas é uma testemunha impressionante da sua resiliência e adaptabilidade. Desde seu nascimento em 1996 até a versão mais recente, Java continua a evoluir e se reinventar, mantendo-se relevante em um cenário tecnológico em constante mudança.

Cada atualização trouxe consigo um pacote de melhorias, desde novos recursos de linguagem até aprimoramentos nas APIs e desempenho.

O Java não apenas resistiu ao teste do tempo, mas também abraçou ativamente novas tendências e demandas da indústria, como programação funcional, modularidade e concorrência. Sua comunidade apaixonada e vibrante, juntamente com o compromisso contínuo da Oracle e de outros contribuidores, garantem que o Java permaneça não apenas como uma das linguagens de programação mais populares, mas também como um pilar fundamental da computação moderna. À medida que olhamos para o futuro, podemos confiar na capacidade do Java de continuar a liderar e inovar, capacitando desenvolvedores a construir soluções robustas e escaláveis para os desafios de amanhã.

https://docs.oracle.com/en/java/javase/21/index.html

https://en.wikipedia.org/wiki/Java_version_history

https://www.java.com/releases/

https://openjdk.org/projects/jdk-updates/

https://openjdk.org/jeps/