Ausnahmebehandlung in der Stream-API
Die Behandlung von Ausnahmen in der Stream-API erfordert einen speziellen Ansatz. Im Gegensatz zu traditionellen Schleifen, bei denen ein try-catch-Block im Schleifenkörper platziert werden kann, arbeiten Streams deklarativ, was die Fehlerbehandlung innerhalb von Streams komplexer macht.
Wird eine Ausnahme nicht behandelt, unterbricht sie die gesamte Stream-Verarbeitung. In diesem Abschnitt wird erläutert, wie Ausnahmen in der Stream-API korrekt abgefangen und behandelt werden.
Das Problem der Fehlerbehandlung
Angenommen, unser Online-Shop verfügt über eine Methode getTotal(), die eine Ausnahme auslösen kann, wenn Bestelldaten beschädigt oder unvollständig sind. Beispielsweise könnte eine Bestellung aus einer Datenbank geladen werden, in der der Gesamtbetrag als null gespeichert ist.
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;
}
}
Wenn eine Bestellung einen Gesamtbetrag unter 0 aufweist, wird der gesamte Stream-API-Prozess mit einer Ausnahme abgebrochen.
List<User> premiumUsers = users.stream()
.filter(User::isActive)
.filter(user -> user.getOrders().stream()
.filter(order -> order.getTotal() >= 10000)
.count() >= 3)
.toList();
Es gibt jedoch ein Problem—dieser Code wird nicht ausgeführt, da die Ausnahmen, die in der Methode getTotal() auftreten können, nicht behandelt werden. Im Folgenden wird erläutert, wie Ausnahmen in der Stream API behandelt werden können.
Ausnahmebehandlung im Stream API
Da try-catch nicht direkt innerhalb von Lambdas verwendet werden kann, gibt es verschiedene Strategien zur Behandlung von Ausnahmen im Stream API.
Eine Möglichkeit besteht darin, die Ausnahme direkt innerhalb von map() abzufangen und durch ein verarbeitetes Ergebnis zu ersetzen:
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();
Innerhalb von mapToDouble() wird die Ausnahme abgefangen und eine RuntimeException ausgelöst, wobei angegeben wird, welcher Benutzer das Problem verursacht hat. Dieser Ansatz ist nützlich, wenn die Ausführung sofort gestoppt und das Problem schnell identifiziert werden soll.
Überspringen von Elementen mit Fehlern
Manchmal soll der gesamte Prozess bei einem Fehler nicht gestoppt werden—es genügt, problematische Elemente zu überspringen. Dafür kann filter() mit Fehlerbehandlung verwendet werden:
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();
Tritt ein Fehler in mapToDouble(Order::getTotal) auf, würde die gesamte Verarbeitung des Bestell-Streams normalerweise gestoppt werden. Der try-catch-Block innerhalb von filter() verhindert dies jedoch und stellt sicher, dass nur der problematische Benutzer aus der Endliste ausgeschlossen wird.
Ausnahmebehandlung mit einem Wrapper
Zur Erhöhung der Robustheit des Codes kann eine Wrapper-Methode erstellt werden, die das Behandeln von Ausnahmen innerhalb von Lambdas ermöglicht und diese automatisch abfängt.
Java erlaubt es nicht, dass eine Function<T, R> geprüfte Ausnahmen wirft. Tritt eine Ausnahme innerhalb von apply() auf, muss sie entweder innerhalb der Methode behandelt oder in eine RuntimeException verpackt werden, was den Code komplexer macht. Zur Vereinfachung definieren wir eine benutzerdefinierte funktionale Schnittstelle:
@FunctionalInterface
interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
}
Diese Schnittstelle funktioniert ähnlich wie Function<T, R>, erlaubt jedoch, dass apply() eine Ausnahme wirft.
Nun erstellen wir eine Klasse ExceptionWrapper mit einer Methode wrap(), die eine ThrowingFunction<T, R> in eine Standard-Function<T, R> umwandelt und einen zweiten Parameter für den Rückgabewert im Ausnahmefall akzeptiert:
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;
}
};
}
}
Die Methode wrap() nimmt eine ThrowingFunction<T, R> entgegen und wandelt sie in eine Standard-Function<T, R> um, wobei Ausnahmen behandelt werden. Tritt ein Fehler auf, wird die Meldung protokolliert und der angegebene Standardwert zurückgegeben.
Verwendung in der Stream-API
Angenommen, es gibt eine Liste von Nutzern in einem Online-Shop und es sollen aktive Nutzer gefunden werden, die mindestens drei Bestellungen mit einem Wert von über 10.000 haben. Falls jedoch eine Bestellung einen negativen Betrag aufweist, soll der Stream nicht abgebrochen werden—es wird einfach 0 zurückgegeben, um anzuzeigen, dass der Preis ungültig war.
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; } }; } }
Wenn eine Bestellung einen negativen Betrag enthält, wird das Programm nicht gestoppt, sondern protokolliert lediglich einen Fehler und ersetzt den Wert durch 0,0. Dies macht die Datenverarbeitung robuster und praxisnäher für den Einsatz in der realen Welt.
Danke für Ihr Feedback!
Fragen Sie AI
Fragen Sie AI
Fragen Sie alles oder probieren Sie eine der vorgeschlagenen Fragen, um unser Gespräch zu beginnen
Awesome!
Completion rate improved to 2.33
Ausnahmebehandlung in der Stream-API
Swipe um das Menü anzuzeigen
Die Behandlung von Ausnahmen in der Stream-API erfordert einen speziellen Ansatz. Im Gegensatz zu traditionellen Schleifen, bei denen ein try-catch-Block im Schleifenkörper platziert werden kann, arbeiten Streams deklarativ, was die Fehlerbehandlung innerhalb von Streams komplexer macht.
Wird eine Ausnahme nicht behandelt, unterbricht sie die gesamte Stream-Verarbeitung. In diesem Abschnitt wird erläutert, wie Ausnahmen in der Stream-API korrekt abgefangen und behandelt werden.
Das Problem der Fehlerbehandlung
Angenommen, unser Online-Shop verfügt über eine Methode getTotal(), die eine Ausnahme auslösen kann, wenn Bestelldaten beschädigt oder unvollständig sind. Beispielsweise könnte eine Bestellung aus einer Datenbank geladen werden, in der der Gesamtbetrag als null gespeichert ist.
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;
}
}
Wenn eine Bestellung einen Gesamtbetrag unter 0 aufweist, wird der gesamte Stream-API-Prozess mit einer Ausnahme abgebrochen.
List<User> premiumUsers = users.stream()
.filter(User::isActive)
.filter(user -> user.getOrders().stream()
.filter(order -> order.getTotal() >= 10000)
.count() >= 3)
.toList();
Es gibt jedoch ein Problem—dieser Code wird nicht ausgeführt, da die Ausnahmen, die in der Methode getTotal() auftreten können, nicht behandelt werden. Im Folgenden wird erläutert, wie Ausnahmen in der Stream API behandelt werden können.
Ausnahmebehandlung im Stream API
Da try-catch nicht direkt innerhalb von Lambdas verwendet werden kann, gibt es verschiedene Strategien zur Behandlung von Ausnahmen im Stream API.
Eine Möglichkeit besteht darin, die Ausnahme direkt innerhalb von map() abzufangen und durch ein verarbeitetes Ergebnis zu ersetzen:
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();
Innerhalb von mapToDouble() wird die Ausnahme abgefangen und eine RuntimeException ausgelöst, wobei angegeben wird, welcher Benutzer das Problem verursacht hat. Dieser Ansatz ist nützlich, wenn die Ausführung sofort gestoppt und das Problem schnell identifiziert werden soll.
Überspringen von Elementen mit Fehlern
Manchmal soll der gesamte Prozess bei einem Fehler nicht gestoppt werden—es genügt, problematische Elemente zu überspringen. Dafür kann filter() mit Fehlerbehandlung verwendet werden:
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();
Tritt ein Fehler in mapToDouble(Order::getTotal) auf, würde die gesamte Verarbeitung des Bestell-Streams normalerweise gestoppt werden. Der try-catch-Block innerhalb von filter() verhindert dies jedoch und stellt sicher, dass nur der problematische Benutzer aus der Endliste ausgeschlossen wird.
Ausnahmebehandlung mit einem Wrapper
Zur Erhöhung der Robustheit des Codes kann eine Wrapper-Methode erstellt werden, die das Behandeln von Ausnahmen innerhalb von Lambdas ermöglicht und diese automatisch abfängt.
Java erlaubt es nicht, dass eine Function<T, R> geprüfte Ausnahmen wirft. Tritt eine Ausnahme innerhalb von apply() auf, muss sie entweder innerhalb der Methode behandelt oder in eine RuntimeException verpackt werden, was den Code komplexer macht. Zur Vereinfachung definieren wir eine benutzerdefinierte funktionale Schnittstelle:
@FunctionalInterface
interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
}
Diese Schnittstelle funktioniert ähnlich wie Function<T, R>, erlaubt jedoch, dass apply() eine Ausnahme wirft.
Nun erstellen wir eine Klasse ExceptionWrapper mit einer Methode wrap(), die eine ThrowingFunction<T, R> in eine Standard-Function<T, R> umwandelt und einen zweiten Parameter für den Rückgabewert im Ausnahmefall akzeptiert:
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;
}
};
}
}
Die Methode wrap() nimmt eine ThrowingFunction<T, R> entgegen und wandelt sie in eine Standard-Function<T, R> um, wobei Ausnahmen behandelt werden. Tritt ein Fehler auf, wird die Meldung protokolliert und der angegebene Standardwert zurückgegeben.
Verwendung in der Stream-API
Angenommen, es gibt eine Liste von Nutzern in einem Online-Shop und es sollen aktive Nutzer gefunden werden, die mindestens drei Bestellungen mit einem Wert von über 10.000 haben. Falls jedoch eine Bestellung einen negativen Betrag aufweist, soll der Stream nicht abgebrochen werden—es wird einfach 0 zurückgegeben, um anzuzeigen, dass der Preis ungültig war.
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; } }; } }
Wenn eine Bestellung einen negativen Betrag enthält, wird das Programm nicht gestoppt, sondern protokolliert lediglich einen Fehler und ersetzt den Wert durch 0,0. Dies macht die Datenverarbeitung robuster und praxisnäher für den Einsatz in der realen Welt.
Danke für Ihr Feedback!