Novidades e Recursos do Java 8

Sumário

  • Introdução ao Java 8
  • Expressões Lambda
  • Referências de Método
  • API Stream
  • Classe Optional
  • Métodos Padrão em Interfaces
  • Interfaces Fnucionais
  • Motor JavaScript Nashorn
  • API Aprimorada de Data e Hora
  • Referências

Introdução ao Java 8

O Java 8 introduziu várias funcionalidades importantes que modernizaram a linguagem e facilitaram o desenvolvimento de código mais conciso e expressivo. As principais inovações incluem:

  • Expressões Lambda: Permitem tratar funcionalidades como argumentos de método
  • Referências de Método: Facilitam a referência a métodos ou construtores existentes
  • API Stream: Habilita processamento funcional de coleções
  • Classe Optional: Fornece uma alternativa segura para valores nulos
  • Métodos Padrão: Permitem implementações em interfaces
  • Interfaces Funcionais: Interfaces com um único método abstrato
  • API de Data e Hora: Novas classes para manipulação de datas e horas
  • Nashorn: Motor JavaScript para execução no ambiente Java

Expressões Lambda

As expressões Lambda são uma das características mais poderosas do Java 8, permitindo escrever código funcional mais conciso. A sintaxe básica é:

(parâmetros) -> {expressão}

Quando o tipo do parâmetro pode ser inferido, ele pode ser omitido. Se houver apenas um parâmetro, os parênteses também podem ser omitidos.

Exemplos Práticos

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class DemonstracaoLambda {
    public static void main(String[] args) throws InterruptedException {
        // Criando thread com sintaxe tradicional
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Execução tradicional");
            }
        }).start();

        // Criando thread com Lambda
        new Thread(() -> System.out.println("Execução com Lambda")).start();
        
        List<string> frutas = Arrays.asList("laranja", "maçã", "banana");
        
        // Ordenação com Lambda
        frutas.sort((a, b) -> a.compareToIgnoreCase(b));
        System.out.println("Lista ordenada: " + frutas);
        
        // Filtragem com Lambda
        List<string> filtradas = frutas.stream()
            .filter(f -> f.startsWith("m"))
            .collect(Collectors.toList());
        System.out.println("Frutas filtradas: " + filtradas);
        
        // Mapeamento com Lambda
        List<integer> tamanhos = frutas.stream()
            .map(String::length)
            .collect(Collectors.toList());
        System.out.println("Tamanhos das frutas: " + tamanhos);
    }
}</integer></string></string>

Saída do Exemplo

Execução tradicional
Execução com Lambda
Lista ordenada: [maçã, banana, laranja]
Frutas filtradas: [maçã]
Tamanhos das frutas: [4, 6, 7]

Referências de Método

As referências de método permitem referenciar métodos ou construtores diretamente usando a notação ::. Existem quatro tipos de referências:

  1. Referência a método estático: Classe::metodoEstatico
  2. Referência a método de instância: objeto::metodoInstancia
  3. Referência a método de instância de objeto arbitrário: Classe::metodoInstancia
  4. Referência a construtor: Classe::new

Exemplos Práticos

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

class DemonstracaoReferencia {
    static class Calculadora {
        public static int quadrado(int n) {
            return n * n;
        }
        
        public int dobro(int n) {
            return n * 2;
        }
    }
    
    public static void main(String[] args) {
        // Referência a método estático
        Function<integer integer=""> quadradoFuncao = Calculadora::quadrado;
        System.out.println("Quadrado de 5: " + quadradoFuncao.apply(5));
        
        // Referência a método de instância
        Calculadora calc = new Calculadora();
        Function<integer integer=""> dobroFuncao = calc::dobro;
        System.out.println("Dobro de 7: " + dobroFuncao.apply(7));
        
        // Referência a construtor
        Supplier<list>> listaSupplier = ArrayList::new;
        List<string> novaLista = listaSupplier.get();
        System.out.println("Nova lista criada: " + novaLista.getClass().getSimpleName());
        
        // Referência a método de instância de objeto arbitrário
        List<string> palavras = Arrays.asList("java", "python", "javascript");
        palavras.sort(String::compareToIgnoreCase);
        System.out.println("Lista ordenada: " + palavras);
    }
}</string></string></list></integer></integer>

