Java 8 스트림 내부에서 CHECKED 예외를 발생시키는 방법은 무엇입니까?


287

Java 8 스트림 / 람다에서 CHECKED 예외를 던질 수 있습니까?

다시 말해,이 컴파일과 같은 코드를 만들고 싶습니다.

public List<Class> getClasses() throws ClassNotFoundException {     

    List<Class> classes = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
              .map(className -> Class.forName(className))
              .collect(Collectors.toList());                  
    return classes;
    }

Class.forName()메소드는 위 의 메소드 ClassNotFoundException가를 검사 하므로 컴파일되지 않습니다 .

확인 된 예외를 런타임 예외 안에 래핑하고 래핑되지 않은 체크되지 않은 예외를 대신 throw하고 싶지 않습니다. 확인 된 예외 자체 를 스트림에 추한 try/ 추가하지 않고 던지고 싶습니다catches .


42
짧은 대답은 예외에 관한 규칙을 어 기지 않으면 서 할 수 없다는 것입니다. 물론 속임수를 쓰면 다른 사람들이 기꺼이 그 방법을 보여줄 것입니다. 당신은해야 잡을 예외를하고 그것으로 처리합니다. 랩핑 한 다음 확인 된 예외를 다시 발생 시키려면 안전하게 수행 할 수 있습니다.
Brian Goetz

35
@Brian, 나는 속임수를 말하는 다른 사람들이 필요하지 않습니다. 내 자신을 속이는 방법을 알고 아래 답변에 부정 행위를 게시했습니다. Streams에서 확인 된 예외를 처리하는 좋은 방법이 없다고 결정한 Java 토론에 참여하고 있음을 알고 있습니다.이 질문에 주목 한 것은 놀라운 일이지만 "이것은 "좋지 않음", 이유를 제시하지 않은 다음 다시 시도 / 캐치를 추가합니다.
MarcG

22
@Brian, Frankly 실제로 사람들이 레거시 for-statement를 리팩토링하려고 할 때 그 중 절반은 스트림으로 변환되지만 나머지 절반은 리팩토링을 포기합니다. 그것들은 원래의 문장보다 확실히 더 읽기 어려운 코드를 만든다. 위의 코드 예제에서 "ClassNotFoundException 발생"을 유지하는 한 외부 코드와의 차이점은 없습니다. 이것이 예외에 관한 규칙을 어기는 실제 사례를 알려주시겠습니까?
MarcG

10
검사되지 않은 예외를 래핑하는 래퍼 메서드를 작성하면 "코드 혼란"이의 제기가 해결되고 형식 시스템이 손상되지 않습니다. 확인 된 예외의 "비밀 한 던져"에 의존하는 응답은 호출 코드가 확인 된 예외를 예상하지 않거나 잡을 수 없기 때문에 유형 시스템을 손상시킵니다.
Brian Goetz

14
원래 예외를 풀고 다시 던지기 위해 스트림 주위에 두 번째 시도 / 캐치가 필요하기 때문에 코드 클러 터 이의 제기를 다루지 않습니다. 반대로, 확인 된 예외를 throw하면 throws ClassNotFoundException스트림을 포함하는 메소드 선언에 유지해야 하므로 호출 코드가 확인 된 예외를 예상하고 잡을 수 있습니다.
MarcG

답변:


250

귀하의 질문에 대한 간단한 대답은 다음과 같습니다. 적어도 직접 할 수는 없습니다. 그리고 그것은 당신의 잘못이 아닙니다. 오라클은 그것을 망쳤다. 그들은 확인 된 예외의 개념에 집착하지만 기능적 인터페이스, 스트림, 람다 등을 디자인 할 때 확인 된 예외를 처리하는 것을 일관되게 잊어 버렸습니다. 로버트 C. 마틴 (Robert C. Martin)과 같은 전문가들은 확인 된 예외를 실패한 실험이라고 부릅니다.

내 의견으로는, 이것은 거대한 버그API 와의 사소한 버그 언어 사양은 .

API의 버그는 실제로 함수형 프로그래밍에 상당히 의미가있는 확인 된 예외를 전달할 수있는 기능이 없다는 것입니다. 아래에서 설명 하겠지만, 그러한 시설은 쉽게 가능했을 것입니다.

언어 사양의 버그는 형식 매개 변수가 형식 목록이 허용되는 상황에서만 사용되는 한 형식 매개 변수가 단일 형식 대신 형식 목록을 유추 할 수 없다는 것입니다 ( throws절).

Java 프로그래머는 다음 코드를 컴파일해야합니다.

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

그러나 다음을 제공합니다.

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

