Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Aprenda Tratamento de Exceções na Stream API | Aplicações Práticas da Stream API
Stream API

bookTratamento de Exceções na Stream API

O tratamento de exceções na Stream API requer uma abordagem especial. Diferentemente dos laços tradicionais, onde um bloco try-catch pode ser colocado dentro do corpo do laço, as streams operam de forma declarativa, tornando o tratamento de exceções dentro delas mais complexo.

Se uma exceção não for tratada, ela interrompe todo o processamento da stream. Nesta seção, será explorada a maneira correta de capturar e tratar exceções na Stream API.

O Problema do Tratamento de Exceções

Suponha que nossa loja online possua um método getTotal() que pode lançar uma exceção caso os dados do pedido estejam corrompidos ou ausentes. Por exemplo, um pedido pode ser carregado de um banco de dados onde o valor total está armazenado como null.

class Order {
    private final double total;

    public Order(double total) {
        this.total = total;
    }

    public double getTotal() throws Exception {
        if (total < 0) {
            throw new IllegalArgumentException("Invalid order total");
        }
        return total;
    }
}

Agora, se algum order tiver um total menor que 0, todo o processo da Stream API será interrompido com uma exceção.

 List<User> premiumUsers = users.stream()
                .filter(User::isActive)
                .filter(user -> user.getOrders().stream()
                        .filter(order -> order.getTotal() >= 10000)
                        .count() >= 3)
                .toList();

Mas existe um problema—este código nem sequer será executado porque você não está tratando as exceções que podem ocorrer no método getTotal(). Portanto, veja como é possível tratar exceções na Stream API.

Tratamento de Exceções na Stream API

Como o try-catch não pode ser utilizado diretamente dentro de lambdas, existem várias estratégias para o tratamento de exceções na Stream API.

Uma abordagem é capturar a exceção diretamente dentro do map() e substituí-la por um resultado processado:

List<User> premiumUsers = users.stream()
        .filter(User::isActive)
        .filter(user -> user.getOrders().stream()
                .mapToDouble(order -> {
                    try {
                        return order.getTotal();
                    } catch (IllegalArgumentException e) {
                        throw new RuntimeException("Error in user's order: " + user, e);
                    }
                })
                .filter(total -> total >= 10000)
                .count() >= 3)
        .toList();

Dentro do mapToDouble(), a exceção é capturada e uma RuntimeException é lançada, especificando qual usuário causou o problema. Essa abordagem é útil quando é necessário interromper a execução imediatamente e identificar rapidamente o erro.

Pulando Elementos com Erros

Às vezes, não é desejável interromper todo o processo quando ocorre um erro—basta pular os elementos problemáticos. Para isso, é possível utilizar filter() com tratamento de exceções:

List<User> premiumUsers = users.stream()
        .filter(User::isActive)
        .filter(user -> {
            try {
                return user.getOrders().stream()
                        .mapToDouble(Order::getTotal)
                        .filter(total -> total >= 10000)
                        .count() >= 3;
            } catch (Exception e) {
                return false; // Skip users with problematic orders
            }
        })
        .toList();

Se ocorrer um erro em mapToDouble(Order::getTotal), normalmente todo o processamento do fluxo de pedidos seria interrompido. No entanto, o bloco try-catch dentro de filter() evita isso, garantindo que apenas o usuário problemático seja excluído da lista final.

Tratamento de Exceções com um Wrapper

Para tornar o código mais robusto, é possível criar um método wrapper que permite o tratamento de exceções dentro de lambdas enquanto as captura automaticamente.

O Java não permite que um Function<T, R> lance exceções verificadas. Se uma exceção ocorrer dentro de apply(), é necessário tratá-la dentro do método ou encapsulá-la em um RuntimeException, tornando o código mais complexo. Para simplificar, vamos definir uma interface funcional personalizada:

@FunctionalInterface
interface ThrowingFunction<T, R> {
    R apply(T t) throws Exception;
}

Esta interface funciona de forma semelhante a Function<T, R>, mas permite que apply() lance uma exceção.

Agora, vamos criar uma classe ExceptionWrapper com um método wrap() que converte um ThrowingFunction<T, R> em um Function<T, R> padrão e aceita um segundo parâmetro especificando o valor de fallback em caso de exceção:

class ExceptionWrapper {
    // Wrapper for `Function` to handle exceptions
    public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> function, R defaultValue) {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception e) {
                System.out.println(e.getMessage());
                return defaultValue;
            }
        };
    }
}

O método wrap() recebe um ThrowingFunction<T, R> e o converte em um Function<T, R> padrão, tratando exceções. Se ocorrer um erro, a mensagem é registrada e o valor padrão especificado é retornado.

Utilização na Stream API

