Java 8 Lambda 표현식의 예외

1. 개요

Java 8에서 Lambda Expressions는 동작을 표현하는 간결한 방법을 제공하여 함수형 프로그래밍을 용이하게하기 시작했습니다. 그러나 JDK에서 제공 하는 기능적 인터페이스 는 예외를 잘 처리하지 못합니다. 예외를 처리 할 때 코드가 장황하고 번거로워집니다.

이 기사에서는 람다 식을 작성할 때 예외를 처리하는 몇 가지 방법을 살펴 보겠습니다.

2. 확인되지 않은 예외 처리

먼저 예제를 통해 문제를 이해합시다.

우리는이 목록을 우리는 일정한 분할이 목록의 모든 요소 50 말을하고 결과를 인쇄 할 :

List integers = Arrays.asList(3, 9, 7, 6, 10, 20); integers.forEach(i -> System.out.println(50 / i));

이 표현은 작동하지만 한 가지 문제가 있습니다. 목록의 요소 중 하나가 0 이면 ArithmeticException : / by zero가 발생합니다 . 기존의 try-catch 블록 을 사용하여 이러한 예외를 기록하고 다음 요소에 대한 실행을 계속 하도록 수정 해 보겠습니다 .

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> { try { System.out.println(50 / i); } catch (ArithmeticException e) { System.err.println( "Arithmetic Exception occured : " + e.getMessage()); } });

의 사용 시도 - 캐치 문제를 해결 없지만,의 간결 람다 표현식이 손실되고해야하는데로는 더 이상 작은 기능입니다.

이 문제를 해결 하기 위해 람다 함수에 대한 람다 래퍼를 작성할 수 있습니다 . 코드가 어떻게 작동하는지 살펴 보겠습니다.

static Consumer lambdaWrapper(Consumer consumer) { return i -> { try { consumer.accept(i); } catch (ArithmeticException e) { System.err.println( "Arithmetic Exception occured : " + e.getMessage()); } }; }
List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(lambdaWrapper(i -> System.out.println(50 / i)));

처음에는 예외 처리를 담당 할 래퍼 메서드를 작성한 다음 람다 식을이 메서드에 매개 변수로 전달했습니다.

래퍼 메서드는 예상대로 작동하지만 기본적으로 람다 식에서 try-catch 블록을 제거하고 다른 메서드로 이동하는 것이며 작성되는 실제 코드 줄 수를 줄이지 않는다고 주장 할 수 있습니다.

이는 래퍼가 특정 사용 사례에 특정한이 경우에 해당되지만 제네릭을 사용하여이 방법을 개선하고 다양한 다른 시나리오에 사용할 수 있습니다.

static  Consumer consumerWrapper(Consumer consumer, Class clazz) { return i -> { try { consumer.accept(i); } catch (Exception ex) { try { E exCast = clazz.cast(ex); System.err.println( "Exception occured : " + exCast.getMessage()); } catch (ClassCastException ccEx) { throw ex; } } }; }
List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach( consumerWrapper( i -> System.out.println(50 / i), ArithmeticException.class));

보시다시피 래퍼 메서드의이 반복은 두 개의 인수, 즉 람다 식과 포착 할 Exception 유형 을 사용합니다. 이 람다 래퍼는 Integers 뿐만 아니라 모든 데이터 유형을 처리 할 수 있으며 수퍼 클래스 Exception이 아닌 특정 유형의 예외를 포착 할 수 있습니다.

또한 메서드 이름이 lambdaWrapper 에서 consumerWrapper로 변경되었습니다 . 이 메서드는 Consumer 형식의 Functional Interface 에 대한 람다 식만 처리하기 때문 입니다. Function , BiFunction , BiConsumer 등과 같은 다른 기능 인터페이스에 대해 유사한 래퍼 메서드를 작성할 수 있습니다 .

3. 확인 된 예외 처리

이전 섹션의 예제를 수정하고 콘솔에 인쇄하는 대신 파일에 씁니다.

static void writeToFile(Integer integer) throws IOException { // logic to write to file which throws IOException }

위의 메서드는 IOException을 throw 할 수 있습니다 .

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> writeToFile(i));

컴파일시 오류가 발생합니다.

java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException

때문에 IOException이는 체크 예외가, 우리는 명시 적으로 처리해야합니다 . 두 가지 옵션이 있습니다.

첫째, 메서드 외부에서 예외를 던지고 다른 곳에서 처리 할 수 ​​있습니다.

또는 람다 식을 사용하는 메서드 내에서 처리 할 수 ​​있습니다.

두 가지 옵션을 모두 살펴 보겠습니다.

3.1. Lambda 식에서 확인 된 예외 발생

main 메서드 에서 IOException 을 선언하면 어떤 일이 발생하는지 살펴 보겠습니다 .

public static void main(String[] args) throws IOException { List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> writeToFile(i)); }

그래도 컴파일하는 동안 처리되지 않은 IOException 과 동일한 오류가 발생합니다 .

java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException

람다식이 익명 내부 클래스와 유사하기 때문입니다.

우리의 경우 writeToFile 메소드는 소비자 기능 인터페이스 의 구현입니다 .

소비자 의 정의를 살펴 보겠습니다 .

@FunctionalInterface public interface Consumer { void accept(T t); }

보시다시피 accept 메소드는 확인 된 예외를 선언하지 않습니다. 이것이 writeToFileIOException 을 throw 할 수없는 이유 입니다.

가장 간단한 방법은 try-catch 블록 을 사용 하고 확인 된 예외를 확인되지 않은 예외로 래핑 한 다음 다시 던지는 것입니다.

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> { try { writeToFile(i); } catch (IOException e) { throw new RuntimeException(e); } }); 

컴파일하고 실행할 코드를 가져옵니다. 그러나이 접근 방식은 이전 섹션에서 이미 논의한 것과 동일한 문제를 소개합니다. 이는 장황하고 번거 롭습니다.

우리는 그것보다 나아질 수 있습니다.

예외를 발생 시키는 단일 accept 메서드를 사용하여 사용자 지정 기능 인터페이스를 만들어 보겠습니다 .

@FunctionalInterface public interface ThrowingConsumer { void accept(T t) throws E; }

이제 예외를 다시 발생시킬 수있는 래퍼 메서드를 구현해 보겠습니다.

static  Consumer throwingConsumerWrapper( ThrowingConsumer throwingConsumer) { return i -> { try { throwingConsumer.accept(i); } catch (Exception ex) { throw new RuntimeException(ex); } }; }

마지막으로 writeToFile 메서드를 사용하는 방법을 단순화 할 수 있습니다 .

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(throwingConsumerWrapper(i -> writeToFile(i)));

이것은 여전히 ​​일종의 해결 방법이지만 최종 결과는 매우 깨끗하고 유지 관리가 훨씬 쉽습니다 .

둘은 ThrowingConsumerthrowingConsumerWrapper은 제네릭 쉽게 우리의 응용 프로그램의 다른 장소에서 재사용 될 수있다.

3.2. Handling a Checked Exception in Lambda Expression

In this final section, we'll modify the wrapper to handle checked exceptions.

Since our ThrowingConsumer interface uses generics, we can easily handle any specific exception.

static  Consumer handlingConsumerWrapper( ThrowingConsumer throwingConsumer, Class exceptionClass) { return i -> { try { throwingConsumer.accept(i); } catch (Exception ex) { try { E exCast = exceptionClass.cast(ex); System.err.println( "Exception occured : " + exCast.getMessage()); } catch (ClassCastException ccEx) { throw new RuntimeException(ex); } } }; }

Let's see how to use it in practice:

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(handlingConsumerWrapper( i -> writeToFile(i), IOException.class));

Note, that the above code handles only IOException, whereas any other kind of exception is rethrown as a RuntimeException .

4. Conclusion

In this article, we showed how to handle a specific exception in lambda expression without losing the conciseness with the help of wrapper methods. We also learned how to write throwing alternatives for the Functional Interfaces present in JDK to either throw or handle a checked exception.

Another way would be to explore the sneaky-throws hack.

Functional Interface 및 래퍼 메서드의 전체 소스 코드는 여기에서 다운로드 할 수 있으며 여기에서 Github에서 클래스를 테스트 할 수 있습니다.

즉시 사용 가능한 작업 솔루션을 찾고 있다면 ThrowingFunction 프로젝트를 확인해 볼 가치가 있습니다.