기능 인터페이스를 정의하는 방법은 현재 예외를 전달에서 컴파일러를 방지 - 말할 것없는 선언이없는 Stream.map()그 경우 Function.apply() throws E, Stream.map() throws E뿐만 아니라.

누락 된 것은 확인 된 예외를 통과하기위한 유형 매개 변수의 선언입니다. 다음 코드는 이러한 통과 유형 매개 변수가 현재 구문으로 실제로 선언 될 수있는 방법을 보여줍니다. 아래에 설명 된 제한 인 표시된 줄의 특수한 경우를 제외하고이 코드는 예상대로 컴파일되고 동작합니다.

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

의 경우 throwSomeMore우리보고 싶다 IOException놓친되고 있지만, 실제로 그리워 Exception.

형식 유추가 예외의 경우에도 단일 형식을 찾는 것처럼 보이기 때문에 이것은 완벽하지 않습니다. 유형의 추론이 하나의 유형을 필요로하기 때문에, E공통으로 해결하기 위해 필요 superClassNotFoundExceptionIOExceptionException.

형식 목록이 허용되는 위치에서 형식 매개 변수를 사용하는 경우 컴파일러에서 여러 형식을 찾을 수 있도록 형식 유추 정의를 조정해야합니다 ( throws절). 그러면 컴파일러가보고 한 예외 유형 throws은 단일 catch-all 수퍼 유형이 아니라 참조 된 메소드의 확인 된 예외를 원래 선언 한대로 지정됩니다 .

나쁜 소식은 이것이 오라클이 엉망으로 만들었다는 것을 의미합니다. 확실히 사용자 랜드 코드를 깨뜨리지는 않지만 기존 기능 인터페이스에 예외 유형 매개 변수를 도입하면 이러한 인터페이스를 명시 적으로 사용하는 모든 사용자 랜드 코드의 컴파일이 중단됩니다. 이 문제를 해결하려면 새로운 구문 설탕을 발명해야합니다.

짝수 더 나쁜 소식은이 주제는 이미 2010 년에 브라이언 게츠 논의 된 것입니다 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java : (새 링크 http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-June / 001484.html )하지만이 조사는 궁극적으로 진행되지 않았으며, Oracle에서 확인 된 예외와 람다 사이의 상호 작용을 완화 할 수있는 현재의 작업이 없음을 알게되었습니다.


16
흥미 롭군 더 쉬운 병렬 코드를 허용하는 스트림을 좋아하는 사람도 있고 더 깨끗한 코드를 허용하는 사람도 있다고 생각합니다. Brian Goetz는 분명히 병렬 처리에 대해 더 많은 관심을 가지고 있지만 (실제로 Java Concurrency를 작성 했으므로) Robert Martin은 (깨끗한 코드 책을 작성 했으므로) 깨끗한 코드에 대해 더 관심이 있습니다. Boilerplate try / catches는 병렬 처리에 드는 비용이 적기 때문에 스트림 내부에서 확인 된 예외를 사용하는 문제로 인해 Brian Goetz가 놀라지 않을 것입니다. 로버트 마틴 (Robert Martin)은 확인 된 예외가 혼란에 빠지기 때문에 예외를 싫어하는 것도 당연합니다.
MarcG

5
몇 년 안에 스트림 내부에서 확인 된 예외를 처리하는 데 어려움이 다음 두 가지 결과 중 하나를 초래할 것으로 예상됩니다. 내 UtilException 답변입니다. Java-8 스트림이 확인 된 예외 관의 마지막 못인 것입니다. 확인 된 예외가 JDK의 일부라는 사실이 아닙니다. 비즈니스 코드에서 확인 된 예외를 좋아하고 사용하지만 (일부 특정 사용 사례의 경우) 모든 일반적인 JDK 예외 확장 런타임을 선호합니다.
MarcG

9
@Unihedro 기능 인터페이스가 예외를 전달하지 않는다는 문제가 남아 있습니다. 람다 내부try-catch블록 이 필요하며 이는 의미가 없습니다. 람다에서 어떤 식 으로든 사용 되 자마자 문제가 있습니다. 기본적으로, 확인 된 예외를 발생시키는 메소드는 (불량한) 설계에 의해 기능 인터페이스로서 기능 프로그래밍에 직접 참여하는 것에서 제외되었습니다. Class.forNamenames.forEach(Class::forName)
Christian Hujer

26
@ChristianHujer "예외 투명성"탐사는 그저 탐사 (BGGA 제안에서 시작된 탐사)였습니다. 보다 심도 깊은 분석 결과, 가치와 복잡성의 균형이 좋지 않은 것으로 나타 났으며, 심각한 문제 (결정 불가능한 추론 문제로 이어지고 "catch X"가 불충분 함)가있었습니다. 유망한 것-심지어 "명백한"-더 깊은 탐험 후에 결함이있는 것으로 판명되었습니다. 이것은 그러한 경우 중 하나였습니다.
Brian Goetz