Saída do Exemplo

Quadrado de 5: 25
Dobro de 7: 14
Nova lista criada: ArrayList
Lista ordenada: [java, javascript, python]

API Stream

A API Stream, introduzida no Java 8, permite processar coleções de forma declarativa e funcional. As operações Stream podem ser divididas em:

  • Operações intermediárias: filter, map, sorted, limit, etc.
  • Operações terminais: forEach, collect, reduce, etc.

Exemplos Práticos

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class DemonstracaoStream {
    public static void main(String[] args) {
        List<integer> numeros = Arrays.asList(5, 2, 8, 1, 9, 3, 7);
        
        // Filtragem
        List<integer> pares = numeros.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());
        System.out.println("Números pares: " + pares);
        
        // Mapeamento
        List<string> quadrados = numeros.stream()
            .map(n -> "Quadrado de " + n + " é " + (n * n))
            .collect(Collectors.toList());
        System.out.println("Quadrados: " + quadrados);
        
        // Ordenação
        List<integer> ordenados = numeros.stream()
            .sorted()
            .collect(Collectors.toList());
        System.out.println("Números ordenados: " + ordenados);
        
        // Redução
        int soma = numeros.stream()
            .reduce(0, Integer::sum);
        System.out.println("Soma total: " + soma);
        
        // Agrupamento
        Map<boolean list="">> agrupados = numeros.stream()
            .collect(Collectors.partitioningBy(n -> n % 2 == 0));
        System.out.println("Agrupados por paridade: " + agrupados);
        
        // Estatísticas
        double media = numeros.stream()
            .mapToInt(Integer::intValue)
            .average()
            .orElse(0.0);
        System.out.println("Média: " + media);
    }
}</boolean></integer></string></integer></integer>

Saída do Exemplo

Números pares: [2, 8]
Quadrados: [Quadrado de 5 é 25, Quadrado de 2 é 4, Quadrado de 8 é 64, Quadrado de 1 é 1, Quadrado de 9 é 81, Quadrado de 3 é 9, Quadrado de 7 é 49]
Números ordenados: [1, 2, 3, 5, 7, 8, 9]
Soma total: 35
Agrupados por paridade: {false=[5, 1, 9, 3, 7], true=[2, 8]}
Média: 5.0

Classe Optional

A classe Optional é um contêiner que pode conter ou não um valor não nulo. Foi projetada para ajudar a evitar exceções NullPointerException e encorajar código mais explícito para lidar com casos ausentes.

Exemplos Práticos

import java.util.Optional;

class DemonstracaoOptional {
    public static void main(String[] args) {
        // Criando Optional
        Optional<string> vazio = Optional.empty();
        Optional<string> presente = Optional.of("Valor presente");
        Optional<string> nulo = Optional.ofNullable(null);
        
        // Verificando presença
        System.out.println("Vazio está presente? " + vazio.isPresent());
        System.out.println("Presente está presente? " + presente.isPresent());
        
        // Obtendo valores ou padrões
        System.out.println("Valor padrão: " + vazio.orElse("Padrão"));
        System.out.println("Valor padrão supplier: " + nulo.orElseGet(() -> "Valor gerado"));
        
        // Executando ações condicionais
        presente.ifPresent(valor -> System.out.println("Ação com valor: " + valor));
        
        // Transformação
        Optional<string> maiusculas = presente.map(String::toUpperCase);
        System.out.println("Valor em maiúsculas: " + maiusculas.orElse("Nenhum valor"));
        
        // Encadeamento de Optional
        Optional<string> resultado = presente
            .map(String::toUpperCase)
            .filter(valor -> valor.length() > 5);
        System.out.println("Resultado filtrado: " + resultado.orElse("Não atende ao critério"));
    }
}</string></string></string></string></string>

Saída do Exemplo

