데브코스 수업에선 구현도 하고 그랬지만, 스트림이 뭔지도 몰랐던 나에겐 무리였다. 고로 이 글에서 스트림이 뭐고 어떻게 그리고 왜 쓰는지 정리해보겠다.
기존의 for문 이나 iterator를 사용하면 코드가 길어짐
가독성 재사용성 떡락 또한 데이터 타입마다 다르게 다루어야한다 귀찮다.
⇒ 스트림은 데이터 소스에 상관없이(데이터 소스를 추상화) 모두 같은 방식으로 다룰 수 있다.
→)스트림을 사용하려면 스트림을 만들어야겠죠.
어떤 스트림을 만드느냐에 따라서 방법이 조금씩 다릅니다.
Arrays.stream(배열명)으로 만들 수 있습니다.String[] strArr = {"a", "b", "c", "d", "e", "f", "g", "h"};
Stream<String> arrStream = Arrays.stream(strArr);
.stream()으로 생성합니다.LinkedList<String> linkedList = new LinkedList<>();
Stream<String> linkedListStream = linkedList.stream();
ArrayList<Integer> arrayList = new ArrayList<>();
Stream<Integer> arrayListStream = arrayList.stream();
builder(), Stream.of())//builder 사용
Stream<String> builderStream = Stream.<String>builder()
.add("a").add("b")
.build();
//Stream.of()사용
Stream<String> streamOf = Stream.of("Hello", "World");
Stream.generate(), Stream.iterate())Stream.generate()
Stream<Integer> randomStream = Stream.generate(() -> (int) (Math.random() * 100)).limit(3);
Stream.generate()는 Supplier<T>람다식을 이용하므로 값을 공급하는(단지 생성하는) 스트림을 만들때 사용한다.
limit()를 사용하지 않으면 무한으로 값이 생성된다 → 무한스트림!
Stream.iterate()
Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2).limit(3
Stream.iterate()는 매개변수가 1개 있는 UnaryOperator람다식을 사용해서 초기값으로 연산식을 통해 연산되는 값이 연속적으로 생성된다.
또한, generate()와 동일하게 limit()를 사용하여 개수를 제한할 수 있다.
IntStream, LongStream, DoubleStream)IntStream intStream = IntStream.range(1,5);
DoubleStream doubleStream = DoubleStream.of(1.2,15.8,2154.8487);
데이터를 가공하는 과정(변형, 필터링… 등)
중간 연산은 메서드체이닝이 된다. (연속적으로 수행)
filter() : 조건에 맞는 데이터 선택(필터링) , if문과 비슷한 역할List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> result = list.stream().filter(n -> n > 3);
result.forEach(System.out::println);
/*출력
4
5
*/map(): 데이터를 변환List<String> strList = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h");
Stream<String> strStream = strList.stream().map(l -> l.toUpperCase());
strStream.forEach(l -> System.out.print(l+" "));
/*출력
A B C D E F G H sorted() : 데이터 정렬 (Comparator를 사용한다.)List<String> words = List.of("apple", "banana", "blueberry", "oranges");
Stream<String> wordsStream = words.stream()
.sorted((s1, s2) -> Integer.compare(s1.length(), s2.length()));
wordsStream.forEach(System.out::println);
//Comparator.comparing()을 사용하면 더 간결
Stream<String> sortedWords = words.stream()
.sorted(Comparator.comparing(String::length)); // 문자열 길이 기준 정렬
sortedWords.forEach(System.out::println);
/*출력
apple
banana
oranges
blueberry
*/distinct() : 중복제거skip(n) : 앞의 n개 건너뛰기peek() : 중간 상태 확인용(디버깅)List<String> listForRests = Arrays.asList("a", "a", "c", "d", "e", "f", "f", "h");
Stream<String> wordsStreamForRests = listForRests.stream().distinct()
.limit(4)
.peek(s -> System.out.println("중간확인 : " + s))
.skip(2);
wordsStreamForRests.forEach(System.out::println);스트림을 처리하여 최종 결과 반환
최종 연산 이 후엔 스트림이 소모(사용)되므로, 한번만 실행 가능
//count()로 개수 반환 long타입반환
long count = IntStream.of(grades).count();
//sum()으로 합계 구하기
int summed = IntStream.of((grades)).sum();
//min() 최솟값 : OptionalInt반환 (null값 대비)
OptionalInt minimum = IntStream.of(grades).min();
//max() 최댓값 : OptionalInt반환 (null값 대비)
OptionalInt maximum = IntStream.of(grades).max();
//average() 최솟값 : OptionalDouble반환 (null값 대비)
OptionalDouble avg = IntStream.of(grades).average();
collect(): 스트림 요소 원하는 자료형으로 변환List<String> list = List.of("apple", "banana", "cherry", "apple");
// List -> Set 변환 (중복 제거)
Set<String> set = list.stream()
.collect(Collectors.toSet());reduce() : 스트림의 요소를 누적하여 하나로 합친다.초기값이 있으면 T 반환, 없으면 Optional<T>를 반환한다. (null값 대비)
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
//초기값이 없으므로 Optional<T>를 반환한다.
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b); //15
//초기값 있으므로 Optional불필요(null일 수가 없음)
int sumWithInitial = numbers.stream()
.reduce(5,(a, b) -> a + b); //20
anyMatch(), allMatch(), noneMatch())특정 조건을 만족하는 요소가 있는치 체크
boolean반환
//길이가 4이상인 요소가 하나라도 있는지
boolean any = members.stream().anyMatch(name -> name.length() >= 4);
System.out.println(any); // true
//모든 요소가 "e"를 포함하는지
boolean all = members.stream().allMatch(name -> name.contains("e"));
System.out.println(all); // false
//q로 끝나는 요소가 하나도 없는지
boolean noneMatched = members.stream().noneMatch(name -> name.endsWith("q"));
System.out.println(noneMatched); // true
여러 개의 스레드를 사용하여 데이터를 병렬로 처리가 가능!
parallelStream() 이나 parallel()을 이용해 병렬 스트림 사용
순서가 보장되지 않는다. (순서가 보장되어야 한다면 주의해야 한다.)
⇒ 즉, 기본 스트림은 순차적으로 하나씩 데이터를 처리하지만 병렬스트림은 여러 스레드에서 동시에 데이터를 처리한다!
List<String> list = List.of("A", "B", "C", "D", "E");
list.parallelStream()
.forEach(s -> System.out.println(Thread.currentThread().getName() + " - " + s));
/*
출력
ForkJoinPool.commonPool-worker-3 - A
ForkJoinPool.commonPool-worker-1 - B
main - C
ForkJoinPool.commonPool-worker-2 - E
ForkJoinPool.commonPool-worker-5 - D
*/
parallel()은 기존 스트림을 병렬스트림으로 변환해준다.
list.stream() // 순차 스트림
.parallel() // 병렬 처리로 변환
.forEach(s -> System.out.println(Thread.currentThread().getName() + " - " + s));
이번 정리를 통해서 대충 스트림이 어떤 역할을 하는지는 알 것 같다.
하지만 아직 코드로 직접 사용하는데는 서툰거 같아서 틈틈히 일부로 스트림을 사용해봐야겠다.
또한 스트림을 알아보면서(사실 람다 공부했을 때) 메서드 참조(::) 가 근근히 보이는데 이것도 나중에 정리를 해봐야겠다. (멋져보이지 않는가!)