13
@BrianGoetz 언급 할 수없는 추론 문제에 대한 공개 정보가 있습니까? 궁금해서 이해하고 싶습니다.
Christian Hujer

169

LambdaExceptionUtil헬퍼 클래스를 사용하면 다음과 같이 Java 스트림에서 확인 된 예외를 사용할 수 있습니다.

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

참고 Class::forName발생 ClassNotFoundException하는, 확인 . 스트림 자체도 ClassNotFoundException검사하고 검사되지 않은 예외는 줄 바꿈하지 않습니다.

public final class LambdaExceptionUtil {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

사용 방법에 대한 다른 많은 예제 (정적으로 가져온 후 LambdaExceptionUtil) :

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }    

참고 1 : 위 클래스 의 rethrow방법은 LambdaExceptionUtil두려움없이 사용될 수 있으며 어떤 상황에서도 사용할 수 있습니다 . 마지막 문제를 해결하는 데 도움을 준 @PaoloC 사용자에게 큰 감사의 말 : 이제 컴파일러는 throw 절을 추가하라고 요청하고 Java 8 스트림에서 기본적으로 확인 된 예외를 던질 수있는 것처럼 모든 것을 요구합니다.


참고 2 : 위 클래스 의 uncheck방법 LambdaExceptionUtil은 보너스 방법이므로 사용하지 않으려면 클래스에서 안전하게 제거 할 수 있습니다. 이를 사용한 경우 다음 유스 케이스, 장점 / 단점 및 제한 사항을 이해하기 전에주의해서 사용하십시오.

uncheck문자 그대로 선언 된 예외를 절대로 throw 할 수없는 메소드를 호출하는 경우 메소드를 사용할 수 있습니다. 예를 들어, new String (byteArr, "UTF-8")은 UnsupportedEncodingException을 발생 시키지만 Java 스펙에서는 UTF-8이 항상 존재하도록 보장합니다. 던지기 선언은 성가신 것이며 최소한의 상용구로 침묵시키는 해결책은 환영합니다.String text = uncheck(() -> new String(byteArr, "UTF-8"));

uncheckthrows 선언을 추가 할 수있는 옵션이 없지만 예외를 던지는 것이 전적으로 적합한 엄격한 인터페이스를 구현하는 경우 메소드를 사용할 수 있습니다 . 예외를 던지는 특권을 얻기 위해 예외를 랩하면 실제로 예외가 발생한 것에 대한 정보를 제공하지 않는 가짜 예외가있는 스택 추적이 생성됩니다. 확인 된 예외를 발생시키지 않는 Runnable.run ()이 좋은 예입니다.

• 어쨌든, uncheck메소드 를 사용하기로 결정한 경우 throws 절없이 CHECKED 예외를 발생시키는 다음 두 가지 결과에 유의하십시오. 1) 호출 코드는 이름으로 잡을 수 없습니다 (시도 할 경우 컴파일러는 다음과 같이 말합니다 : 해당 try 문의 본문에는 예외가 발생하지 않습니다). 어쨌든 원하는 "catch Exception"또는 "catch Throwable"에 의해 기본 프로그램 루프에서 거품이 발생하고 포착 될 수 있습니다. 2) 그것은 최소한의 놀라움의 원칙을 위반합니다. 더 이상 RuntimeException가능한 모든 예외 를 잡을 수 있다고 보장하기에 충분하지 않습니다 . 따라서 프레임 워크 코드에서는 수행하지 말고 완전히 제어하는 ​​비즈니스 코드에서만 수행해야한다고 생각합니다.


4
이 답변이 부당하게 다운되었다고 생각합니다. 코드가 작동합니다. 확인 된 예외는 발생하거나 처리해야합니다. 그것들을 던지려면 스트림을 포함하는 메소드에 "throws 절"을 유지하십시오. 그러나 단순히 래핑하고 다시 던져서 처리하고 싶다면 위의 코드를 사용하여 예외를 "해제"하고 스스로 거품을내는 것을 선호합니다. 내가 아는 유일한 차이점은 버블 링 예외가 RuntimeException을 확장하지 않는다는 것입니다. 나는 순수 주의자들이 그것을 좋아하지 않는다는 것을 안다. 그러나 이것은 "누군가를 물기 위해 다시 올 것이다"? 가능성이 없어 보입니다.
MarcG

