Foutafhandeling 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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394package 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.
Bedankt voor je feedback!
Vraag AI
Vraag AI
Vraag wat u wilt of probeer een van de voorgestelde vragen om onze chat te starten.
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
Foutafhandeling 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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394package 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.
Bedankt voor je feedback!