
Java에서 Stream을 사용하다 보면 max()나 min()을 사용할 때 조금 헷갈리는 부분이 있다.
예를 들어 다음과 같은 상황을 생각해보자.
어떤 Member 객체가 있고, 그 안에 intro라는 String 필드가 있다고 가정하자.
그리고 이러한 Member 객체가 ArrayList에 담겨 있다고 해보자.
ArrayList<Member> memberList = new ArrayList<>();
이때 다음과 같은 문제를 해결해야 한다.
intro 문자열의 길이가 가장 긴 Member를 찾아라.
Stream에서는 max()를 이용해서 최대값을 찾을 수 있다.
memberList.stream()
.max(Comparator.comparingInt(m -> m.getIntro().length()));
여기서 중요한 것은 비교 기준을 정해줘야 한다는 것이다.
우리는 intro의 길이를 기준으로 비교해야 하기 때문에 다음과 같은 Comparator를 사용한다.
Comparator.comparingInt(m -> m.getIntro().length())
즉 의미는 다음과 같다.
intro 길이를 기준으로 가장 큰 객체를 찾는다
위 코드의 반환 타입은 다음과 같다.
Optional<Member>
즉 Member가 아니라 Optional<Member>가 반환된다.
처음 Stream을 배울 때 여기서 많이 헷갈리게 된다.
왜 바로 Member를 반환하지 않을까?
이유는 매우 단순하다.
Stream이 비어있을 수도 있기 때문이다.
예를 들어 다음과 같은 상황이 있을 수 있다.
ArrayList<Member> memberList = new ArrayList<>();
이 리스트가 비어있다면
최대값 자체가 존재하지 않는다
그런데 만약 바로 Member를 반환하도록 설계되어 있다면?
NullPointerException
이 발생할 가능성이 생긴다.
그래서 Java에서는 이러한 문제를 해결하기 위해 Optional 타입을 도입했다.
Optional의 의미는 다음과 같다.
값이 있을 수도 있고
값이 없을 수도 있다
예를 들어 다음 코드는 사용할 수 없다.
Member member = memberList.stream()
.max(Comparator.comparingInt(m -> m.getIntro().length()));
왜냐하면 반환 타입이
Optional<Member>
이기 때문이다.
그래서 Optional을 먼저 처리해야 한다.
Optional에서 가장 많이 사용하는 메서드는 ifPresent()이다.
memberList.stream()
.max(Comparator.comparingInt(m -> m.getIntro().length()))
.ifPresent(member -> {
System.out.println(member.getIntro());
});
의미는 다음과 같다.
값이 존재한다면 실행해라
즉 Optional 내부에 값이 있을 때만 코드를 실행한다.
Optional에서 값을 꺼내고 싶다면 orElse()를 사용할 수 있다.
Member member = memberList.stream()
.max(Comparator.comparingInt(m -> m.getIntro().length()))
.orElse(null);
의미
값이 있으면 반환
값이 없으면 null 반환
만약 가장 긴 intro 문자열만 출력하고 싶다면 다음과 같이 사용할 수 있다.
String intro = memberList.stream()
.max(Comparator.comparingInt(m -> m.getIntro().length()))
.map(Member::getIntro)
.orElse("");
Stream의 흐름은 다음과 같다.
Stream<Member>
↓
max
↓
Optional<Member>
↓
map
↓
Optional<String>
↓
orElse
↓
String
Stream에서 max() / min()을 사용할 때 기억해야 할 핵심은 다음이다.
Comparator.comparingInt()
Optional<T>
대표적인 방법
ifPresent()
orElse()
orElseThrow()
map()
처음 Stream을 배울 때 가장 헷갈리는 부분은 다음 두 가지이다.
Comparator
Optional
하지만 이 구조만 이해하면
같은 작업을 매우 간결하게 처리할 수 있다.
💡 개인적으로 느낀 Stream의 핵심 흐름은 다음과 같다.
stream()
filter()
map()
max / min
Optional 처리
이 패턴을 이해하면 대부분의 Stream 문제를 해결할 수 있다.