4
@Christian Hujer는 downvoter에 정직하기 위해 "이점, 단점 및 제한 사항"설명을 추가하기 전에 이전 버전을 downvoted했습니다. 아마도 그 당시에는 그럴만 한 가치가 있었을 것입니다. 최소한 결과를 이해하고 설명하지 않으면 규칙을 어기는 방법을 누군가에게 가르 칠 수 없습니다. 이 질문을 게시 한 주된 이유는 답변의 단점에 대한 피드백을받는 것이 었습니다. 나는이 피드백을 여기가 아니라 programmers.stackexchange의 다른 질문에서 얻었습니다. 그런 다음 여기로 돌아와서 답변을 업데이트했습니다.
MarcG

16
이것은 유지 보수 할 수없는 코드를 장려하기 때문에 만 downvoted . 이것은 영리한 것이기는하지만 추악한 해킹 이며이 답변을 유용하게 사용할 수는 없습니다. 이것은 또 다른 언어의 "사용하지 마십시오"입니다.
Unihedron

12
@Unihedro 그러나 왜 유지할 수 없었습니까? 왜 그런지 알 수 없습니다. 예가 있습니까?
MarcG

2
제 생각에 @SuppressWarnings ("unchecked")컴파일러 속임수는 완전히 받아 들일 수 없습니다.
Thorbjørn Ravn Andersen

26

당신은 이것을 안전하게 할 수 없습니다. 당신은 속일 수는 있지만 프로그램이 깨져서 필연적으로 누군가를 물게 될 것입니다 (당신이되어야하지만 종종 우리의 부정 행위는 다른 사람을 때립니다).

여기에 조금 더 안전한 방법이 있습니다 (그러나 나는 이것을 권장하지 않습니다).

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

여기서 당신이하고있는 일은 람다에서 예외를 포착하고, 계산이 예외적으로 실패했음을 나타내는 신호를 스트림 파이프 라인에서 던지고 신호를 포착하고 해당 신호에 따라 기본 예외를 throw하는 것입니다. 핵심은 예외가 발생했다는 것을 선언하지 않고 확인 된 예외가 유출되지 않고 항상 합성 예외를 포착한다는 것입니다.


18
그냥 질문입니다. 람다는 확인 된 예외를 컨텍스트에서 전파 할 수없는 디자인 결정은 무엇입니까? 나는 등의 기능적 인터페이스 Functionthrows아무것도 아니라는 것을 이해합니다 . 그냥 궁금 해서요
fge

4
그렇게 throw w.cause;하면 컴파일러가 메소드가 던지거나 잡히지 않는다고 불평하지 Throwable않습니까? 따라서 캐스트 IOException가 필요할 수 있습니다. 또한 람다가 여러 유형의 instanceof검사 예외를 던지면 검사 된 예외가 발생한 예외를 확인하기 위해 검사 가 약간의 검사 (또는 비슷한 목적의 다른 것)로 인해 추악 해졌습니다 .
Victor Stafusa

10
@schatten 한 가지 이유는 WE를 포착하는 것을 잊어 버릴 수 있으며 이상한 예외 (아무도 처리 방법을 모르는 사람)가 유출되기 때문입니다. (이 장난감 예제에서는 "예외를 잡았으므로 안전합니다."라고 말할 수 있습니다. 그러나 코드베이스가이 접근 방식을 채택 할 때마다 누군가는 잊게됩니다. 예외를 무시하려는 유혹은 한계가 없습니다.) 안전하게 사용하는 것은 특정 (사용 사이트, 예외) 조합에만 해당됩니다. 여러 예외 또는 비 독점적 용도로는 적합하지 않습니다.
Brian Goetz

2
@hoodaticus 동의합니다. 그 말에 따르면, stackoverflow.com/a/30974991/2365724에 표시된 것처럼 점점 더 줄 바꿈하는 것을 선호합니까 (위에서 볼 수 있듯이 "잊어 버릴 위험"증가) 또는 4 개의 영리한 인터페이스를 만들고 랩핑없이 람다를 사용 합니까? 감사합니다
PaoloC 2016 년

10
솔직히이 솔루션은 완전히 작동하지 않습니다. 스트림의 요점은 보일러 플레이트를 줄이는 것이 아니라 늘리는 것이라고 생각했습니다.
wvdz

24

당신은 할 수 있습니다!

@marcg 확장 UtilException하고 throw E필요한 곳에 추가 :이 방법으로 컴파일러는 throw 절 과 모든 것을 추가하도록 요청합니다. 확인 된 예외를 기본적으로 throw 할 수있는 것처럼 Java 8 스트림에서 .

지침 : LambdaExceptionUtilIDE에서 복사 하여 붙여 넣은 다음 아래에 표시된 것처럼 사용하십시오 LambdaExceptionUtilTest.

