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 | Applicazioni Pratiche Dello Stream API
Stream API

bookGestione delle Eccezioni nello Stream API

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 dove 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 order ha un total 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 è necessario interrompere l'intero processo quando si verifica un errore—basta 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 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 una 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 consideri una lista di utenti in un negozio online, dove è necessario individuare 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 nel mondo reale.

Tutto è chiaro?

Come possiamo migliorarlo?

Grazie per i tuoi commenti!

Sezione 4. Capitolo 3

Chieda ad AI

expand

Chieda ad AI

ChatGPT

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

Awesome!

Completion rate improved to 2.33

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 dove 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 order ha un total 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 è necessario interrompere l'intero processo quando si verifica un errore—basta 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 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 una 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 consideri una lista di utenti in un negozio online, dove è necessario individuare 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 nel mondo reale.

Tutto è chiaro?

Come possiamo migliorarlo?

Grazie per i tuoi commenti!

Sezione 4. Capitolo 3
some-alt