Java 점검 예외에 대한 임시 해결책


49

람다 및 기본 메소드 인터페이스에 대한 새로운 Java 8 기능이 많이 있습니다. 그러나 여전히 확인 된 예외로 지루합니다. 예를 들어 객체의 보이는 필드를 모두 나열하려면 간단히 다음과 같이 작성하고 싶습니다.

    Arrays.asList(p.getClass().getFields()).forEach(
        f -> System.out.println(f.get(p))
    );

그러나 get메소드가 Consumer인터페이스 계약에 동의하지 않는 확인 된 예외를 throw 할 수 있으므로 해당 예외를 포착하고 다음 코드를 작성해야합니다.

    Arrays.asList(p.getClass().getFields()).forEach(
            f -> {
                try {
                    System.out.println(f.get(p));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
    );

그러나 대부분의 경우 예외를 a로 throw RuntimeException하고 프로그램에서 컴파일 오류가없는 예외를 처리하도록합니다.

따라서 확인 된 예외 성가심에 대한 논쟁의 여지가있는 해결 방법에 대한 귀하의 의견을 듣고 싶습니다. 이를 위해 다음과 같이 보조 인터페이스 ConsumerCheckException<T>와 유틸리티 기능을 만들었습니다 rethrow( Doval의 의견 에 따라 업데이트 됨 ).

  @FunctionalInterface
  public interface ConsumerCheckException<T>{
      void accept(T elem) throws Exception;
  }

  public class Wrappers {
      public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
        return elem -> {
          try {
            c.accept(elem);
          } catch (Exception ex) {
            /**
             * within sneakyThrow() we cast to the parameterized type T. 
             * In this case that type is RuntimeException. 
             * At runtime, however, the generic types have been erased, so 
             * that there is no T type anymore to cast to, so the cast
             * disappears.
             */
            Wrappers.<RuntimeException>sneakyThrow(ex);
          }
        };
      }

      /**
       * Reinier Zwitserloot who, as far as I know, had the first mention of this
       * technique in 2009 on the java posse mailing list.
       * http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

그리고 지금은 쓸 수 있습니다 :

    Arrays.asList(p.getClass().getFields()).forEach(
            rethrow(f -> System.out.println(f.get(p)))
    );

이것이 체크 된 예외를 돌릴 수있는 가장 좋은 관용구인지 확실하지 않지만 설명 한 것처럼 체크 된 예외를 처리하지 않고 첫 번째 예를 얻는 더 편리한 방법을 원합니다. 이것은 내가 찾은 가장 간단한 방법입니다 그것을하기 위해.



2
Robert의 링크 외에도 Sneakily Throwing Checked Exceptions도 살펴보십시오 . 원한다면 sneakyThrow내부를 사용 rethrow하여 원래의 확인 된 예외를로 감싸는 대신 던질 수 있습니다 RuntimeException. 또는 동일한 작업을 수행하는 Project Lombok@SneakyThrows주석을 사용할 수 있습니다 .
Doval

1
또한 Consumers in forEach은 parallel Streams를 사용할 때 병렬 방식으로 실행될 수 있습니다 . 소비자와의 관계에서 제기 된 던지기는 호출 스레드로 전파되어 1) 적절하지 않을 수도있는 동시에 실행중인 다른 소비자를 중단시키지 않으며 2 명 이상의 소비자가 무언가를 던질 경우에만 던지는 것들 중 하나는 호출 스레드에 의해 볼 수 있습니다.
Joonas Pulakka


2
람다는 너무 못 생겼다.
Tulains Córdova

답변:


16

기술의 장단점 :

  • 호출 코드가 확인 된 예외를 처리하는 경우 스트림을 포함하는 메소드의 throws 절에 예외를 추가해야합니다. 컴파일러는 더 이상 강제로 추가하지 않으므로 잊어 버리기가 더 쉽습니다. 예를 들면 다음과 같습니다.

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
    
  • 호출 코드가 이미 확인 된 예외를 처리하면 컴파일러는 스트림을 포함하는 메소드 선언에 throws 절을 추가하도록 상기시킵니다 (그렇지 않으면 다음과 같이 말하십시오 : 해당 try 문의 본문에 예외가 발생하지 않습니다) .

  • 어쨌든 스트림을 포함하는 메소드 내부에서 확인 된 예외를 포착하기 위해 스트림 자체를 둘러 쌀 수 없습니다 (시도하면 컴파일러는 다음과 같이 말합니다 : 해당 try 문의 본문에는 예외가 발생하지 않습니다).

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

  • 확인 된 예외를 싫어하고 시작하기 위해 Java 언어에 추가해서는 안된다고 생각하는 경우 (많은 사람들이 이런 식으로 생각하며 그중 하나가 아닙니다), 확인 된 예외를 던지기에 추가하지 마십시오. 스트림을 포함하는 메소드의 절. 그런 다음 확인 된 예외는 확인되지 않은 예외처럼 작동합니다.

  • throws 선언을 추가 할 수있는 옵션이없는 엄격한 인터페이스를 구현하고 있지만 예외를 throw하는 것이 전적으로 적합한 경우 예외를 래핑하는 권한을 얻기 위해 예외를 래핑하면 가짜 예외가있는 스택 추적이 발생합니다. 실제로 무엇이 잘못되었는지에 대한 정보를 제공하지 않습니다. 확인 된 예외를 발생시키지 않는 Runnable.run ()이 좋은 예입니다. 이 경우 검사 된 예외를 스트림이 포함 된 메소드의 throws 절에 추가하지 않도록 결정할 수 있습니다.

  • 어쨌든 스트림을 포함하는 메소드의 throws 절에 확인 된 예외를 추가하지 않거나 추가하는 것을 잊은 경우 CHECKED 예외가 발생하는 다음 두 가지 결과에 유의하십시오.

    1. 호출 코드는 이름으로 붙잡을 수 없습니다 (시도하면 컴파일러는 다음과 같이 말합니다 : 해당 try 문의 본문에는 예외가 발생하지 않습니다). 그것은 아마도 "캐치 예외"또는 "캐치 던지기 가능"에 의해 메인 프로그램 루프에서 잡히고 어쩌면 원하는 것일 수도 있습니다.

    2. 그것은 최소한의 놀라움의 원칙을 위반합니다 : 더 이상 모든 예외를 잡을 수 있도록 RuntimeException을 잡기에 충분하지 않습니다. 따라서 프레임 워크 코드에서는 수행하지 말고 완전히 제어하는 ​​비즈니스 코드에서만 수행해야한다고 생각합니다.

참고 문헌 :

참고 : 이 기술을 사용하기로 결정한 경우 LambdaExceptionUtilStackOverflow 에서 도우미 클래스를 복사 할 수 있습니다 : https : //.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8 스트림 . 예제와 함께 완전한 구현 (Function, Consumer, Supplier ...)을 제공합니다.


6

이 예에서 실제로 실패 할 수 있습니까? 그렇게 생각하지 말고 귀하의 경우는 특별 할 수 있습니다. 그것이 정말로 "실패 할 수 없다"고 그 성가신 컴파일러 일이라면 예외를 감싸고 Error"일어날 수 없습니다"라는 주석을 던지고 싶습니다 . 일을 만든다 명확 유지 보수를 위해. 그렇지 않으면 그들은 "어떻게 이런 일이 일어날 수 있을까"와 "도대체 누가 이것을 처리합니까?"

이것은 논쟁의 여지가있는 연습에서 YMMV입니다. 아마 약간의 공감대를 얻을 것입니다.


3
오류가 발생하기 때문에 +1 한 줄짜리 주석이 포함 된 catch 블록에서 몇 시간 동안 디버깅을 한 후에 몇 번이나
Axel

3
오류가 발생하기 때문에 -1입니다. 오류는 JVM에서 무언가 잘못되었음을 나타내며 상위 레벨에서 적절하게 처리 할 수 ​​있습니다. 그런 식으로 선택하면 RuntimeException이 발생합니다. 또 다른 가능한 해결 방법은 주장 (-ea 플래그 필요) 또는 로깅입니다.
duros

3
@duros : 예외가 "일어날 수없는"이유에 따라, 그것은 던져 얻는다는 사실은 무언가가 있음을 나타낼 수 있습니다 심각하게 잘못. 예를 들어, 이를 지원하는 것으로 알려진 clone봉인 된 유형 Foo을 호출 하고이를 던진다 고 가정하십시오 CloneNotSupportedException. 코드가 예기치 않은 다른 종류의 코드와 연결되지 않으면 어떻게 될 수 Foo있습니까? 그런 일이 생기면 무엇이든 신뢰할 수 있습니까?
supercat

1
@ supercat 훌륭한 예입니다. 다른 사람들이 누락 된 문자열 인코딩 또는 MessageDigest에서 예외를 발생시키는 경우 UTF-8 또는 SHA가 누락되면 런타임이 손상되었을 수 있습니다.
user949300

1
@duros 그것은 완전히 오해입니다. An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.(의 Javadoc에서 인용 됨 Error) 이것은 정확히 그런 종류의 상황이므로 부적절한 것은 아니며 Error여기에 던지는 것이 가장 적합한 옵션입니다.
biziclop

1

확인 된 예외가 지연되는이 코드의 다른 버전 :

public class Cocoon {
         static <T extends Throwable> T forgetThrowsClause(Throwable t) throws T{
            throw (T) t;
        }

        public static <X, T extends Throwable> Consumer<X> consumer(PeskyConsumer<X,T> touchyConsumer) throws T {
            return new Consumer<X>() {
                @Override
                public void accept(X t) {
                    try {
                        touchyConsumer.accept(t);
                    } catch (Throwable exc) {
                        Cocoon.<RuntimeException>forgetThrowsClause(exc) ;
                    }

                }
            } ;
        }
// and so on for Function, and other codes from java.util.function
}

마술은 당신이 전화하면 :

 myArrayList.forEach(Cocoon.consumer(MyClass::methodThatThrowsException)) ;

그런 다음 코드에서 예외를 잡아야합니다.


요소가 다른 스레드에서 소비되는 병렬 스트림에서 실행될 경우 어떻게됩니까?
prunge

0

sneakyThrow는 멋지다! 나는 당신의 고통을 완전히 느낍니다.

자바를 유지해야한다면 ...

Paguro에는 확인 된 예외를 래핑하는 기능 인터페이스가 있으므로 더 이상 생각할 필요가 없습니다. 여기에는 기능 인터페이스를 사용하고 검사 된 예외를 랩핑하는 Clojure 변환기 (또는 Java 8 스트림) 라인을 따라 변경 불가능한 콜렉션 및 기능 변환이 포함됩니다.

또한이 VAVr이클립스 컬렉션 밖으로 시도.

그렇지 않으면 Kotlin을 사용하십시오.

Kotlin 은 Java와 양방향 호환이 가능하며, 확인 된 예외, 프리미티브 (아직 프로그래머가 생각하지 않아도 됨), 더 나은 유형 시스템, 불변성을 가정합니다. 위의 Paguro 라이브러리를 큐 레이트하더라도 m 가능한 모든 Java 코드를 Kotlin으로 변환합니다. Java에서 내가했던 모든 것은 이제 Kotlin에서하는 것을 선호합니다.


누군가는 이것을 다운 투표했습니다. 괜찮 습니다. 당신이 그 투표를 하지 않았는지 말해주지 않으면 아무 것도 배우지 않을 것입니다.
GlenPeterson

1
github의 라이브러리에 대해 +1, eclipse.org/collections 또는 프로젝트 vavr을 확인하여 기능적이고 변경 불가능한 콜렉션을 제공 할 수 있습니다 . 이것들에 대한 당신의 생각은 무엇입니까?
firephil

-1

확인 된 예외는 매우 유용하며 처리 방법은 코딩하는 제품의 종류에 따라 다릅니다.

  • 도서관
  • 데스크탑 애플리케이션
  • 일부 상자 내에서 실행되는 서버
  • 학문적 운동

일반적으로 라이브러리는 확인 된 예외를 처리하지 말고 공개 API에서 발생 된 것으로 선언해야합니다. 일반 RuntimeExcepton으로 랩핑 할 수있는 드문 경우는 예를 들어 라이브러리 코드 내부에 개인 XML 문서를 작성하고 구문 분석하여 임시 파일을 작성한 다음 읽는 것입니다.

데스크탑 애플리케이션의 경우 고유 한 예외 처리 프레임 워크를 작성하여 제품 개발을 시작해야합니다. 자체 프레임 워크를 사용하면 일반적으로 검사 된 예외를 2 ~ 4 개의 래퍼 (런타임 사용자 정의 하위 클래스)로 랩핑하고 단일 예외 처리기로 처리합니다.

서버의 경우 일반적으로 코드는 일부 프레임 워크 내에서 실행됩니다. 베어 (bare) 서버를 사용하지 않는 경우 위에서 설명한 (런타임 기반) 자체 프레임 워크를 작성하십시오.

학술 연습의 경우 항상 RuntimeExcepton에 직접 래핑 할 수 있습니다. 누군가도 작은 프레임 워크를 만들 수 있습니다.


-1

이 프로젝트를 보고 싶을 수도 있습니다 . 확인 된 예외 및 기능 인터페이스의 문제로 인해 특별히 만들었습니다.

그것으로 당신은 커스텀 코드를 작성하는 대신 이것을 할 수 있습니다 :

// I don't know the type of f, so it's Foo...
final ThrowingConsumer<Foo> consumer = f -> System.out.println(f.get(p));

모든 Throwing * 인터페이스는 비 Throwing 인터페이스를 확장하므로 스트림에서 직접 사용할 수 있습니다.

Arrays.asList(p.getClass().getFields()).forEach(consumer);

아니면 그냥 할 수 있습니다 :

import static com.github.fge.lambdas.consumers.Consumers.wrap;

Arrays.asList(p.getClass().getFields())
    .forEach(wrap(f -> System.out.println(f.get(p)));

그리고 다른 것들.


더 나은 여전히fields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))
user2418306

다운 보터가 설명해 주시겠습니까?
fge
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.