Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Leer Foutafhandeling in Stream API | Praktische Toepassingen van Stream API
Quizzes & Challenges
Quizzes
Challenges
/
Stream-API

bookFoutafhandeling in Stream API

Het afhandelen van exceptions in de Stream API vereist een specifieke aanpak. In tegenstelling tot traditionele lussen, waarbij een try-catch-blok in het luslichaam kan worden geplaatst, werken streams declaratief, waardoor exception handling binnen streams complexer wordt.

Als een exception niet wordt afgehandeld, onderbreekt dit de volledige streamverwerking. In deze sectie wordt de juiste manier besproken om exceptions in de Stream API op te vangen en af te handelen.

Het probleem van Exception Handling

Stel dat onze online winkel een getTotal()-methode heeft die een exception kan opwerpen als ordergegevens beschadigd of ontbrekend zijn. Bijvoorbeeld, een order kan worden geladen uit een database waarbij het totaalbedrag is opgeslagen als 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;
    }
}

Nu, als een order een totaal minder dan 0 heeft, zal het volledige Stream API-proces stoppen met een exceptie.

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

Maar er is een probleem—deze code zal niet eens uitvoeren omdat de excepties die kunnen optreden in de getTotal() methode niet worden afgehandeld. Laten we daarom bekijken hoe excepties kunnen worden afgehandeld in de Stream API.

Afhandelen van uitzonderingen in Stream API

Aangezien try-catch niet direct kan worden gebruikt binnen lambdas, zijn er verschillende strategieën voor het afhandelen van uitzonderingen in de Stream API.

Een benadering is om de uitzondering direct binnen map() op te vangen en te vervangen door een verwerkt resultaat:

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

Binnen mapToDouble() wordt de uitzondering opgevangen en wordt een RuntimeException gegooid, waarbij wordt aangegeven welke gebruiker het probleem veroorzaakte. Deze aanpak is nuttig wanneer het noodzakelijk is om de uitvoering onmiddellijk te stoppen en snel het probleem te identificeren.

Elementen met fouten overslaan

Soms is het niet wenselijk om het volledige proces te stoppen wanneer zich een fout voordoet—je wilt alleen de problematische elementen overslaan. Dit kan worden bereikt door filter() te gebruiken met foutafhandeling:

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

Als er een fout optreedt in mapToDouble(Order::getTotal), zou normaal gesproken de volledige order stream-verwerking stoppen. Door de try-catch-blok binnen filter() wordt dit voorkomen, zodat alleen de problematische gebruiker wordt uitgesloten van de eindlijst.

Foutafhandeling met een Wrapper

Om onze code robuuster te maken, kun je een wrappermethode creëren waarmee fouten kunnen worden afgehandeld binnen lambdas terwijl deze automatisch worden opgevangen.

Java staat niet toe dat een Function<T, R> gecontroleerde uitzonderingen gooit. Als er een uitzondering optreedt binnen apply(), moet je deze ofwel binnen de methode afhandelen of omzetten naar een RuntimeException, waardoor de code complexer wordt. Om dit te vereenvoudigen, definiëren we een aangepaste functionele interface:

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

Deze interface werkt vergelijkbaar met Function<T, R>, maar staat toe dat apply() een uitzondering gooit.

Nu maken we een ExceptionWrapper klasse met een wrap() methode die een ThrowingFunction<T, R> omzet naar een standaard Function<T, R> en een tweede parameter accepteert die de terugvalwaarde specificeert in het geval van een uitzondering:

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

De methode wrap() neemt een ThrowingFunction<T, R> en zet deze om in een standaard Function<T, R> waarbij uitzonderingen worden afgehandeld. Als er een fout optreedt, wordt het bericht gelogd en wordt de opgegeven standaardwaarde geretourneerd.

Gebruik in Stream API

Stel, je hebt een lijst van gebruikers in een online winkel, en je moet actieve gebruikers vinden die minstens drie bestellingen hebben met een waarde van meer dan 10.000. Als een bestelling echter een negatief bedrag heeft, wil je de stream niet stoppen—je retourneert gewoon 0 als indicatie dat de prijs ongeldig was.

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

Nu, als een order een negatief bedrag bevat, stopt het programma niet maar logt eenvoudig een foutmelding en vervangt het door 0,0. Dit maakt de gegevensverwerking robuuster en praktischer voor toepassingen in de echte wereld.

Was alles duidelijk?

Hoe kunnen we het verbeteren?

Bedankt voor je feedback!

Sectie 4. Hoofdstuk 3

Vraag AI

expand

Vraag AI

ChatGPT

Vraag wat u wilt of probeer een van de voorgestelde vragen om onze chat te starten.

