Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Leer Foutafhandeling in Stream API | Sectie
Stream-API in Java

bookFoutafhandeling in Stream API

Veeg om het menu te tonen

Het afhandelen van exceptions in de Stream API vereist een specifieke benadering. In tegenstelling tot traditionele lussen, waarbij een try-catch-blok binnen 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 webwinkel 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 als null is opgeslagen.

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 worden beëindigd 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—het is voldoende om alleen de problematische elementen over te slaan. 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 orderstreamverwerking 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 binnen lambdas kunnen worden afgehandeld en 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 binnen de methode afhandelen of omzetten naar een RuntimeException, wat de code complexer maakt. 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 fallbackwaarde specificeert voor het geval er een uitzondering optreedt:

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 naar 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, er is een lijst van gebruikers in een online winkel en het is nodig om actieve gebruikers te vinden die minimaal drie bestellingen hebben met een waarde van meer dan 10.000. Als een bestelling echter een negatief bedrag heeft, moet de stream niet worden gestopt—er wordt gewoon 0 geretourneerd 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 registreert eenvoudig een foutmelding en vervangt het door 0,0. Dit maakt de gegevensverwerking robuuster en praktischer voor toepassing in de echte wereld.

Was alles duidelijk?

Hoe kunnen we het verbeteren?

Bedankt voor je feedback!

Sectie 1. Hoofdstuk 42

Vraag AI

expand

Vraag AI

ChatGPT

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

Sectie 1. Hoofdstuk 42
some-alt