티스토리 뷰
728x90
반응형
스트림은 주의해서 사용하라
- 스트림 API는 다량의 데이터 처리 작업(순차 또는 병렬)을 돕고자 자바 8에 추가되었습니다.
- 스트림 API가 제공하는 추상 개념 2가지
- 스트림은 데이터 원소의 유한 혹은 무한 시퀀스를 의미합니다.
- 스트림 파이프라인은 이 원소들로 수행하는 연산 단계를 표현하는 개념입니다.
- 스트림 안의 데이터 원소들은 객체 참조나 기본 타입값입니다. 기본 타입 값으로는 int, long, double을 지원하는데 기본 타입의 경우 IntStream, LongStream, DoubleStream과 같은 Stream을 사용하는게 성능상 좋습니다.
- Stream - 객체 참조에 대한 Stream
- IntStream - int 타입에 대한 Stream
- LongStream - long 타입에 대한 Stream
- DoubleStream - double 타입에 대한 Stream
💡 스트림 파이프라인
- 스트림 파이프라인은 소스 스트림에서 시작해 종단 연산으로 끝나며, 그 사이에 하나 이상의 중간 연산이 있을 수 있습니다. 각 중간 연산은 스트림을 어떠한 방식으로 변환합니다.
- 종단 연산은 마지막 중간 연산이 내놓은 스트림에 최후의 연산을 가합니다. 원소를 정렬해 컬렉션에 담거나, 특정 원소 하나를 선택하거나, 모든 원소를 출력하는 식입니다.
- 스트림 파이프라인은 지연 평가 됩니다. 평가(호출해서 값이 사용됨)는 종단 연산이 호출될 때 이뤄지며, 종단 연산에 쓰이지 않는 데이터 원소는 계산에 쓰이지 않습니다. 이러한 지연 평가가 무한 스트림을 다룰 수 있게 해주는 열쇠입니다.
- 기본적으로 스트림 파이프라인은 순차적으로 수행되는데, 파이프라인을 병렬로 실행하려면 파이프라인을 구성하는 스트림 중 하나에 parallel 메서드를 호출해 사용하면 되긴하나, 효과를 볼 수 있는 상황은 많지 않습니다.
📜 중간 연산
- filter(Predicate<? super T> predicate): predicate 함수에 맞는 요소만 사용하도록 필터
- map(function<? super T, ? extends R> function): 요소 각각의 function 적용
- flatMap(Function<? super T, ? extends R> function): 스트림의 스트림을 하나씩 스트림으로 변환(평탄화)
- distince(): 중복 제거
- sort(): 기본 정렬
- sort(Comparator<? suprt T> comparator): comparator 함수를 이용하여 정렬
- skip(long n): n개 만큼 스트림 요소를 건너뜀
- limit(long maxSize): maxSize 갯수만큼 출력
📜 종단 연산
- forEach(Consumer<? super T> consumer): Stream 요소를 순회하며 소비
- count(): 스트림 내의 요소 수 반환
- max(comparator<? super T> comparator): 스트림 내의 최대 값 반환
- min(Comparator<? super T> comparator): 스트림 내의 최소 값 반환
- allMatch(Predicate<? super T> predicate): 스트림 내에 모든 요소가 predicate 함수에 만족할 경우 true 반환
- anyMatch(Predicate<? super T> predicate): 스트림 내에 하나의 요소라도 predicate 함수에 만족할 경우 true 반환
- sum(): 스트림 내 요소의 합(IntStream, LongStream, DoubleStream)
- average(): 스트림 내 요소의 평균 값
요구사항 예시
- 사전에서 단어를 읽어온 다음 사용자가 지정한 문자의 길이보다 큰 원소 수를 가진 아나그램 그룹을 출력합니다. 아나그램이란 철자를 구성하는 알파벳이 같고 순서만 다른 언어를 말합니다. ex) abc, bca, cab, bac는 모두 동일한 키(abc)를 가집니다.
💡 과도한 스트림을 사용하여 구현한 Anagram
public class Anagram {
public static void main(String[] args) throws IOException {
List<String> dictionary = Arrays.asList("Dormitory", "Dirty Room", "Hot water", "Worth tea");
dictionary.stream()
.collect(groupingBy(word -> word.chars().sorted()
.collect(StringBuilder::new,
(sb, value) -> sb.append((char)value),
StringBuilder::append).toString()))
.values().stream()
.filter(group -> group.size() >= 2)
.map(group -> group.size() +": " + group)
.forEach(System.out::println);
}
}
💡 적절한 스트림을 사용하여 구현한 Anagram
- 모든 구현을 스트림으로 표현하는 것이 아닌 alphabetize 메서드를 통해 스트림을 적절한 부분에만 사용을 했습니다. 또한, 스트림 변수명을 이해하기 쉽도록 만들었으며, 그 결과 이전 코드보다 훨씬 간결하고 이해하기 쉬운 코드가 만들어졌습니다.
public class Anagrams {
public static void main(String[] args) {
List<String> dictionary = Arrays.asList("Dormitory", "Dirty Room", "Hot water", "Worth tea");
dictionary.stream()
.collect(groupingBy(Anagrams::alphabetize))
.values().stream()
.filter(group -> group.size() >= 2)
.forEach(group -> System.out.println(group.size() +": " + group))
}
// 도우미 메서드
private static String alphabetize(String s) {
char[] a = s.toCharArray();
Arrays.sort(a);;
return new String(a);
}
}
💡 주의 사항
- 자바에서는 char 값을 처리할 때 스트림 사용을 삼가해야 합니다. 그 이유는 자바에서는 char용 스트림을 제공하지 않습니다.
char을 스트림 요소로 사용하면 char이 아닌 int값이 반환됩니다. - 올바른 값을 출력하기 위해서는 명시적으로 형변환을 해줘야하며, 이로 인해 성능이 느려질 수 있습니다. 그렇기 때문에 char 값을 처리하는 경우는 스트림 사용을 삼가하는게 좋습니다.
"Hello Word".chars().forEach(System.out::print); // 721011081081113287111114100
// 명시적 반환
"Hello Word".chars().forEach(c -> System.out.print((char) c)); // Hello Word
💡 스트림은 언제 사용해야 할까?
- 필자는 스트림을 처음 접하고 모든 반복문을 스트림으로 바꾸고 싶은 유혹에 빠져버려 반복문 대신 멋있어 보이는 스트림을 자주 사용하곤 했습니다. 하지만 이처럼 사용하면 절대 안되고, 반복 코드에서는 코드 블럭을 이용하고 스트림 파이프라인에서는 되풀이되는 계산을 함수 객체로 표현하는데 두 방법이 가진 차이점을 고려해 적절히 선택해야합니다. 다음은 코드 블럭으로만 할 수 있는 일이며 이런 경우에는 람다나 스트림을 써서는 안됩니다.
🧨 람다나 스트림을 사용하면 안되는 경우
- 상황 1 - 코드 블럭에서는 범위 안의 지역변수를 읽고 수정할 수 있지만 람다에서는 final(or effective final)만 읽을 수 있고 지역변수를 수정할 수 없습니다.
- 상황 2 - 코드 블럭은 return, break, continue문을 이용해 코드 반복을 제어할 수 있지만 람다로는 할 수 없습니다.
👍 스트림을 사용하기 좋은 경우
- 원소들의 시퀀스를 일관되게 변환합니다.
- 원소들의 시퀀스를 필터링합니다.
- 원소들의 시퀀스를 하나의 연산을 사용해 결합합니다. (덧셈, 연결, 최솟값 구하기...)
- 원소들의 시퀀스를 컬렉션에 모읍니다.
- 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾습니다.
💡 스트림과 반복문
- 아래 같은 상황에서는 사실 취향 차이기 때문에 둘 중 어떤것을 선택해도 상관없습니다.
public class Card {
public enum Suit {
SPADE, HEART, DIAMOND, CLUB
}
public enum Rank {
ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN,
EIGHT, NINE, TEN, JACK, QUEEN, KING
}
private final Suit suit;
private final Rank rank;
public Card(Suit suit, Rank rank) {
this.suit = suit;
this.rank = rank;
}
@Override
public String toString() {
return rank + " of " + suit + "S";
}
// 반복문
public static List<Card> forExam() {
List<Card> result = new ArrayList<>();
for (Suit s : Suit.values()) {
for (Rank r : Rank.values()) {
result.add(new Card(s, r));
}
}
return result;
}
// 스트림
public static List<Card> streamExam() {
return Stream.of(Suit.values())
.flatMap(s ->
Stream.of(Rank.values())
.map(r -> new Card(s, r))
)
.collect(Collectors.toList());
}
}
✔️ 정리
- 스트림을 사용하면 깔끔해지지만, 잘못 사용하면 유지보수와 가독성을 읽을 수 있습니다.
- 스트림, 반복문 정답은 없으니 둘 다 적용해보고 더 나은쪽을 선택하는 것이 좋은거 같습니다.
참고자료)
https://catsbi.oopy.io/5861b4b5-60fa-4a6e-aadf-5caeafe68b1e
728x90
반응형
'스터디 > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - Item47. 반환 타입으로는 스트림보다 컬렉션이 낫다 (0) | 2022.08.01 |
---|---|
이펙티브 자바 - Item46. 스트림에서는 부작용 없는 함수를 사용하라 (0) | 2022.07.31 |
이펙티브 자바 - Item44. 표준 함수형 인터페이스를 사용하라 (0) | 2022.07.30 |
이펙티브 자바 - Item43. 람다보다는 메서드 참조를 사용하라 (0) | 2022.07.30 |
이펙티브 자바 - Item42. 익명클래스 보다는 람다를 사용하라 (0) | 2022.07.30 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- redis sorted set으로 대기열 구현
- java ThreadLocal
- spring boot redisson 분산락 구현
- 서비스 기반 아키텍처
- pipe and filter architecture
- spring boot poi excel download
- transactional outbox pattern spring boot
- JDK Dynamic Proxy와 CGLIB의 차이
- 공간 기반 아키텍처
- spring boot 엑셀 다운로드
- 자바 백엔드 개발자 추천 도서
- java userThread와 DaemonThread
- redis sorted set
- polling publisher spring boot
- 람다 표현식
- 레이어드 아키텍처란
- redis 대기열 구현
- transactional outbox pattern
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- spring boot excel download paging
- spring boot excel download oom
- space based architecture
- spring boot redisson sorted set
- 트랜잭셔널 아웃박스 패턴 스프링부트
- service based architecture
- spring boot redis 대기열 구현
- @ControllerAdvice
- microkernel architecture
- spring boot redisson destributed lock
- pipeline architecture
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
글 보관함