Unntakshåndtering i Stream API
Håndtering av unntak i Stream API krever en spesiell tilnærming. I motsetning til tradisjonelle løkker, hvor en try-catch-blokk kan plasseres inne i løkkekroppen, opererer streams deklarativt, noe som gjør unntakshåndtering mer komplisert.
Hvis et unntak ikke håndteres, avbrytes hele stream-prosesseringen. I denne delen vil du utforske riktig måte å fange og håndtere unntak i Stream API.
Problemet med unntakshåndtering
La oss si at vår nettbutikk har en getTotal()-metode som kan kaste et unntak hvis ordredata er ødelagt eller mangler. For eksempel kan en ordre være lastet fra en database hvor totalbeløpet er lagret som 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;
}
}
Nå, hvis en order har en total mindre enn 0, vil hele Stream API-prosessen avsluttes med et unntak.
List<User> premiumUsers = users.stream()
.filter(User::isActive)
.filter(user -> user.getOrders().stream()
.filter(order -> order.getTotal() >= 10000)
.count() >= 3)
.toList();
Men det er et problem—denne koden vil ikke engang kjøre fordi du ikke håndterer unntakene som kan oppstå i getTotal()-metoden. La oss derfor se på hvordan du kan håndtere unntak i Stream API.
Håndtering av unntak i Stream API
Siden try-catch ikke kan brukes direkte inne i lambdas, finnes det flere strategier for håndtering av unntak i Stream API.
En tilnærming er å fange unntaket direkte inne i map() og erstatte det med et behandlet resultat:
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();
Inne i mapToDouble(), fanges unntaket og det kastes en RuntimeException som spesifiserer hvilken bruker som forårsaket problemet. Denne tilnærmingen er nyttig når det er behov for å umiddelbart stoppe kjøringen og raskt identifisere problemet.
Hoppe over elementer med feil
Noen ganger ønsker du ikke å stoppe hele prosessen når en feil oppstår—du trenger bare å hoppe over problematiske elementer. For å oppnå dette kan du bruke filter() med unntakshåndtering:
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();
Hvis en feil oppstår i mapToDouble(Order::getTotal), ville hele ordrestrømprosessen normalt stoppe. Men try-catch-blokken inne i filter() forhindrer dette, og sørger for at kun den problematiske brukeren blir utelatt fra den endelige listen.
Unntakshåndtering med en Wrapper
For å gjøre koden vår mer robust, kan du opprette en wrapper-metode som muliggjør håndtering av unntak inne i lambdas samtidig som de automatisk fanges opp.
Java tillater ikke at en Function<T, R> kaster kontrollerte unntak. Hvis et unntak oppstår inne i apply(), må du enten håndtere det i metoden eller pakke det inn i en RuntimeException, noe som gjør koden mer kompleks. For å forenkle dette, la oss definere et egendefinert funksjonelt grensesnitt:
@FunctionalInterface
interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
}
Dette grensesnittet fungerer på samme måte som Function<T, R>, men tillater at apply() kan kaste et unntak.
La oss nå opprette en ExceptionWrapper-klasse med en wrap()-metode som konverterer en ThrowingFunction<T, R> til en standard Function<T, R>, og aksepterer en andre parameter som spesifiserer tilbakefallsverdien ved et unntak:
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;
}
};
}
}
Metoden wrap() tar en ThrowingFunction<T, R> og konverterer den til en standard Function<T, R> samtidig som den håndterer unntak. Hvis en feil oppstår, logges meldingen og den angitte standardverdien returneres.
Bruk i Stream API
Anta at du har en liste med brukere i en nettbutikk, og du må finne aktive brukere som har minst tre ordre med verdi over 10 000. Hvis en ordre har et negativt beløp, ønsker du ikke å stoppe strømmen—du returnerer bare 0 som en indikasjon på at prisen var ugyldig.
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; } }; } }
Nå, hvis en ordre inneholder et negativt beløp, stopper ikke programmet, men logger bare en feil og erstatter det med 0,0. Dette gjør databehandlingen mer robust og praktisk for bruk i virkelige situasjoner.
Takk for tilbakemeldingene dine!
Spør AI
Spør AI
Spør om hva du vil, eller prøv ett av de foreslåtte spørsmålene for å starte chatten vår
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
Unntakshåndtering i Stream API
Sveip for å vise menyen
Håndtering av unntak i Stream API krever en spesiell tilnærming. I motsetning til tradisjonelle løkker, hvor en try-catch-blokk kan plasseres inne i løkkekroppen, opererer streams deklarativt, noe som gjør unntakshåndtering mer komplisert.
Hvis et unntak ikke håndteres, avbrytes hele stream-prosesseringen. I denne delen vil du utforske riktig måte å fange og håndtere unntak i Stream API.
Problemet med unntakshåndtering
La oss si at vår nettbutikk har en getTotal()-metode som kan kaste et unntak hvis ordredata er ødelagt eller mangler. For eksempel kan en ordre være lastet fra en database hvor totalbeløpet er lagret som 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;
}
}
Nå, hvis en order har en total mindre enn 0, vil hele Stream API-prosessen avsluttes med et unntak.
List<User> premiumUsers = users.stream()
.filter(User::isActive)
.filter(user -> user.getOrders().stream()
.filter(order -> order.getTotal() >= 10000)
.count() >= 3)
.toList();
Men det er et problem—denne koden vil ikke engang kjøre fordi du ikke håndterer unntakene som kan oppstå i getTotal()-metoden. La oss derfor se på hvordan du kan håndtere unntak i Stream API.
Håndtering av unntak i Stream API
Siden try-catch ikke kan brukes direkte inne i lambdas, finnes det flere strategier for håndtering av unntak i Stream API.
En tilnærming er å fange unntaket direkte inne i map() og erstatte det med et behandlet resultat:
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();
Inne i mapToDouble(), fanges unntaket og det kastes en RuntimeException som spesifiserer hvilken bruker som forårsaket problemet. Denne tilnærmingen er nyttig når det er behov for å umiddelbart stoppe kjøringen og raskt identifisere problemet.
Hoppe over elementer med feil
Noen ganger ønsker du ikke å stoppe hele prosessen når en feil oppstår—du trenger bare å hoppe over problematiske elementer. For å oppnå dette kan du bruke filter() med unntakshåndtering:
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();
Hvis en feil oppstår i mapToDouble(Order::getTotal), ville hele ordrestrømprosessen normalt stoppe. Men try-catch-blokken inne i filter() forhindrer dette, og sørger for at kun den problematiske brukeren blir utelatt fra den endelige listen.
Unntakshåndtering med en Wrapper
For å gjøre koden vår mer robust, kan du opprette en wrapper-metode som muliggjør håndtering av unntak inne i lambdas samtidig som de automatisk fanges opp.
Java tillater ikke at en Function<T, R> kaster kontrollerte unntak. Hvis et unntak oppstår inne i apply(), må du enten håndtere det i metoden eller pakke det inn i en RuntimeException, noe som gjør koden mer kompleks. For å forenkle dette, la oss definere et egendefinert funksjonelt grensesnitt:
@FunctionalInterface
interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
}
Dette grensesnittet fungerer på samme måte som Function<T, R>, men tillater at apply() kan kaste et unntak.
La oss nå opprette en ExceptionWrapper-klasse med en wrap()-metode som konverterer en ThrowingFunction<T, R> til en standard Function<T, R>, og aksepterer en andre parameter som spesifiserer tilbakefallsverdien ved et unntak:
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;
}
};
}
}
Metoden wrap() tar en ThrowingFunction<T, R> og konverterer den til en standard Function<T, R> samtidig som den håndterer unntak. Hvis en feil oppstår, logges meldingen og den angitte standardverdien returneres.
Bruk i Stream API
Anta at du har en liste med brukere i en nettbutikk, og du må finne aktive brukere som har minst tre ordre med verdi over 10 000. Hvis en ordre har et negativt beløp, ønsker du ikke å stoppe strømmen—du returnerer bare 0 som en indikasjon på at prisen var ugyldig.
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; } }; } }
Nå, hvis en ordre inneholder et negativt beløp, stopper ikke programmet, men logger bare en feil og erstatter det med 0,0. Dette gjør databehandlingen mer robust og praktisk for bruk i virkelige situasjoner.
Takk for tilbakemeldingene dine!