Vazio está presente? false
Presente está presente? true
Valor padrão: Padrão
Valor padrão supplier: Valor gerado
Ação com valor: Valor presente
Valor em maiúsculas: VALOR PRESENTE
Resultado filtrado: VALOR PRESENTE

Métodos Padrão em Interfaces

O Java 8 permite que interfaces tenham métodos com implementação, conhecidos como métodos padrão. Isso é útil para adicionar funcionalidades a interfaces existentes sem quebrar implementações existentes.

Exemplos Práticos

interface InterfacePadrao {
    // Método abstrato
    void metodoAbstrato();
    
    // Método padrão
    default void metodoPadrao() {
        System.out.println("Implementação padrão do método");
    }
    
    // Método estático
    static void metodoEstatico() {
        System.out.println("Método estático da interface");
    }
}

class Implementacao implements InterfacePadrao {
    @Override
    public void metodoAbstrato() {
        System.out.println("Implementação do método abstrato");
    }
    
    // Sobrescrevendo método padrão
    @Override
    public void metodoPadrao() {
        System.out.println("Implementação customizada do método padrão");
        InterfacePadrao.super.metodoPadrao(); // Chamando implementação padrão
    }
}

class DemonstracaoMetodoPadrao {
    public static void main(String[] args) {
        InterfacePadrao obj = new Implementacao();
        obj.metodoAbstrato();
        obj.metodoPadrao();
        InterfacePadrao.metodoEstatico();
    }
}

Saída do Exemplo

Implementação do método abstrato
Implementação customizada do método padrão
Implementação padrão do método
Método estático da interface

Interfaces Funcionais

Interfaces funcionais são interfaces que possuem exatamente um método abstrato. Elas podem ser implementadas por expressões Lambda. O pacote java.util.function contém várias interfaces funcionais úteis.

Principais Interfaces Funcionais

  • Function: Recebe T e retorna R
  • Consumer: Recebe T e não retorna nada
  • Supplier: Não recebe nada e retorna T
  • Predicate: Recebe T e retorna boolean
  • UnaryOperator: Recebe T e retorna T

Exemplos Práticos

import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

class DemonstracaoInterfacesFuncionais {
    public static void main(String[] args) {
        List<integer> numeros = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // Usando Predicate para filtrar
        Predicate<integer> ehPar = n -> n % 2 == 0;
        List<integer> pares = numeros.stream()
            .filter(ehPar)
            .collect(Collectors.toList());
        System.out.println("Números pares: " + pares);
        
        // Usando Function para transformar
        Function<integer integer=""> quadrado = n -> n * n;
        List<integer> quadrados = numeros.stream()
            .map(quadrado)
            .collect(Collectors.toList());
        System.out.println("Quadrados: " + quadrados);
        
        // Usando Consumer para imprimir
        numeros.forEach(System.out::println);
        
        // Interface funcional personalizada
        OperacaoMatematica soma = (a, b) -> a + b;
        OperacaoMatematica multiplicacao = (a, b) -> a * b;
        
        System.out.println("5 + 3 = " + soma.executar(5, 3));
        System.out.println("5 * 3 = " + multiplicacao.executar(5, 3));
    }
    
    @FunctionalInterface
    interface OperacaoMatematica {
        int executar(int a, int b);
    }
}</integer></integer></integer></integer></integer>

Saída do Exemplo

Números pares: [2, 4, 6, 8, 10]
Quadrados: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
1
2
3
4
5
6
7
8
9
10
5 + 3 = 8
5 * 3 = 15

Motor JavaScript Nashorn

O Nashorn é um motor JavaScript que executa no ambiente Java, permitindo a execução de código JavaScript diretamente no Java.

Exemplos Práticos

import javax.script.*;
import java.io.FileReader;

class DemonstracaoNashorn {
    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("nashorn");
        
