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

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 .



답변

귀하의 질문에 대한 간단한 대답은 다음과 같습니다. 적어도 직접 할 수는 없습니다. 그리고 그것은 당신의 잘못이 아닙니다. 오라클은 그것을 망쳤다. 그들은 확인 된 예외의 개념에 집착하지만 기능적 인터페이스, 스트림, 람다 등을 디자인 할 때 확인 된 예외를 처리하는 것을 일관되게 잊어 버렸습니다. 로버트 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에서 확인 된 예외와 람다 사이의 상호 작용을 완화 할 수있는 현재의 작업이 없음을 알게되었습니다.


답변

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가능한 모든 예외 를 잡을 수 있다고 보장하기에 충분하지 않습니다 . 따라서 프레임 워크 코드에서는 수행하지 말고 완전히 제어하는 ​​비즈니스 코드에서만 수행해야한다고 생각합니다.


답변

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

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

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하는 것입니다. 핵심은 예외가 발생했다는 것을 선언하지 않고 확인 된 예외가 유출되지 않고 항상 합성 예외를 포착한다는 것입니다.


답변

당신은 할 수 있습니다!

@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());
    }

}


답변

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));


답변

나는 도서관을 썼다확인 된 예외를 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());
}


답변

이 답변은 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
        }