[英]A better approach to handling exceptions in a functional way
在 Java 8 中使用 FP 惯用法时,异常,尤其是检查异常,会严重中断程序逻辑流程。这是一个任意示例:
String s1 = "oeu", s2 = "2";
Stream.of(s1, s2).forEach(s ->
System.out.println(Optional.of(s).map(Integer::parseInt).get()));
当不可解析的字符串出现异常时,上面的代码会中断。 但是说我只想用默认值替换它,就像我可以用Optional
:
Stream.of(s1, s2).forEach(s ->
System.out.println(Optional.of(s)
.map(Integer::parseInt)
.orElse(-1)));
当然,这仍然失败,因为Optional
只处理null
s。 我想要以下内容:
Stream.of(s1, s2).forEach(s ->
System.out.println(
Exceptional.of(s)
.map(Integer::parseInt)
.handle(NumberFormatException.class, swallow())
.orElse(-1)));
注意:这是一个自我回答的问题。
下面提供的是Exceptional
类的完整代码。 它有一个非常大的API,它是Optional
API的纯扩展,因此它可以在任何现有代码中替代它 - 除了它不是最终Optional
类的子类型。 可以看到该类与Try
monad具有相同的关系,因为Optional
与Maybe
monad相同:它从中汲取灵感,但适应了Java习语(例如实际抛出异常,甚至来自非终端操作) 。
这些是课程遵循的一些关键指导原则:
与monadic方法相反,不忽略Java的异常机制;
相反,它减轻了异常和高阶函数之间的阻抗不匹配;
异常处理不是静态类型安全(由于偷偷摸摸),但在运行时始终是安全的(除非在显式请求中,否则永远不会吞下异常)。
该类试图涵盖处理异常的所有典型方法:
recover
; flatRecover
,类似于flatMap
,允许返回一个新的Exceptional
实例,该实例将被解包并且当前实例的状态被适当更新; propagate
异常,从Exceptional
表达式中抛出Exceptional
,并使propagate
调用声明此异常类型; propagate
它( 翻译它); handle
它,导致空的Exceptional
; swallow
它。 propagate
方法允许人们有选择地从他的代码中选择他想要公开的检查异常。 在调用终端操作时(例如get
)仍然未处理的异常将在没有声明的情况下被偷偷抛出。 这通常被认为是一种先进且危险的方法,但是通常采用这种方法来有效地减轻检查异常的麻烦,并结合不声明它们的lambda形状。 Exceptional
类希望为偷偷摸摸的投掷提供更清洁,更具选择性的选择。
/*
* Copyright (c) 2015, Marko Topolnik. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public final class Exceptional<T>
{
private final T value;
private final Throwable exception;
private Exceptional(T value, Throwable exc) {
this.value = value;
this.exception = exc;
}
public static <T> Exceptional<T> empty() {
return new Exceptional<>(null, null);
}
public static <T> Exceptional<T> ofNullable(T value) {
return value != null ? of(value) : empty();
}
public static <T> Exceptional<T> of(T value) {
return new Exceptional<>(Objects.requireNonNull(value), null);
}
public static <T> Exceptional<T> ofNullableException(Throwable exception) {
return exception != null? new Exceptional<>(null, exception) : empty();
}
public static <T> Exceptional<T> ofException(Throwable exception) {
return new Exceptional<>(null, Objects.requireNonNull(exception));
}
public static <T> Exceptional<T> from(TrySupplier<T> supplier) {
try {
return ofNullable(supplier.tryGet());
} catch (Throwable t) {
return new Exceptional<>(null, t);
}
}
public static Exceptional<Void> fromVoid(TryRunnable task) {
try {
task.run();
return new Exceptional<>(null, null);
} catch (Throwable t) {
return new Exceptional<>(null, t);
}
}
public static <E extends Throwable> Consumer<? super E> swallow() {
return e -> {};
}
public T get() {
if (value != null) return value;
if (exception != null) sneakyThrow(exception);
throw new NoSuchElementException("No value present");
}
public T orElse(T other) {
if (value != null) return value;
if (exception != null) sneakyThrow(exception);
return other;
}
public T orElseGet(Supplier<? extends T> other) {
if (value != null) return value;
if (exception != null) sneakyThrow(exception);
return other.get();
}
public Stream<T> stream() {
return value == null ? Stream.empty() : Stream.of(value);
}
public<U> Exceptional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (value == null) return new Exceptional<>(null, exception);
final U u;
try {
u = mapper.apply(value);
} catch (Throwable exc) {
return new Exceptional<>(null, exc);
}
return ofNullable(u);
}
public<U> Exceptional<U> flatMap(Function<? super T, Exceptional<U>> mapper) {
Objects.requireNonNull(mapper);
return value != null ? Objects.requireNonNull(mapper.apply(value)) : empty();
}
public Exceptional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (value == null) return this;
final boolean b;
try {
b = predicate.test(value);
} catch (Throwable t) {
return ofException(t);
}
return b ? this : empty();
}
public <X extends Throwable> Exceptional<T> recover(
Class<? extends X> excType, Function<? super X, T> mapper)
{
Objects.requireNonNull(mapper);
return excType.isInstance(exception) ? ofNullable(mapper.apply(excType.cast(exception))) : this;
}
public <X extends Throwable> Exceptional<T> recover(
Iterable<Class<? extends X>> excTypes, Function<? super X, T> mapper)
{
Objects.requireNonNull(mapper);
for (Class<? extends X> excType : excTypes)
if (excType.isInstance(exception))
return ofNullable(mapper.apply(excType.cast(exception)));
return this;
}
public <X extends Throwable> Exceptional<T> flatRecover(
Class<? extends X> excType, Function<? super X, Exceptional<T>> mapper)
{
Objects.requireNonNull(mapper);
return excType.isInstance(exception) ? Objects.requireNonNull(mapper.apply(excType.cast(exception))) : this;
}
public <X extends Throwable> Exceptional<T> flatRecover(
Iterable<Class<? extends X>> excTypes, Function<? super X, Exceptional<T>> mapper)
{
Objects.requireNonNull(mapper);
for (Class<? extends X> c : excTypes)
if (c.isInstance(exception))
return Objects.requireNonNull(mapper.apply(c.cast(exception)));
return this;
}
public <E extends Throwable> Exceptional<T> propagate(Class<E> excType) throws E {
if (excType.isInstance(exception))
throw excType.cast(exception);
return this;
}
public <E extends Throwable> Exceptional<T> propagate(Iterable<Class<? extends E>> excTypes) throws E {
for (Class<? extends E> excType : excTypes)
if (excType.isInstance(exception))
throw excType.cast(exception);
return this;
}
public <E extends Throwable, F extends Throwable> Exceptional<T> propagate(
Class<E> excType, Function<? super E, ? extends F> translator)
throws F
{
if (excType.isInstance(exception))
throw translator.apply(excType.cast(exception));
return this;
}
public <E extends Throwable, F extends Throwable> Exceptional<T> propagate(
Iterable<Class<E>> excTypes, Function<? super E, ? extends F> translator)
throws F
{
for (Class<? extends E> excType : excTypes)
if (excType.isInstance(exception))
throw translator.apply(excType.cast(exception));
return this;
}
public <E extends Throwable> Exceptional<T> handle(Class<E> excType, Consumer<? super E> action) {
if (excType.isInstance(exception)) {
action.accept(excType.cast(exception));
return empty();
}
return this;
}
public <E extends Throwable> Exceptional<T> handle(Iterable<Class<E>> excTypes, Consumer<? super E> action) {
for (Class<? extends E> excType : excTypes)
if (excType.isInstance(exception)) {
action.accept(excType.cast(exception));
return empty();
}
return this;
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) return value;
if (exception != null) sneakyThrow(exception);
throw exceptionSupplier.get();
}
public boolean isPresent() {
return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
if (exception != null) sneakyThrow(exception);
}
public boolean isException() {
return exception != null;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
return obj instanceof Exceptional && Objects.equals(value, ((Exceptional)obj).value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
throw (T) t;
}
}
@FunctionalInterface
public interface TrySupplier<T> {
T tryGet() throws Throwable;
}
@FunctionalInterface
public interface TryRunnable {
void run() throws Throwable;
}
如果允许java.util.function
提供的每个功能接口都抛出异常怎么办?
public interface ThrowingSupplier<R, X extends Throwable> {
public R get() throws X;
}
我们可以使用一些默认方法来提供您想要的行为。
我编写了一个库 ,用这种方式重新定义了java.util.function
大多数接口。 我甚至提供了一个ThrowingStream
,让你可以使用与常规Stream
相同的API来使用这些新接口。
@FunctionalInterface
public interface ThrowingSupplier<R, X extends Throwable> {
public R get() throws X;
default public Supplier<R> fallbackTo(Supplier<? extends R> supplier) {
ThrowingSupplier<R, Nothing> t = supplier::get;
return orTry(t)::get;
}
default public <Y extends Throwable> ThrowingSupplier<R, Y> orTry(
ThrowingSupplier<? extends R, ? extends Y> supplier) {
Objects.requireNonNull(supplier, "supplier");
return () -> {
try {
return get();
} catch (Throwable x) {
try {
return supplier.get();
} catch (Throwable y) {
y.addSuppressed(x);
throw y;
}
}
};
}
}
( Nothing
是永远不会抛出的RuntimeException
。)
你原来的例子会变成
ThrowingFunction<String, Integer, NumberFormatException> parse = Integer::parseInt;
Function<String, Optional<Integer>> safeParse = parse.fallbackTo(s -> null)
.andThen(Optional::ofNullable);
Stream.of(s1, s2)
.map(safeParse)
.map(i -> i.orElse(-1))
.forEach(System.out::println);
以下是我之前就此主题进行的一些讨论 。
我沿着推理做了一个接口Result<T>
。 Result<T>
要么是类型为T
的值成功,要么是带有异常的失败。 它是Async<T>
的子类型,作为立即完成的异步操作,但这在此并不重要。
要创建结果 -
Result.success( value )
Result.failure( exception )
Result.call( callable )
然后可以以各种方式transform, map, then, peek, catch_, finally_
结果 - transform, map, then, peek, catch_, finally_
等等。例如
Async<Integer> rInt = Result.success( s )
.map( Integer::parseInt )
.peek( System.out::println )
.catch_( NumberFormatException.class, ex->42 ) // default
.catch_( Exception.class, ex-> { ex.printStacktrace(); throw ex; } )
.finally_( ()->{...} )
不幸的是,API专注于Async,因此一些方法返回Async。 其中一些可以被Result覆盖以返回Result; 但有些不能,例如then()
(这是flatmap)。 但是,如果感兴趣,很容易提取与Async无关的独立Result API。
有一个名为better-java-monads的第三方库。 它有Try
monad,它提供必要的功能。 它还具有TryMapFunction
和TrySupplier
功能接口,以使用具有已检查异常的Try
monad。
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Function;
public class RecoverableOptional<T> {
/**
* Common instance for {@code empty()}.
*/
private static final RecoverableOptional<?> EMPTY = new RecoverableOptional<>();
/**
* If non-null, the value; if null, indicates no value is present
*/
private final T value;
/**
* If non-null, the value; if null, indicates recover value is present
*/
private final T defaultValue;
/**
* Constructs an empty instance.
*
* should exist per VM.
*/
private RecoverableOptional() {
this.value = null;
this.defaultValue = null;
}
/**
* Returns an empty {@code Optional} instance. No value is present for this
* Optional.
*
* @param <T> Type of the non-existent value
* @return an empty {@code Optional}
* @apiNote Though it may be tempting to do so, avoid testing if an object
* is empty by comparing with {@code ==} against instances returned by
* {@code Option.empty()}. There is no guarantee that it is a singleton.
*/
public static <T> T empty() {
@SuppressWarnings("unchecked")
RecoverableOptional<T> t = (RecoverableOptional<T>) EMPTY;
return t.get();
}
/**
* Constructs an instance with the value present.
*
* @param value the non-null value to be present
* @throws NullPointerException if value is null
*/
private RecoverableOptional(T value, T value2) {
this.value = Objects.requireNonNull(value);
this.defaultValue = value2;
}
/**
* Returns an {@code Optional} with the specified present non-null value.
*
* @param <T> the class of the value
* @param value the value to be present, which must be non-null
* @return an {@code Optional} with the value present
* @throws NullPointerException if value is null
*/
private static <T> RecoverableOptional<T> of(T value, T value2) {
return new RecoverableOptional<>(value, value2);
}
/**
* Returns an {@code Optional} with the specified present non-null value.
*
* @param <T> the class of the value
* @param value2 the value to be present on recovery
* @return an {@code Optional} with the value present
* @throws NullPointerException if value is null
*/
public <T> RecoverableOptional<T> recoverWith(T value2) {
return new RecoverableOptional<T>((T) value, value2);
}
/**
* Returns an {@code Optional} describing the specified value, if non-null,
* otherwise returns an empty {@code Optional}.
*
* @param <T> the class of the value
* @param value the possibly-null value to describe
* @return an {@code Optional} with a present value if the specified value
* is non-null, otherwise an empty {@code Optional}
*/
public static <T> RecoverableOptional<T> ofNullable(T value, T value2) {
return value == null ? empty() : of(value, value2);
}
/**
* Returns an {@code Optional} describing the specified value, if non-null,
* otherwise returns an empty {@code Optional}.
*
* @param <T> the class of the value
* @param value the possibly-null value to describe
* @return an {@code Optional} with a present value if the specified value
* is non-null, otherwise an empty {@code Optional}
*/
public static <T> RecoverableOptional<T> ofNullable(T value) {
return value == null ? empty() : of(value, null);
}
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
public <U> U map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
try {
return value == null ? null : mapper.apply(value);
} catch (Exception e) {
if (defaultValue == null) {
return null;
}
return mapper.apply(defaultValue);
}
}
}
/**
* Return {@code true} if there is a value present, otherwise {@code false}.
*
* @return {@code true} if there is a value present, otherwise {@code false}
*/
public boolean isPresent() {
return value != null;
}
}
现在测试
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.File;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(SpringRunner.class)
public class RecoverOptionalTest {
@Test
public void recoverSilentlyForExceptionWithRecoverValue() {
File file = mock(File.class);
when(file.getName()).thenThrow(new RuntimeException(""));
String value = RecoverableOptional
.ofNullable(file)
.recoverWith(new File("eliko"))
.map(f -> f.getName());
assertEquals(value, "eliko");
}
@Test
public void recoverSilentlyForExceptionWithNullForNoRecoveryValue() {
File file = mock(File.class);
when(file.getName()).thenThrow(new RuntimeException(""));
String value = RecoverableOptional
.ofNullable(file)
.map(f -> f.getName());
assertNull(value);
}
@Test
public void noRecover() {
File file = new File("check");
String value = RecoverableOptional
.ofNullable(file)
.recoverWith(new File("eliko"))
.map(f -> f.getName());
assertEquals(value, "check");
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.