        try {
            // Executando código JavaScript simples
            engine.eval("print('Olá do JavaScript!');");
            
            // Passando variáveis do Java para JavaScript
            engine.eval("var msg = 'Mensagem do Java';");
            engine.eval("print(msg);");
            
            // Chamando função JavaScript do Java
            engine.eval("function soma(a, b) { return a + b; }");
            
            Invocable invocable = (Invocable) engine;
            Object resultado = invocable.invokeFunction("soma", 10, 5);
            System.out.println("Resultado da soma: " + resultado);
            
            // Executando arquivo JavaScript
            // engine.eval(new FileReader("script.js"));
            
        } catch (ScriptException e) {
            System.out.println("Erro na execução do JavaScript: " + e.getMessage());
        } catch (NoSuchMethodException e) {
            System.out.println("Método não encontrado: " + e.getMessage());
        }
    }
}

Saída do Exemplo

Olá do JavaScript!
Mensagem do Java
Resultado da soma: 15

API Aprimorada de Data e Hora

O Java 8 introduziu um novo pacote java.time para substituir as antigas classes java.util.Date e java.util.Calendar. As novas classes são imutáveis e thread-safe.

Classes Principais

  • LocalDate: Representa uma data (ano-mês-dia)
  • LocalTime: Representa um tempo (hora-minuto-segundo)
  • LocalDateTime: Combina data e tempo
  • ZonedDateTime: Data, tempo e fuso horário
  • Duration: Representa uma quantidade de tempo

Exemplos Práticos

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

class DemonstracaoDataHora {
    public static void main(String[] args) {
        // Data e hora atual
        LocalDate hoje = LocalDate.now();
        LocalTime agora = LocalTime.now();
        LocalDateTime agoraCompleto = LocalDateTime.now();
        
        System.out.println("Data atual: " + hoje);
        System.out.println("Hora atual: " + agora);
        System.out.println("Data e hora atuais: " + agoraCompleto);
        
        // Criando datas específicas
        LocalDate dataEspecifica = LocalDate.of(2024, 12, 25);
        LocalTime horaEspecifica = LocalTime.of(15, 30);
        LocalDateTime dataHoraEspecifica = LocalDateTime.of(2024, 12, 25, 15, 30);
        
        System.out.println("Data específica: " + dataEspecifica);
        System.out.println("Hora específica: " + horaEspecifica);
        System.out.println("Data e hora específicas: " + dataHoraEspecifica);
        
        // Manipulando datas
        LocalDate amanha = hoje.plusDays(1);
        LocalDate semanaPassada = hoje.minusWeeks(1);
        
        System.out.println("Amanhã: " + amanha);
        System.out.println("Semana passada: " + semanaPassada);
        
        // Formatando datas
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
        String formatado = agoraCompleto.format(formatter);
        System.out.println("Data formatada: " + formatado);
        
        // Diferença entre datas
        long diasEntre = ChronoUnit.DAYS.between(semanaPassada, hoje);
        System.out.println("Dias entre as datas: " + diasEntre);
        
        // Fuso horário
        ZoneId fusoSaoPaulo = ZoneId.of("America/Sao_Paulo");
        ZonedDateTime agoraSaoPaulo = ZonedDateTime.now(fusoSaoPaulo);
        
        System.out.println("Horário em São Paulo: " + agoraSaoPaulo);
        
        // Conversão entre fusos
        ZoneId fusoNovaYork = ZoneId.of("America/New_York");
        ZonedDateTime agoraNovaYork = agoraSaoPaulo.withZoneSameInstant(fusoNovaYork);
        
        System.out.println("Horário em Nova York: " + agoraNovaYork);
    }
}

Saída do Exemplo

Data atual: 2024-10-28
Hora atual: 17:26:47.282
Data e hora atuais: 2024-10-28T17:26:47.282
Data específica: 2024-12-25
Hora específica: 15:30
Data e hora específicas: 2024-12-25T15:30
Amanhã: 2024-10-29
Semana passada: 2024-10-21
Data formatada: 28/10/2024 17:26
Dias entre as datas: 7
Horário em São Paulo: 2024-10-28T17:26:47.282-03:00[America/Sao_Paulo]
Horário em Nova York: 2024-10-28T15:26:47.282-04:00[America/New_York]

Referências

Tags: java-8 lambda-expressions stream-api optional functional-interfaces

Publicado em 6-19 01:36