Considere uma lista de usuários em uma loja online, onde é necessário encontrar usuários ativos que possuam pelo menos três pedidos com valor superior a 10.000. No entanto, se algum pedido apresentar um valor negativo, o stream não deve ser interrompido—deve-se simplesmente retornar 0 para indicar que o preço é inválido.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
package com.example; import java.util.List; import java.util.function.Function; public class Main { public static void main(String[] args) { List<User> users = List.of( new User("Alice", true, List.of(new Order(12000.00), new Order(15000.00), new Order(11000.00))), new User("Bob", true, List.of(new Order(8000.00), new Order(9000.00), new Order(12000.00))), new User("Charlie", false, List.of(new Order(15000.00), new Order(16000.00), new Order(17000.00))), new User("David", true, List.of(new Order(5000.00), new Order(20000.00), new Order(30000.00))), new User("Eve", true, List.of(new Order(null), new Order(10000.00), new Order(10000.00), new Order(12000.00))), new User("Frank", true, List.of(new Order(-5000.00), new Order(10000.00))) // Error: Negative order amount ); List<User> premiumUsers = users.stream() .filter(User::isActive) .filter(user -> user.getOrders().stream() .map(ExceptionWrapper.wrap(Order::getTotal, 0.0)) // Using the wrapper function .filter(total -> total >= 10000) .count() >= 3) .toList(); System.out.println("Premium users: " + premiumUsers); } } // The `Order` class represents a customer's order class Order { private final Double total; public Order(Double total) { this.total = total; } public double getTotal() throws Exception { if (total == null || total < 0) { throw new Exception("Error: Order amount cannot be negative or equal to null!"); } return total; } } // The `User` class represents an online store user class User { private final String name; private final boolean active; private final List<Order> orders; public User(String name, boolean active, List<Order> orders) { this.name = name; this.active = active; this.orders = orders; } public boolean isActive() { return active; } public List<Order> getOrders() { return orders; } public String getName() { return name; } @Override public String toString() { return "User{name='" + name + "'}"; } } // Functional interface for handling exceptions in `Function` @FunctionalInterface interface ThrowingFunction<T, R> { R apply(T t) throws Exception; } // A helper class with wrapper methods for exception handling class ExceptionWrapper { // A wrapper for `Function` that catches exceptions public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> function, R defaultValue) { return t -> { try { return function.apply(t); } catch (Exception e) { System.out.println(e.getMessage()); return defaultValue; } }; } }

Agora, se um pedido contiver um valor negativo, o programa não é interrompido, mas simplesmente registra um erro e o substitui por 0.0. Isso torna o processamento de dados mais robusto e prático para o uso no mundo real.

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 4. Capítulo 3

Pergunte à IA

expand

Pergunte à IA

ChatGPT

Pergunte o que quiser ou experimente uma das perguntas sugeridas para iniciar nosso bate-papo

Awesome!

Completion rate improved to 2.33

bookTratamento de Exceções na Stream API

Deslize para mostrar o menu

O tratamento de exceções na Stream API requer uma abordagem especial. Diferentemente dos laços tradicionais, onde um bloco try-catch pode ser colocado dentro do corpo do laço, as streams operam de forma declarativa, tornando o tratamento de exceções dentro delas mais complexo.

Se uma exceção não for tratada, ela interrompe todo o processamento da stream. Nesta seção, será explorada a maneira correta de capturar e tratar exceções na Stream API.

O Problema do Tratamento de Exceções

Suponha que nossa loja online possua um método getTotal() que pode lançar uma exceção caso os dados do pedido estejam corrompidos ou ausentes. Por exemplo, um pedido pode ser carregado de um banco de dados onde o valor total está armazenado como null.

class Order {
    private final double total;

    public Order(double total) {
        this.total = total;
    }

    public double getTotal() throws Exception {
        if (total < 0) {
            throw new IllegalArgumentException("Invalid order total");
        }
        return total;
    }
}

Agora, se algum order tiver um total menor que 0, todo o processo da Stream API será interrompido com uma exceção.

 List<User> premiumUsers = users.stream()
                .filter(User::isActive)
                .filter(user -> user.getOrders().stream()
                        .filter(order -> order.getTotal() >= 10000)
                        .count() >= 3)
                .toList();

Mas existe um problema—este código nem sequer será executado porque você não está tratando as exceções que podem ocorrer no método getTotal(). Portanto, veja como é possível tratar exceções na Stream API.

Tratamento de Exceções na Stream API

Como o try-catch não pode ser utilizado diretamente dentro de lambdas, existem várias estratégias para o tratamento de exceções na Stream API.

Uma abordagem é capturar a exceção diretamente dentro do map() e substituí-la por um resultado processado:

List<User> premiumUsers = users.stream()
        .filter(User::isActive)
        .filter(user -> user.getOrders().stream()
                .mapToDouble(order -> {
                    try {
                        return order.getTotal();
                    } catch (IllegalArgumentException e) {
                        throw new RuntimeException("Error in user's order: " + user, e);
                    }
                })
                .filter(total -> total >= 10000)
                .count() >= 3)
        .toList();

Dentro do mapToDouble(), a exceção é capturada e uma RuntimeException é lançada, especificando qual usuário causou o problema. Essa abordagem é útil quando é necessário interromper a execução imediatamente e identificar rapidamente o erro.

Pulando Elementos com Erros

Às vezes, não é desejável interromper todo o processo quando ocorre um erro—basta pular os elementos problemáticos. Para isso, é possível utilizar filter() com tratamento de exceções:

List<User> premiumUsers = users.stream()
        .filter(User::isActive)
        .filter(user -> {
            try {
                return user.getOrders().stream()
                        .mapToDouble(Order::getTotal)
                        .filter(total -> total >= 10000)
                        .count() >= 3;
            } catch (Exception e) {
                return false; // Skip users with problematic orders
            }
        })
        .toList();

Se ocorrer um erro em mapToDouble(Order::getTotal), normalmente todo o processamento do fluxo de pedidos seria interrompido. No entanto, o bloco try-catch dentro de filter() evita isso, garantindo que apenas o usuário problemático seja excluído da lista final.

Tratamento de Exceções com um Wrapper

Para tornar o código mais robusto, é possível criar um método wrapper que permite o tratamento de exceções dentro de lambdas enquanto as captura automaticamente.

O Java não permite que um Function<T, R> lance exceções verificadas. Se uma exceção ocorrer dentro de apply(), é necessário tratá-la dentro do método ou encapsulá-la em um RuntimeException, tornando o código mais complexo. Para simplificar, vamos definir uma interface funcional personalizada:

@FunctionalInterface
interface ThrowingFunction<T, R> {
    R apply(T t) throws Exception;
}

Esta interface funciona de forma semelhante a Function<T, R>, mas permite que apply() lance uma exceção.

Agora, vamos criar uma classe ExceptionWrapper com um método wrap() que converte um ThrowingFunction<T, R> em um Function<T, R> padrão e aceita um segundo parâmetro especificando o valor de fallback em caso de exceção:

class ExceptionWrapper {
    // Wrapper for `Function` to handle exceptions
    public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> function, R defaultValue) {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception e) {
                System.out.println(e.getMessage());
                return defaultValue;
            }
        };
    }
}

