Utwórz niestandardowy typ zwrotu, który będzie propagował sprawdzony wyjątek. Jest to alternatywa dla tworzenia nowego interfejsu, który odzwierciedla istniejący interfejs funkcjonalny z niewielką modyfikacją „zgłasza wyjątek” w metodzie interfejsu funkcjonalnego.
Definicja
CheckedValueSupplier
public static interface CheckedValueSupplier<V> {
public V get () throws Exception;
}
CheckedValue
public class CheckedValue<V> {
private final V v;
private final Optional<Exception> opt;
public Value (V v) {
this.v = v;
}
public Value (Exception e) {
this.opt = Optional.of(e);
}
public V get () throws Exception {
if (opt.isPresent()) {
throw opt.get();
}
return v;
}
public Optional<Exception> getException () {
return opt;
}
public static <T> CheckedValue<T> returns (T t) {
return new CheckedValue<T>(t);
}
public static <T> CheckedValue<T> rethrows (Exception e) {
return new CheckedValue<T>(e);
}
public static <V> CheckedValue<V> from (CheckedValueSupplier<V> sup) {
try {
return CheckedValue.returns(sup.get());
} catch (Exception e) {
return Result.rethrows(e);
}
}
public static <V> CheckedValue<V> escalates (CheckedValueSupplier<V> sup) {
try {
return CheckedValue.returns(sup.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Stosowanie
// Don't use this pattern with FileReader, it's meant to be an
// example. FileReader is a Closeable resource and as such should
// be managed in a try-with-resources block or in another safe
// manner that will make sure it is closed properly.
// This will not compile as the FileReader constructor throws
// an IOException.
Function<String, FileReader> sToFr =
(fn) -> new FileReader(Paths.get(fn).toFile());
// Alternative, this will compile.
Function<String, CheckedValue<FileReader>> sToFr = (fn) -> {
return CheckedValue.from (
() -> new FileReader(Paths.get("/home/" + f).toFile()));
};
// Single record usage
// The call to get() will propagate the checked exception if it exists.
FileReader readMe = pToFr.apply("/home/README").get();
// List of records usage
List<String> paths = ...; //a list of paths to files
Collection<CheckedValue<FileReader>> frs =
paths.stream().map(pToFr).collect(Collectors.toList());
// Find out if creation of a file reader failed.
boolean anyErrors = frs.stream()
.filter(f -> f.getException().isPresent())
.findAny().isPresent();
Co się dzieje?
Tworzony jest pojedynczy interfejs funkcjonalny, który zgłasza sprawdzony wyjątek ( CheckedValueSupplier
). Będzie to jedyny funkcjonalny interfejs, który pozwala na sprawdzanie wyjątków. Wszystkie inne interfejsy funkcjonalne wykorzystają CheckedValueSupplier
do zawinięcia dowolnego kodu, który zgłasza sprawdzony wyjątek.
CheckedValue
Klasa odbędzie wyniku wykonywania jakiejkolwiek logiki, która rzuca sprawdzonej wyjątek. Zapobiega to propagacji sprawdzonego wyjątku do momentu, w którym kod będzie próbował uzyskać dostęp do wartości, którą zawiera instancja CheckedValue
.
Problemy z tym podejściem.
- Obecnie rzucamy „wyjątek”, skutecznie ukrywając pierwotnie rzucony typ.
- Nie jesteśmy świadomi, że wystąpił wyjątek, dopóki nie
CheckedValue#get()
zostanie wywołany.
Consumer i in
Niektóre interfejsy funkcjonalne (Consumer
na przykład) muszą być obsługiwane w inny sposób, ponieważ nie zapewniają wartości zwracanej.
Funkcja zamiast konsumenta
Jednym z podejść jest użycie funkcji zamiast konsumenta, co ma zastosowanie podczas obsługi strumieni.
List<String> lst = Lists.newArrayList();
// won't compile
lst.stream().forEach(e -> throwyMethod(e));
// compiles
lst.stream()
.map(e -> CheckedValueSupplier.from(
() -> {throwyMethod(e); return e;}))
.filter(v -> v.getException().isPresent()); //this example may not actually run due to lazy stream behavior
Zwiększać
Alternatywnie, zawsze możesz eskalować do RuntimeException
. Istnieją inne odpowiedzi, które obejmują eskalację sprawdzonego wyjątku z poziomu Consumer
.
Nie spożywaj
Po prostu unikaj funkcjonalnych interfejsów i używaj dobrze zaprojektowanej pętli for.