public final class LambdaExceptionUtil {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
        return t -> {
            try {
                consumer.accept(t);
            } catch (Exception exception) {
                throwActualException(exception);
            }
        };
    }

    /**
     * .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
     */
    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E  {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception exception) {
                throwActualException(exception);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <E extends Exception> void throwActualException(Exception exception) throws E {
        throw (E) exception;
    }

}

사용법과 행동을 보여주는 몇 가지 테스트 :

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}

1
@setheron 죄송합니다 . <Integer>전에 추가하십시오 map. 실제로 Java 컴파일러는 Integer리턴 유형을 유추 할 수 없습니다 . 다른 모든 것이 정확해야합니다.
PaoloC

1
이것은 나를 위해 일했습니다. 예외 처리를 강제함으로써 MarcG의 답변을 완벽하게 만들었습니다.
Skychan

1
위의 문제에 대한 해결책 :이 Consumer <ThingType>과 같은 변수를 선언하십시오. expression = rethrowConsumer ((ThingType thing)-> thing.clone ()); 그런 다음 내부 foreach 내부에서 해당 표현을 사용하십시오.
Skychan

1
@ Skychan :이 수정 된 새 버전에서는 더 이상 예외를 억제하지 않기 때문에 추론 시스템에는 약간 더 어려울 수 있습니다. 아래의 일부 의견에서 Brian Goetz는 "예외 투명성"에 대해 "결정할 수없는 추론 문제"로 이어집니다.
MarcG

3
아주 좋아요 유감스럽게도 유일하게 확인 된 것은 여러 검사 예외를 발생시키는 메소드와 완벽하게 작동하지 않는다는 것입니다. 이 경우 컴파일러는 일반적인 수퍼 타입을 잡을 것입니다 (예 :) Exception.
wvdz

12

NoException (내 프로젝트), jOOλ 's Unchecked , throwing- lambdas , Throwable interfaces 또는 Faux Pas 중 하나를 사용하십시오 .

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));

7

나는 도서관을 썼다확인 된 예외를 throw 할 수 있도록 Stream API를 확장 를 . Brian Goetz의 트릭을 사용합니다.

당신의 코드는

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}

7

이 답변은 17과 비슷하지만 래퍼 예외 정의를 피하십시오.

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }

1
간단하고 효과적인 솔루션입니다.
Lin W

2
이것이 바로 Op가 원하지 않는 것입니다. 람다에서 블록을 사용해보십시오. 또한 try 블록 외부의 다른 코드가 RuntimeException에서 IOException을 래핑하지 않는 한 예상대로 작동합니다. 이를 피하기 위해 사용자 정의 랩퍼 RunTimeException (개인 내부 클래스로 정의)을 사용할 수 있습니다.
Malte Hartwig

5

당신은 할 수 없습니다.

그러나, 당신은 "투하는 람다"를보다 쉽게 ​​조작 할 수있게 해주는 나의 프로젝트 중 하나를 보고 싶을 것입니다 .

귀하의 경우 다음을 수행 할 수 있습니다.

import static com.github.fge.lambdas.functions.Functions.wrap;

final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);

List<Class> classes =
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(f.orThrow(MyException.class))
          .collect(Collectors.toList());

그리고 잡아라 MyException.

이것이 한 예입니다. 다른 예는 .orReturn()기본값 을 사용할 수 있다는 것 입니다.

아직 진행중인 작업이지만 더 많은 작업이 진행될 것입니다. 더 나은 이름, 더 많은 기능 등


2
그러나 원래 확인 된 예외를 throw하려면 스트림 주위에 try / catch를 추가하여 랩을 풀어야합니다. 여전히 끔찍합니다! 원하는 경우 확인되지 않은 예외를 throw 할 수 있고 원하는 경우 스트림에 기본값을 반환 할 수 있다는 아이디어가 마음에 들지만 .orThrowChecked()확인 된 예외 자체를 throw 할 수있는 몇 가지 방법을 프로젝트에 추가해야한다고 생각합니다. . UtilException이 페이지 에서 내 대답을 살펴 보고이 세 번째 가능성을 프로젝트에 추가하려는 아이디어가 마음에 드는지 확인하십시오.
MarcG

"그러나, 만약 당신이 원래의 체크 된 예외를 던지려면 스트림 주위에 try / catch를 추가해야합니다. <-네,하지만 선택의 여지가 없습니다. Lambdas 확인 된 예외를 컨텍스트에서 전파 할 수 없습니다. 이는 디자인 "결정"(개인적으로 결함이지만 오웰로 간주)
fge

당신의 생각에 관해서는, 나는 그것이하는 일을 잘 따르지 않습니다. 죄송합니다. 결국 당신은 여전히 체크되지 않은 채로 던집니다. 그래서 이것은 내가하는 것과 어떻게 다른가요? (다른 인터페이스가 있다는 것을 제외하고)
fge

