기능적인 방식으로 예외를 처리하는 더 나은 접근 방식
예외, 특히 확인 된 예외는 FP 관용구가 Java 8에서 사용될 때 프로그램 논리의 흐름을 심각하게 방해 할 수 있습니다. 다음은 임의의 예입니다.
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)));
물론 s Optional
만 처리 하기 때문에 여전히 실패합니다 null
. 다음과 같이 싶습니다.
Stream.of(s1, s2).forEach(s ->
System.out.println(
Exceptional.of(s)
.map(Integer::parseInt)
.handle(NumberFormatException.class, swallow())
.orElse(-1)));
참고 : 이것은 스스로 대답하는 질문입니다.
아래에 Exceptional
클래스 의 전체 코드가 있습니다. API의 순수한 확장 인 상당히 큰 API가 Optional
있으므로 최종 Optional
클래스 의 하위 유형이 아니라는 점을 제외하면 기존 코드에서이를 대체 할 수 있습니다 . 클래스는와 같은 관계에있는 것으로 볼 수있는 Try
바와 같이 모나드 Optional
함께 Maybe
그것에서 영감을 그리는 있지만 (심지어 비 단말기 동작에서, 예컨대 실제로 던지는 예외와 같은) 자바 관용구하도록 구성된다 : 모나드.
다음은 수업에서 따르는 몇 가지 주요 지침입니다.
모나 딕 접근 방식과는 반대로 Java의 예외 메커니즘을 무시하지 않습니다.
대신 예외와 고차 함수 간의 임피던스 불일치를 완화합니다.
예외 처리는 정적으로 형식이 안전하지 않지만 (부적절하게 던지기 때문에) 런타임시 항상 안전합니다 (명시적인 요청을 제외하고 예외를 삼키지 않음).
클래스는 예외를 처리하는 모든 일반적인 방법을 다루려고합니다.
recover
대체 값을 제공하는 일부 처리 코드 포함flatRecover
와 유사하게flatMap
,Exceptional
언 래핑 될 새 인스턴스 를 반환 하고 현재 인스턴스의 상태를 적절하게 업데이트 할 수 있습니다.propagate
예외,Exceptional
표현식 에서 던지고propagate
호출이이 예외 유형을 선언하도록합니다.propagate
다른 예외로 래핑 한 후 ( 번역 )handle
결과적으로 비어 있습니다Exceptional
.- 처리의 특별한 경우
swallow
로, 빈 처리기 블록이 있습니다.
이 propagate
접근 방식을 사용하면 코드에서 노출하려는 예외를 선택적으로 선택할 수 있습니다. 터미널 작업이 호출 될 때 처리되지 않은 예외 (예 :)get
는 선언없이 몰래 던집니다. 이것은 종종 고급적이고 위험한 접근 방식으로 간주되지만 그럼에도 불구하고이를 선언하지 않는 람다 셰이프와 함께 확인 된 예외의 성가신 문제를 다소 완화하는 방법으로 자주 사용됩니다. 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
에서 예외를 throw 할 수 있다면 어떨까요?
public interface ThrowingSupplier<R, X extends Throwable> {
public R get() throws X;
}
원하는 동작을 제공하기 위해 몇 가지 기본 방법을 사용할 수 있습니다.
- You could fallback to some default value or action
- Or you could try to perform another action which may throw an exception
I've written a library which redefines most of the interfaces in java.util.function
this way. I even provide a ThrowingStream
which let's you use these new interfaces with the same API as a regular Stream
.
@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
is a RuntimeException
that can never be thrown.)
Your original example would become
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);
Here's some discussions I had previously on this topic.
I made an interface Result<T>
along the reasonings. A Result<T>
is either a success with a value of type T
, or a failure with an Exception. It's a subtype of Async<T>
, as an immediately completed async action, but that is not important here.
To create a result -
Result.success( value )
Result.failure( exception )
Result.call( callable )
Result can then be transformed in various ways - transform, map, then, peek, catch_, finally_
etc. For example
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_( ()->{...} )
Unfortunately the API is focusing on Async, so some methods return Async. Some of them can be overridden by Result to return Result; but some cannot, e.g. then()
(which is flatmap). However, if interested, it's easy to extract a standalone Result API that has nothing to do with Async.
There's a third-party library called better-java-monads. It has the Try
monad which provides the necessary functions. It also has TryMapFunction
and TrySupplier
functional interfaces to use the Try
monad with checked exceptions.
ReferenceURL : https://stackoverflow.com/questions/31270759/a-better-approach-to-handling-exceptions-in-a-functional-way
'development' 카테고리의 다른 글
데이터베이스 유형별 실제 사례 (실제 사례) (0) | 2020.12.25 |
---|---|
IndexNotReadyException-Android 스튜디오 (0) | 2020.12.25 |
Python : 포함 된 패키지 가져 오기 (0) | 2020.12.25 |
Dotfuscator Community Edition은 얼마나 좋은가요? (0) | 2020.12.25 |
주석 (#)은 Vim의 삽입 모드에서 줄 시작으로 이동합니다. (0) | 2020.12.25 |