O lançamento do Java Development Kit (JDK) 23 (em setembro de 2024) constitui um marco significativo na história da linguagem de programação Java, que é uma ferramenta robusta, versátil e crucial no âmbito do desenvolvimento de software.
Desde 1995, quando o Java foi apresentado pela primeira vez pela Sun Microsystems com o slogan “Write One, Run Anywhere” (em português, algo como “Escreva uma vez, execute em qualquer lugar”), que a linguagem tem proporcionado a possibilidade de executar código em qualquer dispositivo. Ao longo dos anos, o Java tem evoluído através de várias versões, apresentando novas funcionalidades e melhorias que mantêm a compatibilidade com as versões anteriores.
A tradição mantém-se com esta nova versão, que tem como objetivo melhorar a produtividade dos developers e o desempenho das aplicações, assegurando ao mesmo tempo que o Java continua a ser relevante e poderoso.
Esta versão inclui um total de 12 Java Enhancement Proposals (JEPs), nomeadamente:
Vejamos algumas das atualizações mais relevantes.
Esta nova funcionalidade tem os seguintes objetivos:
Exemplo:
//improve the switch expression:
switch (x.getStatus()) {
case 0 -> "ok";
case 1 -> "warning";
case 2 -> "error";
default -> "unknown status: " + x.getStatus();
}
//exposing the matched value:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i;
}
//allowing guards to inspect the corresponding value:
switch (x.getYearlyFlights()) {
case 0 -> ...;
case 1 -> ...;
case 2 -> issueDiscount();
case int i when i >= 100 -> issueGoldCard();
case int i -> ... appropriate action when i > 2 && i < 100 ...
}
Mais informação sobre esta funcionalidade aqui.
Na linguagem de programação Java, os constructors permitem que as instruções apareçam antes de uma invocação explícita do constructor, como super(..) ou this(..). Embora essas instruções não possam referenciar a instância em construção, podem iniciar os seus campos.
A iniciação de campos antes de invocar outro constructor aumenta a fiabilidade da classe, particularmente quando os métodos são substituídos. Esta funcionalidade está atualmente em preview.
Exemplo:
//Flexible Constructor Bodies
class Parent {
int x;
public Parent(int x) {
this.x = x;
}
}
class Child extends Parent {
int y;
public Child(int x, int y) {
// Statements before calling the parent constructor
int temp = x * 2; // Cannot reference instance fields
super(temp); // Explicit constructor invocation
this.y = y; // Instance fields can be initialized after the invocation
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child(5, 10);
System.out.println("x: " + child.x + ", y: " + child.y); // Outputs: x: 10, y: 10
}
}
Mais informação sobre esta funcionalidade aqui.
A funcionalidade de structured concurrency simplifica a programação multithread, tratando várias tarefas executadas em diferentes threads como uma única unidade de trabalho, simplificando assim o tratamento e o cancelamento de erros.
Exemplo:
// Structured Concurrency
public class StructuredConcurrencyExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> future1 = scope.fork(() -> fetchDataFromService1());
Future<String> future2 = scope.fork(() -> fetchDataFromService2());
scope.join(); // Join both forks
scope.throwIfFailed(); // Propagate exceptions
String result1 = future1.resultNow();
String result2 = future2.resultNow();
System.out.println(result1 + " " + result2);
}
}
private static String fetchDataFromService1() {
// Simulate fetching data
return "Data1";
}
private static String fetchDataFromService2() {
// Simulate fetching data
return "Data2";
}
}
Mais informação sobre esta funcionalidade aqui.
Os scoped values permitem que os métodos partilhem dados imutáveis com as suas chamadas dentro de uma thread e com threads secundárias. São mais simples de compreender do que as variáveis de thread locais e oferecem menores custos de espaço e tempo.
Quando usados com virtual threads e structured concurrency, os scoped values são particularmente eficientes. Esta funcionalidade é atualmente uma API em preview.
Exemplo:
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import jdk.incubator.concurrent.ScopedValue;
public class Main {
public static void main(String[] args) {
ScopedValue<String> scopedValue = ScopedValue.create("Hello, World!");
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
System.out.println("Scoped Value: " + scopedValue.get());
});
executor.shutdown();
}
}
Mais informação sobre esta funcionalidade aqui.
Esta funcionalidade tem como objetivo melhorar a Java Stream API, introduzindo duas novas operações terminais: 'Stream.gather(toList(), toSet())' e 'Stream.gather(toMap(), toSet())'. Estas operações permitem recolher elementos de um stream em várias coleções simultaneamente, o que simplifica o código e melhora a legibilidade quando um stream precisa de ser recolhido em vários containers.
A funcionalidade, inicialmente proposta no Java 22, foi aperfeiçoada com base nos comentários e está agora a ser apresentada para uma segunda preview, de modo a garantir que a sua funcionalidade e desempenho satisfazem as necessidades da comunidade antes de ser finalizada.
Exemplo:
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamGatherersExample {
public static void main(String[] args) {
// Example stream
Stream<String> stream = Stream.of("apple", "banana", "cherry", "apple", "date", "banana");
// Using Stream.gather to collect elements into a List and a Set simultaneously
var result = stream.collect(Collectors.gather(
Collectors.toList(),
Collectors.toSet()
));
// Extracting the results
List<String> list = result.getFirst();
Set<String> set = result.getSecond();
// Printing the results
System.out.println("List: " + list);
System.out.println("Set: " + set);
}
}
Mais informação sobre esta funcionalidade aqui.
Eis as minhas dicas para os developers Java que utilizam o novo Java 23:
Adotar técnicas de codificação eficientes:
Guidelines para o tratamento de erros:
Estratégias de otimização de desempenho:
O Java 23 introduz um conjunto de funcionalidades avançadas e melhorias concebidas para melhorar a eficiência e o desempenho do desenvolvimento de software, reforçando simultaneamente a compatibilidade e a interoperabilidade do Java com outras linguagens de programação e plataformas. Esta versão inclui melhorias para a Java Virtual Machine (JVM), novas funcionalidades de linguagem e atualizações para API e bibliotecas existentes, tudo com o objetivo de fornecer aos developers mais ferramentas para escreverem código de elevado desempenho e de fácil manutenção. As funcionalidades de destaque incluem melhorias no pattern matching para expressões switch, melhorias na foreign function e na API de memória, e novas opções de garbage collection, que contribuem coletivamente para a robustez e adaptabilidade das aplicações Java em diversos ambientes.
Como developer de software Java sénior, considero as novas funcionalidades do Java 23 empolgantes e essenciais para o desenvolvimento moderno. As melhorias no pattern matching simplificam a lógica condicional complexa, tornando o código mais legível e fácil de manter. As melhorias na foreign function e na API de memória são particularmente importantes, uma vez que abrem novas possibilidades de integração de Java com código nativo e outras linguagens, aumentando a versatilidade de Java em ecossistemas multilinguagem.
Em geral, o Java 23 continua a tradição de evoluir a linguagem e a plataforma para satisfazer as necessidades de desenvolvimento contemporâneas, garantindo que o Java continua a ser uma escolha de topo para os developers de todo o mundo.
O lançamento do Java Development Kit (JDK) 23 (em setembro de 2024) constitui um marco significativo na história da linguagem de programação Java, que é uma ferramenta robusta, versátil e crucial no âmbito do desenvolvimento de software.
Desde 1995, quando o Java foi apresentado pela primeira vez pela Sun Microsystems com o slogan “Write One, Run Anywhere” (em português, algo como “Escreva uma vez, execute em qualquer lugar”), que a linguagem tem proporcionado a possibilidade de executar código em qualquer dispositivo. Ao longo dos anos, o Java tem evoluído através de várias versões, apresentando novas funcionalidades e melhorias que mantêm a compatibilidade com as versões anteriores.
A tradição mantém-se com esta nova versão, que tem como objetivo melhorar a produtividade dos developers e o desempenho das aplicações, assegurando ao mesmo tempo que o Java continua a ser relevante e poderoso.
Esta versão inclui um total de 12 Java Enhancement Proposals (JEPs), nomeadamente:
Vejamos algumas das atualizações mais relevantes.
Esta nova funcionalidade tem os seguintes objetivos:
Exemplo:
//improve the switch expression:
switch (x.getStatus()) {
case 0 -> "ok";
case 1 -> "warning";
case 2 -> "error";
default -> "unknown status: " + x.getStatus();
}
//exposing the matched value:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i;
}
//allowing guards to inspect the corresponding value:
switch (x.getYearlyFlights()) {
case 0 -> ...;
case 1 -> ...;
case 2 -> issueDiscount();
case int i when i >= 100 -> issueGoldCard();
case int i -> ... appropriate action when i > 2 && i < 100 ...
}
Mais informação sobre esta funcionalidade aqui.
Na linguagem de programação Java, os constructors permitem que as instruções apareçam antes de uma invocação explícita do constructor, como super(..) ou this(..). Embora essas instruções não possam referenciar a instância em construção, podem iniciar os seus campos.
A iniciação de campos antes de invocar outro constructor aumenta a fiabilidade da classe, particularmente quando os métodos são substituídos. Esta funcionalidade está atualmente em preview.
Exemplo:
//Flexible Constructor Bodies
class Parent {
int x;
public Parent(int x) {
this.x = x;
}
}
class Child extends Parent {
int y;
public Child(int x, int y) {
// Statements before calling the parent constructor
int temp = x * 2; // Cannot reference instance fields
super(temp); // Explicit constructor invocation
this.y = y; // Instance fields can be initialized after the invocation
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child(5, 10);
System.out.println("x: " + child.x + ", y: " + child.y); // Outputs: x: 10, y: 10
}
}
Mais informação sobre esta funcionalidade aqui.
A funcionalidade de structured concurrency simplifica a programação multithread, tratando várias tarefas executadas em diferentes threads como uma única unidade de trabalho, simplificando assim o tratamento e o cancelamento de erros.
Exemplo:
// Structured Concurrency
public class StructuredConcurrencyExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> future1 = scope.fork(() -> fetchDataFromService1());
Future<String> future2 = scope.fork(() -> fetchDataFromService2());
scope.join(); // Join both forks
scope.throwIfFailed(); // Propagate exceptions
String result1 = future1.resultNow();
String result2 = future2.resultNow();
System.out.println(result1 + " " + result2);
}
}
private static String fetchDataFromService1() {
// Simulate fetching data
return "Data1";
}
private static String fetchDataFromService2() {
// Simulate fetching data
return "Data2";
}
}
Mais informação sobre esta funcionalidade aqui.
Os scoped values permitem que os métodos partilhem dados imutáveis com as suas chamadas dentro de uma thread e com threads secundárias. São mais simples de compreender do que as variáveis de thread locais e oferecem menores custos de espaço e tempo.
Quando usados com virtual threads e structured concurrency, os scoped values são particularmente eficientes. Esta funcionalidade é atualmente uma API em preview.
Exemplo:
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import jdk.incubator.concurrent.ScopedValue;
public class Main {
public static void main(String[] args) {
ScopedValue<String> scopedValue = ScopedValue.create("Hello, World!");
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
System.out.println("Scoped Value: " + scopedValue.get());
});
executor.shutdown();
}
}
Mais informação sobre esta funcionalidade aqui.
Esta funcionalidade tem como objetivo melhorar a Java Stream API, introduzindo duas novas operações terminais: 'Stream.gather(toList(), toSet())' e 'Stream.gather(toMap(), toSet())'. Estas operações permitem recolher elementos de um stream em várias coleções simultaneamente, o que simplifica o código e melhora a legibilidade quando um stream precisa de ser recolhido em vários containers.
A funcionalidade, inicialmente proposta no Java 22, foi aperfeiçoada com base nos comentários e está agora a ser apresentada para uma segunda preview, de modo a garantir que a sua funcionalidade e desempenho satisfazem as necessidades da comunidade antes de ser finalizada.
Exemplo:
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamGatherersExample {
public static void main(String[] args) {
// Example stream
Stream<String> stream = Stream.of("apple", "banana", "cherry", "apple", "date", "banana");
// Using Stream.gather to collect elements into a List and a Set simultaneously
var result = stream.collect(Collectors.gather(
Collectors.toList(),
Collectors.toSet()
));
// Extracting the results
List<String> list = result.getFirst();
Set<String> set = result.getSecond();
// Printing the results
System.out.println("List: " + list);
System.out.println("Set: " + set);
}
}
Mais informação sobre esta funcionalidade aqui.
Eis as minhas dicas para os developers Java que utilizam o novo Java 23:
Adotar técnicas de codificação eficientes:
Guidelines para o tratamento de erros:
Estratégias de otimização de desempenho:
O Java 23 introduz um conjunto de funcionalidades avançadas e melhorias concebidas para melhorar a eficiência e o desempenho do desenvolvimento de software, reforçando simultaneamente a compatibilidade e a interoperabilidade do Java com outras linguagens de programação e plataformas. Esta versão inclui melhorias para a Java Virtual Machine (JVM), novas funcionalidades de linguagem e atualizações para API e bibliotecas existentes, tudo com o objetivo de fornecer aos developers mais ferramentas para escreverem código de elevado desempenho e de fácil manutenção. As funcionalidades de destaque incluem melhorias no pattern matching para expressões switch, melhorias na foreign function e na API de memória, e novas opções de garbage collection, que contribuem coletivamente para a robustez e adaptabilidade das aplicações Java em diversos ambientes.
Como developer de software Java sénior, considero as novas funcionalidades do Java 23 empolgantes e essenciais para o desenvolvimento moderno. As melhorias no pattern matching simplificam a lógica condicional complexa, tornando o código mais legível e fácil de manter. As melhorias na foreign function e na API de memória são particularmente importantes, uma vez que abrem novas possibilidades de integração de Java com código nativo e outras linguagens, aumentando a versatilidade de Java em ecossistemas multilinguagem.
Em geral, o Java 23 continua a tradição de evoluir a linguagem e a plataforma para satisfazer as necessidades de desenvolvimento contemporâneas, garantindo que o Java continua a ser uma escolha de topo para os developers de todo o mundo.