Java 8 Stream API에서 예외 처리 패턴
문제 상황
레거시 코드를 Java 8로 마이그레이션하면서 Stream API를 적용하던 중 checked exception 처리가 막혔다. 파일을 읽는 로직을 Stream으로 변환하려 했는데, IOException이 람다 시그니처와 맞지 않아 컴파일 에러가 발생했다.
// 컴파일 에러 발생
files.stream()
.map(file -> new String(Files.readAllBytes(file)))
.collect(Collectors.toList());
해결 방법
1. Wrapper 메서드 사용
가장 간단한 방법은 checked exception을 unchecked로 감싸는 것이다.
private String readFile(Path file) {
try {
return new String(Files.readAllBytes(file));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
files.stream()
.map(this::readFile)
.collect(Collectors.toList());
2. Function Wrapper 유틸
재사용 가능한 유틸리티를 만들어 사용했다.
@FunctionalInterface
public interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}
// 사용
files.stream()
.map(ThrowingFunction.wrap(file ->
new String(Files.readAllBytes(file))))
.collect(Collectors.toList());
3. Optional 활용
예외 발생 시 빈 값으로 처리하고 싶다면 Optional을 사용했다.
files.stream()
.map(file -> {
try {
return Optional.of(new String(Files.readAllBytes(file)));
} catch (IOException e) {
return Optional.<String>empty();
}
})
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
결론
실무에서는 Wrapper 유틸을 만들어 공통 라이브러리에 추가했다. Vavr 같은 라이브러리도 있지만, 팀 내 외부 의존성 추가가 까다로워 직접 구현했다. Stream API가 편리하지만 예외 처리는 아직 아쉬운 부분이다.