一种更好的方法来以function的方式处理exception
在Java 8中使用FP惯用语时,exception(尤其是被选中的exception)会严重中断程序逻辑的stream向。下面是一个任意的例子:
String s1 = "oeu", s2 = "2"; Stream.of(s1, s2).forEach(s -> System.out.println(Optional.of(s).map(Integer::parseInt).get()));
上面的代码在不可parsing的string出现exception时中断。 但是说我只想用一个默认值replace它,就像我可以使用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的一个纯粹的扩展,所以它可以是任何现有代码中的一个embedded式替代品,只是它不是最终的Optional
类的子types。 这个类可以被看作与Try
monad有着同样的关系,因为Optional
是Maybe
monad:它从中吸取灵感,但是适应了Java的习惯用法(比如实际上抛出exception,甚至是从非terminal操作) 。
这些是class级遵循的一些重要准则:
-
而不是一元方法,不会忽略Java的exception机制;
-
而是减轻了exception和高阶函数之间的阻抗失配;
-
exception处理不是静态types安全的(由于偷偷摸摸的抛出),但在运行时总是安全的(永远不会吞下除明确的请求之外的exception)。
该课程试图涵盖处理exception的所有典型方法:
- 用一些提供替代值的处理代码来
recover
; -
flatRecover
类似于flatMap
,它允许返回一个新的Exceptional
实例,它将被解包并且当前实例的状态被适当地更新; -
propagate
一个exception,将它从Exceptional
expression式中抛出,并使propagate
调用声明这个exceptiontypes; - 在包装到另一个exception之后
propagate
它( 翻译它); -
handle
它,导致一个空的Exceptional
; - 作为处理的一个特例,用一个空的处理程序块
swallow
它。
propagate
方法允许有select地select他想从他的代码中暴露的检查的exception。 在terminal操作被调用时(例如get
),仍然未处理的exception将被无情地抛出,而无需声明。 这通常被认为是一种先进的和危险的方法,但是经常被用来作为一种方式来减less检查exception的麻烦,并结合没有声明它们的拉姆达形状。 Exceptional
class希望提供一个更清洁,更有select性的偷偷摸摸的替代。
/* * 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<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
提供的每个函数接口都被允许抛出exception呢?
public interface ThrowingSupplier<R, X extends Throwable> { public R get() throws X; }
我们可以使用一些默认的方法来提供你想要的行为。
- 您可以回退到某个默认值或操作
- 或者你可以尝试执行另一个可能会抛出exception的动作
我写了一个库 ,以这种方式重新定义了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
types值的成功,要么是exception失败。 它是Async<T>
的一个子types,作为一个立即完成的asynchronous操作,但在这里并不重要。
创build一个结果 –
Result.success( value ) Result.failure( exception ) Result.call( callable )
结果可以以各种方式进行转换 – 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()
(这是平面图)。 但是,如果感兴趣,可以很容易地提取与Async无关的独立结果API。
有一个叫做better-java-monads的第三方库。 Try
it monad提供了必要的function。 它也有TryMapFunction
和TrySupplier
函数接口来使用带有checkedexception的Try
monad。