Suggested prompts:

Can you show an example of how to use the ExceptionWrapper in a stream?

What are the pros and cons of each exception handling approach in streams?

How can I customize the fallback value for different types of exceptions?

Awesome!

Completion rate improved to 2.33

bookFoutafhandeling in Stream API

Veeg om het menu te tonen

Het afhandelen van exceptions in de Stream API vereist een specifieke aanpak. In tegenstelling tot traditionele lussen, waarbij een try-catch-blok in het luslichaam kan worden geplaatst, werken streams declaratief, waardoor exception handling binnen streams complexer wordt.

Als een exception niet wordt afgehandeld, onderbreekt dit de volledige streamverwerking. In deze sectie wordt de juiste manier besproken om exceptions in de Stream API op te vangen en af te handelen.

Het probleem van Exception Handling

Stel dat onze online winkel een getTotal()-methode heeft die een exception kan opwerpen als ordergegevens beschadigd of ontbrekend zijn. Bijvoorbeeld, een order kan worden geladen uit een database waarbij het totaalbedrag is opgeslagen als 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;
    }
}

Nu, als een order een totaal minder dan 0 heeft, zal het volledige Stream API-proces stoppen met een exceptie.

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

Maar er is een probleem—deze code zal niet eens uitvoeren omdat de excepties die kunnen optreden in de getTotal() methode niet worden afgehandeld. Laten we daarom bekijken hoe excepties kunnen worden afgehandeld in de Stream API.

Afhandelen van uitzonderingen in Stream API

Aangezien try-catch niet direct kan worden gebruikt binnen lambdas, zijn er verschillende strategieën voor het afhandelen van uitzonderingen in de Stream API.

Een benadering is om de uitzondering direct binnen map() op te vangen en te vervangen door een verwerkt resultaat:

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

Binnen mapToDouble() wordt de uitzondering opgevangen en wordt een RuntimeException gegooid, waarbij wordt aangegeven welke gebruiker het probleem veroorzaakte. Deze aanpak is nuttig wanneer het noodzakelijk is om de uitvoering onmiddellijk te stoppen en snel het probleem te identificeren.

Elementen met fouten overslaan

Soms is het niet wenselijk om het volledige proces te stoppen wanneer zich een fout voordoet—je wilt alleen de problematische elementen overslaan. Dit kan worden bereikt door filter() te gebruiken met foutafhandeling:

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

Als er een fout optreedt in mapToDouble(Order::getTotal), zou normaal gesproken de volledige order stream-verwerking stoppen. Door de try-catch-blok binnen filter() wordt dit voorkomen, zodat alleen de problematische gebruiker wordt uitgesloten van de eindlijst.

Foutafhandeling met een Wrapper

Om onze code robuuster te maken, kun je een wrappermethode creëren waarmee fouten kunnen worden afgehandeld binnen lambdas terwijl deze automatisch worden opgevangen.

Java staat niet toe dat een Function<T, R> gecontroleerde uitzonderingen gooit. Als er een uitzondering optreedt binnen apply(), moet je deze ofwel binnen de methode afhandelen of omzetten naar een RuntimeException, waardoor de code complexer wordt. Om dit te vereenvoudigen, definiëren we een aangepaste functionele interface:

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

Deze interface werkt vergelijkbaar met Function<T, R>, maar staat toe dat apply() een uitzondering gooit.

Nu maken we een ExceptionWrapper klasse met een wrap() methode die een ThrowingFunction<T, R> omzet naar een standaard Function<T, R> en een tweede parameter accepteert die de terugvalwaarde specificeert in het geval van een uitzondering:

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

De methode wrap() neemt een ThrowingFunction<T, R> en zet deze om in een standaard Function<T, R> waarbij uitzonderingen worden afgehandeld. Als er een fout optreedt, wordt het bericht gelogd en wordt de opgegeven standaardwaarde geretourneerd.

Gebruik in Stream API

Stel, je hebt een lijst van gebruikers in een online winkel, en je moet actieve gebruikers vinden die minstens drie bestellingen hebben met een waarde van meer dan 10.000. Als een bestelling echter een negatief bedrag heeft, wil je de stream niet stoppen—je retourneert gewoon 0 als indicatie dat de prijs ongeldig was.

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

Nu, als een order een negatief bedrag bevat, stopt het programma niet maar logt eenvoudig een foutmelding en vervangt het door 0,0. Dit maakt de gegevensverwerking robuuster en praktischer voor toepassingen in de echte wereld.

Was alles duidelijk?

Hoe kunnen we het verbeteren?

Bedankt voor je feedback!

Sectie 4. Hoofdstuk 3
some-alt