어쨌든, 당신은 프로젝트에 기여할 수 있습니다! 또한 Stream구현하는 것을 알았 AutoCloseable습니까?
fge

내가 당신에게 물어 보자 : MyException위의 내용은 확인되지 않은 예외가되어야합니까?
MarcG

3

고급 솔루션 위의 의견을 요약하면 API와 같은 빌더로 검사되지 않은 함수에 대해 특수 래퍼를 사용하여 복구, 다시 던지기 및 suppresing을 제공합니다.

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(Try.<String, Class<?>>safe(Class::forName)
                  .handle(System.out::println)
                  .unsafe())
          .collect(toList());

아래 코드는 소비자, 공급 업체 및 기능 인터페이스에 대한 코드입니다. 쉽게 확장 할 수 있습니다. 이 예에서는 일부 공개 키워드가 삭제되었습니다.

클래스 Try 는 클라이언트 코드의 엔드 포인트입니다. 안전한 메소드는 각 함수 유형마다 고유 한 이름을 가질 수 있습니다. CheckedConsumer , CheckedSupplierCheckedFunction는 독립적으로 사용할 수있는 LIB 기능의 아날로그 체크 시도를

CheckedBuilder 는 일부 확인 된 함수에서 예외를 처리하기위한 인터페이스입니다. orTry 는 이전에 실패한 경우 다른 동일한 유형의 함수를 실행할 수 있습니다. handle 은 예외 유형 필터링을 포함한 예외 처리를 제공합니다. 처리기 순서가 중요합니다. 축소 방법은 불안전 하고 다시 throw가 실행 체인의 마지막 예외 rethrows. orElseorElseGet 메소드는 모든 함수가 실패한 경우 선택적 값과 같은 대체 값을 리턴합니다. 또한 방법 suppress가 있습니다. CheckedWrapperCheckedBuilder 의 일반적인 구현입니다.

final class Try {

    public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T> 
        safe(CheckedSupplier<T> supplier) {
        return new CheckedWrapper<>(supplier, 
                (current, next, handler, orResult) -> () -> {
            try { return current.get(); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().get() : orResult.apply(ex);
            }
        });
    }

    public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
        return supplier;
    }

    public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void> 
        safe(CheckedConsumer<T> consumer) {
        return new CheckedWrapper<>(consumer, 
                (current, next, handler, orResult) -> t -> {
            try { current.accept(t); } catch (Exception ex) {
                handler.accept(ex);
                if (next.isPresent()) {
                    next.get().accept(t);
                } else {
                    orResult.apply(ex);
                }
            }
        });
    }

    public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
        return consumer;
    }

    public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R> 
        safe(CheckedFunction<T, R> function) {
        return new CheckedWrapper<>(function, 
                (current, next, handler, orResult) -> t -> {
            try { return current.applyUnsafe(t); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
            }
        });
    }

    public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
        return function;
    }

    @SuppressWarnings ("unchecked")
    static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E { 
        throw (E) exception; 
    }
}