O método wrap() recebe um ThrowingFunction<T, R> e o converte em um Function<T, R> padrão, tratando exceções. Se ocorrer um erro, a mensagem é registrada e o valor padrão especificado é retornado.

Utilização na Stream API

Considere uma lista de usuários em uma loja online, onde é necessário encontrar usuários ativos que possuam pelo menos três pedidos com valor superior a 10.000. No entanto, se algum pedido apresentar um valor negativo, o stream não deve ser interrompido—deve-se simplesmente retornar 0 para indicar que o preço é inválido.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
package com.example; import java.util.List; import java.util.function.Function; public class Main { public static void main(String[] args) { List<User> users = List.of( new User("Alice", true, List.of(new Order(12000.00), new Order(15000.00), new Order(11000.00))), new User("Bob", true, List.of(new Order(8000.00), new Order(9000.00), new Order(12000.00))), new User("Charlie", false, List.of(new Order(15000.00), new Order(16000.00), new Order(17000.00))), new User("David", true, List.of(new Order(5000.00), new Order(20000.00), new Order(30000.00))), new User("Eve", true, List.of(new Order(null), new Order(10000.00), new Order(10000.00), new Order(12000.00))), new User("Frank", true, List.of(new Order(-5000.00), new Order(10000.00))) // Error: Negative order amount ); List<User> premiumUsers = users.stream() .filter(User::isActive) .filter(user -> user.getOrders().stream() .map(ExceptionWrapper.wrap(Order::getTotal, 0.0)) // Using the wrapper function .filter(total -> total >= 10000) .count() >= 3) .toList(); System.out.println("Premium users: " + premiumUsers); } } // The `Order` class represents a customer's order class Order { private final Double total; public Order(Double total) { this.total = total; } public double getTotal() throws Exception { if (total == null || total < 0) { throw new Exception("Error: Order amount cannot be negative or equal to null!"); } return total; } } // The `User` class represents an online store user class User { private final String name; private final boolean active; private final List<Order> orders; public User(String name, boolean active, List<Order> orders) { this.name = name; this.active = active; this.orders = orders; } public boolean isActive() { return active; } public List<Order> getOrders() { return orders; } public String getName() { return name; } @Override public String toString() { return "User{name='" + name + "'}"; } } // Functional interface for handling exceptions in `Function` @FunctionalInterface interface ThrowingFunction<T, R> { R apply(T t) throws Exception; } // A helper class with wrapper methods for exception handling class ExceptionWrapper { // A wrapper for `Function` that catches exceptions public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> function, R defaultValue) { return t -> { try { return function.apply(t); } catch (Exception e) { System.out.println(e.getMessage()); return defaultValue; } }; } }

Agora, se um pedido contiver um valor negativo, o programa não é interrompido, mas simplesmente registra um erro e o substitui por 0.0. Isso torna o processamento de dados mais robusto e prático para o uso no mundo real.

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 4. Capítulo 3
some-alt