Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Impara Gestione delle Eccezioni nello Stream API | Section
Stream API in Java

bookGestione delle Eccezioni nello Stream API

Scorri per mostrare il menu

La gestione delle eccezioni nell'Stream API richiede un approccio specifico. A differenza dei cicli tradizionali, dove un blocco try-catch può essere inserito all'interno del corpo del ciclo, gli stream operano in modo dichiarativo, rendendo la gestione delle eccezioni al loro interno più complessa.

Se un'eccezione non viene gestita, interrompe l'intero processo dello stream. In questa sezione, verrà illustrato il modo corretto per intercettare e gestire le eccezioni nell'Stream API.

Il problema della gestione delle eccezioni

Supponiamo che il nostro negozio online abbia un metodo getTotal() che può generare un'eccezione se i dati dell'ordine sono corrotti o mancanti. Ad esempio, un ordine potrebbe essere caricato da un database in cui il totale è memorizzato come 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;
    }
}

Ora, se un qualsiasi ordine ha un totale inferiore a 0, l'intero processo della Stream API terminerà con un'eccezione.

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

Tuttavia, c'è un problema: questo codice non verrà nemmeno eseguito perché non stai gestendo le eccezioni che possono verificarsi nel metodo getTotal(). Vediamo quindi come è possibile gestire le eccezioni nella Stream API.

Gestione delle eccezioni nello Stream API

Poiché try-catch non può essere utilizzato direttamente all'interno delle lambdas, esistono diverse strategie per la gestione delle eccezioni nello Stream API.

Un approccio consiste nel catturare l'eccezione direttamente all'interno di map() e sostituirla con un risultato elaborato:

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();

All'interno di mapToDouble(), si cattura l'eccezione e si lancia una RuntimeException, specificando quale utente ha causato il problema. Questo approccio è utile quando è necessario interrompere immediatamente l'esecuzione e identificare rapidamente il problema.

Saltare gli elementi con errori

A volte non si desidera interrompere l'intero processo quando si verifica un errore—è sufficiente saltare gli elementi problematici. Per ottenere questo risultato, è possibile utilizzare filter() con la gestione delle eccezioni:

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 si verifica un errore in mapToDouble(Order::getTotal), l'intero processo di elaborazione dello stream degli ordini normalmente si interromperebbe. Tuttavia, il blocco try-catch all'interno di filter() impedisce ciò, assicurando che solo l'utente problematico venga escluso dall'elenco finale.

Gestione delle eccezioni con un Wrapper

Per rendere il nostro codice più robusto, è possibile creare un metodo wrapper che consente la gestione delle eccezioni all'interno delle lambdas catturandole automaticamente.

Java non permette a Function<T, R> di lanciare eccezioni verificate. Se si verifica un'eccezione all'interno di apply(), è necessario gestirla all'interno del metodo oppure incapsularla in una RuntimeException, rendendo il codice più complesso. Per semplificare questo aspetto, definiamo una interfaccia funzionale personalizzata:

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

Questa interfaccia funziona in modo simile a Function<T, R>, ma consente a apply() di lanciare un'eccezione.

Ora, creiamo una classe ExceptionWrapper con un metodo wrap() che converte una ThrowingFunction<T, R> in una Function<T, R> standard e accetta un secondo parametro che specifica il valore di fallback in caso di eccezione:

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;
            }
        };
    }
}

Il metodo wrap() accetta una ThrowingFunction<T, R> e la converte in una Function<T, R> standard gestendo le eccezioni. Se si verifica un errore, registra il messaggio e restituisce il valore predefinito specificato.

Utilizzo nello Stream API

Si supponga di avere un elenco di utenti in un negozio online e di dover trovare gli utenti attivi che hanno almeno tre ordini di valore superiore a 10.000. Tuttavia, se un ordine presenta un importo negativo, non si desidera interrompere lo stream—si restituisce semplicemente 0 come indicazione che il prezzo era non valido.

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; } }; } }

Ora, se un ordine contiene un importo negativo, il programma non si interrompe ma registra semplicemente un errore e lo sostituisce con 0,0. Questo rende l'elaborazione dei dati più robusta e pratica per l'utilizzo reale.

Tutto è chiaro?

Come possiamo migliorarlo?

Grazie per i tuoi commenti!

Sezione 1. Capitolo 42

Chieda ad AI

expand

Chieda ad AI

ChatGPT

Chieda pure quello che desidera o provi una delle domande suggerite per iniziare la nostra conversazione

Sezione 1. Capitolo 42
some-alt