@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
    void acceptUnsafe(T t) throws Exception;
    @Override default void accept(T t) {
        try { acceptUnsafe(t); } catch (Exception ex) {
            Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
    R applyUnsafe(T t) throws Exception;
    @Override default R apply(T t) {
        try { return applyUnsafe(t); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
    T getUnsafe() throws Exception;
    @Override default T get() {
        try { return getUnsafe(); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

interface ReduceFunction<TSafe, TUnsafe, R> {
    TSafe wrap(TUnsafe current, Optional<TSafe> next, 
            Consumer<Throwable> handler, Function<Throwable, R> orResult);
}

interface CheckedBuilder<TSafe, TUnsafe, R> {
    CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);

    CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
            Class<E> exceptionType, Consumer<E> handler);

    CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Class<E> exceptionType, Consumer<? super E> handler);

    TSafe unsafe();
    TSafe rethrow(Function<Throwable, Exception> transformer);
    TSafe suppress();
    TSafe orElse(R value);
    TSafe orElseGet(Supplier<R> valueProvider);
}

final class CheckedWrapper<TSafe, TUnsafe, R> 
        implements CheckedBuilder<TSafe, TUnsafe, R> {

    private final TUnsafe function;
    private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;

    private final CheckedWrapper<TSafe, TUnsafe, R> root;
    private CheckedWrapper<TSafe, TUnsafe, R> next;

    private Consumer<Throwable> handlers = ex -> { };
    private Consumer<Throwable> lastHandlers = ex -> { };

    CheckedWrapper(TUnsafe function, 
            ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
        this.function = function;
        this.reduceFunction = reduceFunction;
        this.root = this;
    }

    private CheckedWrapper(TUnsafe function, 
            CheckedWrapper<TSafe, TUnsafe, R> prev) {
        this.function = function;
        this.reduceFunction = prev.reduceFunction;
        this.root = prev.root;
        prev.next = this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
        return new CheckedWrapper<>(next, this);
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
            Consumer<Throwable> handler) {
        handlers = handlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handle(Class<E> exceptionType, Consumer<E> handler) {
        handlers = handlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Consumer<Throwable> handler) {
        lastHandlers = lastHandlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
        lastHandlers = lastHandlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public TSafe unsafe() {
        return root.reduce(ex -> Try.throwAsUnchecked(ex));
    }

    @Override
    public TSafe rethrow(Function<Throwable, Exception> transformer) {
        return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
    }

    @Override public TSafe suppress() {
        return root.reduce(ex -> null);
    }

    @Override public TSafe orElse(R value) {
        return root.reduce(ex -> value);
    }

    @Override public TSafe orElseGet(Supplier<R> valueProvider) {
        Objects.requireNonNull(valueProvider);
        return root.reduce(ex -> valueProvider.get());
    }

    private TSafe reduce(Function<Throwable, R> orResult) {
        return reduceFunction.wrap(function, 
                Optional.ofNullable(next).map(p -> p.reduce(orResult)), 
                this::handle, orResult);
    }

    private void handle(Throwable ex) {
        for (CheckedWrapper<TSafe, TUnsafe, R> current = this; 
                current != null; 
                current = current.next) {
            current.handlers.accept(ex);
        }
        lastHandlers.accept(ex);
    }
}

3

TL; DR 그냥 롬복을 사용하십시오 @SneakyThrows .

Christian Hujer는 이미 스트림에서 확인 된 예외를 처리하는 것이 Java의 한계로 인해 불가능한 이유를 자세히 설명했습니다.

다른 답변은 언어의 한계를 극복하기위한 요령을 설명했지만 여전히 "확인 된 예외 자체 를 던지고 스트림에 못생긴 시도 / 캐치를 추가하지 않고" 요구 사항을 충족시킬 수 있습니다. 추가 라인 그들 중 일부는 필요 수십 상용구.

IMHO가 다른 모든 것보다 훨씬 깨끗하다는 다른 옵션을 강조하겠습니다 : Lombok 's @SneakyThrows . 다른 답변을 통해 언급되었지만 많은 불필요한 세부 사항에 약간 묻혀있었습니다.

결과 코드는 다음과 같이 간단합니다.

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(className -> getClass(className))
                .collect(Collectors.toList());
    return classes;
}

@SneakyThrows                                 // <= this is the only new code
private Class<?> getClass(String className) {
    return Class.forName(className);
}

우리는 단지 하나 필요 Extract Method합니다 (IDE에 의해 수행) 리팩토링과 일 개 에 대한 추가 라인을 @SneakyThrows. 주석은 모든 상용구를 추가하여 확인 된 예외를 래핑 RuntimeException하지 않고 명시 적으로 선언 할 필요없이 처리 할 수 ​​있도록합니다.


4
롬복의 사용은 권장하지 않습니다.
Dragas

2

또한 검사되지 않은 예외를 래핑하기 위해 래퍼 메소드를 작성할 수 있으며, 동일한 리턴 유형 R을 갖는 다른 기능 인터페이스를 나타내는 추가 매개 변수를 사용하여 래퍼를 향상시킬 수도 있습니다. 이 경우 예외가 발생하면 실행되고 반환되는 함수를 전달할 수 있습니다. 아래 예를 참조하십시오.

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}

2

원래 문제에 대한 다른 견해 나 해결책이 있습니다. 여기에서는 예외가 발생했을 때 사례를 감지하고 처리하는 옵션으로 유효한 값의 하위 집합 만 처리하는 코드를 작성하는 옵션이 있음을 보여줍니다.

    @Test
    public void getClasses() {

        String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
        List<Class> classes =
                Stream.of(classNames)
                        .map(className -> {
                            try {
                                return Class.forName(className);
                            } catch (ClassNotFoundException e) {
                                // log the error
                                return null;
                            }
                        })
                        .filter(c -> c != null)
                        .collect(Collectors.toList());

        if (classes.size() != classNames.length) {
            // add your error handling here if needed or process only the resulting list
            System.out.println("Did not process all class names");
        }

        classes.forEach(System.out::println);
    }

1

위의 의견에 동의합니다 .Stream.map을 사용하면 예외가 발생하지 않는 Function 구현으로 제한됩니다.

그러나 아래와 같이 자신 만의 FunctionalInterface를 작성할 수 있습니다.

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

그런 다음 아래 표시된대로 Lambdas 또는 참조를 사용하여 구현하십시오.

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}

1

map조작에 의해 발생할 수있는 점검 된 예외를 처리하는 기본 제공 방법은 예외를에 포함하는 것 CompletableFuture입니다. ( Optional예외를 보존 할 필요가없는 경우 더 간단한 대안입니다.)이 클래스는 우발적 인 연산을 기능적인 방식으로 표현할 수 있도록 고안되었습니다.

몇 가지 사소한 헬퍼 메소드가 필요하지만 비교적 간결한 코드에 도달 할 수는 있지만 스트림의 결과가 map성공적으로 완료된 작업 에 따라 결정 됩니다. 그 모습은 다음과 같습니다.

    CompletableFuture<List<Class<?>>> classes =
            Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
                  .map(MonadUtils.applyOrDie(Class::forName))
                  .map(cfc -> cfc.thenApply(Class::getSuperclass))
                  .collect(MonadUtils.cfCollector(ArrayList::new,
                                                  List::add,
                                                  (List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
                                                  x -> x));
    classes.thenAccept(System.out::println)
           .exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });

결과는 다음과 같습니다.

[class java.lang.Object, class java.lang.Number, class java.lang.Number]

applyOrDie메소드는 Function예외를 발생 Function시키고 이미 완료된 것을 리턴하는 로 변환합니다.CompletableFuture 원래 함수의 결과로 정상적으로 완료되거나 예외적으로 throw 된 예외 .

두 번째 map작업은 이제 Stream<CompletableFuture<T>>단지 Stream<T>. CompletableFuture업스트림 작업이 성공한 경우에만이 작업을 실행합니다. API는 이러한 설명을 제공하지만 비교적 고통스럽지 않습니다.

collect단계에 도달 할 때까지는 여기에는 매우 중요한 도우미 방법이 필요합니다. 우리는 "리프트"일반 수집 작업 (이 경우, 원하는 toList()은 "내부") CompletableFuture- cfCollector()수 있도록 우리가 할 것을 사용하여 supplier, accumulator, combiner,과 finisher에 대해 전혀 아무것도 알 필요가 없습니다 CompletableFuture.

헬퍼 메소드는 MonadUtils클래스의 GitHub에서 찾을 수 있으며 여전히 진행중인 작업입니다.


1

아마도 더 좋고 기능적인 방법은 예외를 감싸서 스트림에서 더 전파하는 것입니다. 예를 들어 Try type의 Vavr 을 살펴 보십시오 .

예:

interface CheckedFunction<I, O> {
    O apply(I i) throws Exception; }

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return i -> {
        try {
            return f.apply(i);
        } catch(Exception ex) {

            throw new RuntimeException(ex);
        }
    } }

fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))

또는

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
    throw (E) e;
}

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return arg -> {
        try {
            return f.apply(arg);
        } catch(Exception ex) {
            return throwUnchecked(ex);
        }
    };
}

두 번째 구현은 예외를 a에 래핑하지 않습니다 RuntimeException. throwUnchecked거의 모든 일반 예외는 java에서 검사되지 않은 것으로 취급되기 때문에 작동합니다.


1

이런 종류의 래핑 예외를 사용합니다.

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

이러한 예외를 정적으로 처리해야합니다.

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

온라인으로 사용해보십시오!

어쨌든 첫 번째 rethrow()호출 (예, Java generics ...) 중에 예외가 다시 발생하지만 이 방법을 사용하면 가능한 예외에 대한 엄격한 정적 정의를 얻을 수 있습니다 (에서 선언해야 함 throws). 그리고 아무 instanceof것도 필요 하지 않습니다.


-1

나는이 접근법이 옳다고 생각합니다.

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

내부 체크 예외 포장 Callable하여UndeclaredThrowableException (이 예외에 대한 사용 사례의) 외부로 언 래핑.

예, 나는 그것을 못 생겼습니다.이 경우 람다를 사용하지 말고 병렬 스트림으로 작업하고 병렬화가 코드의 가독성을 정당화하는 객관적인 이점을 제공하지 않는 한 좋은 루프로 돌아갑니다.

다른 많은 사람들이 지적 했듯이이 상황에 대한 해결책이 있으며 그중 하나가 미래 버전의 Java로 만들어지기를 바랍니다.


1
(1) 이와 같은 예를 보여주는 몇 가지 답변이 이미 있으므로 아직 다루지 않은 Q & A에 대한 답변은 무엇입니까? 이와 같은 중복 답변을 게시하면 사이트에 혼란이 추가됩니다. (2) OP는 구체적으로 이것을 원하지 않는다고 말합니다 . "체크 된 예외를 런타임 예외 내에서 랩핑하지 않고 랩핑 된 체크되지 않은 예외를 대신 throw하고 싶지는 않습니다."
Radiodef
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.