이것이 자바다 - Part 17

mj·2023년 1월 30일
0
post-thumbnail

Part 17 스트림 요소 처리

스트림이란?

지금까지 컬렉션 밒 배열에 저장된 요소를 반복 처리하기 위해서는 for 문을 이용하거나 Iterator 를 이용했다.
다음은 List 컬렉션에서 요소를 하나씩 처리하는 for 문이다.

List<String> list = ...;
for(int i=0; i<list.size(); i++) {
	String item = list.get(i);
    //item 처리
}

Set 에서 요소를 처리하기 위해 Iterator 를 다음과 같이 사용했다.

Set<String> set = ...;
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()) {
	String item = iterator.next();
    //요소 처리
}

Java8 부터는 또 다른 방법으로 컬렉션 및 배열의 요소를 반복 처리하기 위해 스트림을 사용할 수 있다.
스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다.

List 컬렉션에서 요소를 반복 처리하기 위해 스트림을 사용하면 다음과 같다.

Stream<String> stream = list.stream();
stream.forEach( item -> //item 처리 );

Stream 은 Iterator 와 비슷한 반복자이지만 다음과 같은 차이점을 가지고 있다.

Iterator 대비 Stream 의 차이점
1. 내부 반복자이므로 처리 속도가 빠르고 병렬 처리에 효율적이다.
2. 람다식으로 다양한 요소 처리를 정의할 수 있다.
3. 중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.

내부 반복자

for 문과 Iterator는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리하는데, 이것을 외부 반복자라고 한다.
반면 스트림은 요소 처리 방법을 컬렉션 내부로 주입시커서 요소를 반복 처리하는데. 이것을 내부 반복자라고 한다.

외부 반복자일 경우는 컬렉션의 요소를 외부로 가져오는 코드와 처리하는 코드를 모두 개발자 코드가 가지고 있어야 한다.

내부 반복자일 경우는 개발자 코드에서 제공한 데이터 처리 코드(람다식)를 가지고 컬렉션 내부에서 요소를 반복 처리한다.

내부 반복자는 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있다.
하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있는 장점이 있다.

중간 처리와 최종 처리

스르팀은 하나 이상 연결될 수 있다.
스트림이 연결되어 있는 것을 스트림 파이프라인 이라고 한다.

오리지널, 필터링(중간처리), 매핑(중간 처리), 집계(최종 처리) 스트림 등이 있다.

중간 스트림들은 다음과 같은 역할을 한다.

  • 필터링 : 최종 처리를 위해 요소를 걸러냄
  • 매핑 : 요소를 변환
  • 정렬 : 정제된 요소들을 정렬

최종 처리는 중간 처리에서 정제된 요소들을 반복하거나, 집계(카운팅, 총합, 평균) 작업을 수행한다.

스트림 파이프라인으로 구성할 때 주의할 점은 파이프라인의 맨 끝에는 반드시 최종 처리 부분이 있어야 한다는 것이다.
최종 처리가 없다면 오리지널 및 중간 처리 스트림은 동작하지 않는다.

리소스로부터 스트림 얻기

java.util.stream 패키지에는 스트림 인터페이스들이 있다.
BaseStream 인터페이스를 부모로 한 자식 인터페이스들은 다음과 같은 상속 관계를 이루고 있다.

BaseStream

<=

StreamIntStreamLongStreamDoubleStream

BaseStream에는 모든 스트림에서 사용할 수 잇는 공통 메소드들이 정의되어 있다.

Stream 은 객체 요소를 처리하는 스트림이고, 나머지는 각각 기본 타입인 int, long, double 요소를 처리하는 스트림이다.

이 스트림 인터페이스들의 구현 객체는 다양한 리소스로부터 얻을 수 있다. 주로 컬렉션과 배열에서 얻지만, 다음과 같은 리소스로부터 스트림 구현 객체를 얻을 수도 있다.

리턴 타입메소드(매개변수)소스
Stream<T>java.util.Collection.stream(), java.util.parallelStream()List 컬렉션, Set 컬렉션
Stream<T>, IntStream, LongStream, DoubleStreamArray.Stream(T[]), Array.Stream(int[]), Array.Stream(long[]), Array.Stream(double[]), Stream.of(T[]), IntStream.of(int[]), LongStream.of(long[]), DoubleStream.of(double[])배열
IntStreamIntStream.range(int, int), IntStream.rangeClosed(int, int)int 범위
LongStreamLongStream.range(long,long), LongStream.rangeClosed(long, long)long 범위
Stream<Path>Files.list(Path)디렉토리
Stream<String>Files.lines(Path, Charset)텍스트 파일
DoubleStream, IntStream, LongStreamRandom.doubles(...), Random.ints(), Random.longs()랜덤 수

컬렉션으로부터 스트림 얻기

java.util.Collection 인터페이스는 스트림과 parallelStream() 메소드를 가지고 있기 때문에 가식 인터페이스인 List와 Set 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.

배열로부터 스트림 얻기

java.util.Arrays 클래스를 이용하면 다양한 종류의 배열로부터 스트림을 얻을 수 있다.

숫자 범위로부터 스트림 얻기

IntStream, LongStream의 정적 메소드인 range()와 rangeClosed() 메소드를 이용하면 특정 범위의 정수 스트림을 얻을 수 있다.
첫 번째 매개값은 시작 수이고 두 번째 매개값은 끝 수인데, 끝 수를 포함하지 않으면 range(), 포함하면 rangeClose() 를 사용한다.

파일로부터 스트림 얻기

java.nio.file.Files 의 lines() 메소드를 이용하면 텍스트 파일의 행 단위 스트림을 얻을 수 있다.
이는 텍스트 파일에서 한 행씩 읽고 처리할 때 유용하게 사용할 수 있다.

요소 걸러내기(필터링)

필터링은 요소를 걸러내는 중간 처리 기능이다.
필터링 메소드에는 다음과 같이 distinct()와 filter()가 있다.

리턴 타입매소드(매개변수)설명
Strea, IntStream, LongStream, DoubleStreamdistinct()중복 제거
filter(Predicate<T>)- 조건 필터링
filter(IntPredicate)- 매개 타입은 요소 타립에 따른 함수형 인터페이스이므로 람다식으로 자교성 가능
filter(LongPredicate)
filter(DoublePredicate)

distinct() 메소드는 요소의 중복을 제거한다. 객체 스트림(Stream)일 경우, equals() 메소드의 리턴값이 true 이면 동일한 요소로 판단한다.

IntStream. LongStream, DoubleStream 은 같은 값일 경우 중복을 제거한다.

Predicate는 함수형 인터페이스로 다음과 같은 종류가 있다.

인터페이스추상 메소드설명
Predicat<T>boolean test(T t)객체 T를 조사
IntPredicateboolean test(int value)int 값을 조사
LongPredicateboolean test(long value)long 값을 조사
DoublePredicateboolean test(double value)double 값을 조사

모든 Predicate 는 매개값을 조사한 후 boolean 을 리턴하는 test() 메소드를 가지고 있다.

Predicate<T> 을 람다식으로 표현하면 다음과 같다.

T -> { ... return true }
or
T -> true;		//return 문ㅁ나 있을 경우 중괄호와 return 키워드 생략 가능

요소 변환(매핑)

매핑은 스트림의 요소를 다른 요소로 변환하는 중간 처리 기능이다.
매핑 메소드는 mapXxx(), asDoubleStream(), asLongStream(), boxed(), flatMapXxx() 등이 있다.

요소를 다른 요소로 변환

mapXxx() 메소드는 요소를 다른 요소로 변환한 새로운 스트림을 리턴한다.

mapXxx() 메소드의 종류는 다음과 같다.

리턴 타입메소드(매개변수)요소->변환 요소
Stream<R>map(Function<T, R>)T -> R
IntStream, LongStream, DoubleStreammapToInt(ToIntFucntion<T>)T -> int
mapToLong(ToLongFunction<T>)T -> long
mapToDOuble(ToDoubleFunction<T>)T -> double
Stream<U>mapToObj(IntFunction<T>)int -> U
mapToObj(LongFunction<U>)long -> U
mapToObj(DoubleFunction<U>)double -> U
DoubleStream, DOubleStream, IntStream, LongStreammapToDOuble(IntToDoubleFunction)int -> double
mapToDouble(LongToDoubleFunction)long -> double
mapToInt(DoubleToIntFunction)double -> int
mapToLong(DOubleToLongFunction)double -> long

모든 Function은 매개값을 리턴값으로 매핑(변환)하는 applyXxx() 메소드를 가지고 있다.

Function<T, R> 을 람다식으로 표현하면 다음과 같다.

T -> { ... return R; }
or
T -> R;		//return 문만 있을 경우 중괄호와 return 키워드 생략 가능

기본 타입 간의 변환이거나 기본 타입 요소를 래퍼 요소로 변환하려면 다음과 같은 간편화 메소드를 사용할 수도 있다.

리턴타입메소드(매개변수)설명
LongStreamasLongStream()int -> long
DoubleStreamasDoubleStream()int -> double, long -> double
Stream<Integer>boxed()int -> Interger, long -> Long, double -> Double
Stream<Long>
Stream<Double>

요소를 복수 개의 요소로 변환

flatMapXxx() 메소드는 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림을 리턴한다.

요소 정렬

정렬은 요소를 오름차순 또는 내림차순으로 정렬하는 중간 처리 기능이다.
요소를 정렬하는 메소드는 다음과 같다.

리턴 타입메소드(매개변수)설명
Stream<T>sorted()Comparable 요소를 정렬한 새로운 스트림 생성
Stream<T>sorted(Comparator<T>)요소를 Comparator 에 따라 정렬한 새 스트림 생성
DoubleStreamsorted()double 요소를 올림차순으로 정렬
IntStreamsorted()int 요소를 올림차순으로 정렬
LongStreamsorted()long 요소를 올림차순으로 정렬

Comparable 구현 객체의 정렬

스트림의 요소가 객체일 경우 객체가 Comparable을 구현하고 있어야만 sorted() 메소드를 사용하여 정렬할 수 있다.
그렇지 않다면 ClassCaseException 이 발생한다. Comparable을 구현하는 자세한 방법은 15장에 나와있다.

public Xxx implements Comparable {
	...
}
List<Xxx> list = new Arraylist<>();
Stream<Xxx> stream = list.stream();
Stream<Xxx> orderedStream = stream.sorted();

만약 내림차순으로 정렬하고 싶다면 다음과 같이 Comparator.reverseOrder() 메소드가 리턴하는 Comparator를 매개값으로 제공하면 된다.

Stream<Xxx> reverseOrderedStream = stream.sorted(Comparator.reverseOrder());

Comparator 를 이용한 정렬

요소 객체가 Comparable을 구현하고 있지 않다면, 비교자를 제공하면 요소를 정렬시킬 수 있다.
비교자는 Comparator 인터페이스를 구현한 객체를 말하는데 15장에서는 명시적인 클래스로 구현하는 방법을 설명했지만, 다음과 같이 간단하게 람다식으로 작성할 수도 있다.

sorted((o1, o2) -> { ... });

중괄호 안에는 o1이 o2 보다 작으면 음수, 같으면 0, 크면 약수를 리턴하도록 작성하면 된다.
o1, o2 가 정수일 경우에는 Interger.compare(o1, o2) 를, 실수일 경우에는 Double.compare(o1, o2) 를 호출해서 리턴값을 리턴해도 좋다.

요소를 하나씩 처리(루핑)

루핑은 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것을 말한다.
루핑 메소드에는 peek() 과 forEach() 가 있다.

리턴타입메소드(매개변수)설명
Stream<T>, IntStream, DoubleStreampeek(Consumer< ? super T>)T 반복
peek(IntConsumer actio)int 반복
peek(DoubleConsumer action)double 반복
voidforEach(Consumer< ? super T> action)T 반복
forEach(IntConsumer action)int 반복
forEach(DoubleConsumer action)double 반복

peek() 과 forEach()는 동일하게 요소를 루핑하지만 peek()은 중간 처리 메소드 이고, forEach() 는 최종 처리 메소드 이다.

따라서 peek()은 최종 처리가 뒤에 붙지 않으면 동작하지 않는다.

매개타입은 Consumer는 함수형 인터페이스이다.
모든 Comsuper는 매개값을 처리(소비)하는 accept() 메소드를 가지고 있다.

Comsumer< ? super T> 를 람다식으로 표현하면 다음과 같다.

T -> { ... }
or
T -> 실행문		//하나의 실행문만 있을 경우 중괄호 생략

요소 조건 만족 여부(매칭)

매핑은 요소들이 특정 조건에 만족하는지 여부를 조사하는 최종 처리 기능이다.
매칭과 관련된 메소드는 다음과 같다.

리턴 타입메소드(매개변수)조사 내용
booleanallMatch(Predicate<T> predicate)모든 요소가 만족하는지 여부
allMatch(IntPredicate predicate)
allMatch(LongPredicate predicate)
allMatch(DoublePredicate predicate)
booleanallMatch(Predicate<T> predicate)최소한 하나의 요소가 만족하는지 여부
anyMatch(IntPredicate predicate)
anyMatch(LongPredicate predicate)
anyMatch(DoublePredicate predicate)
booleanallMatch(Predicate<T> predicate)모든 요소가 만족하지 않는지 여부
noneMatch(IntPredicate predicate)
noneMatch(LongPredicate predicate)
noneMatch(DoublePredicate predicate)

allMatch(), anyMatch(), noneMatch() 메소드는 매개값으로 주어진 Predicate 가 리턴하는 값에 따라 true 또는 false를 리턴한다.

예를 들어 allMatch()는 모든 요소의 Predicate가 true를 리턴해야만 true를 리턴한다.

요소 기본 집계

집계는 최종 처리 기능으로 요소들을 처리해서 카운팅, 합계, 평균값, 최대값, 최소값등과 같이 하나의 값으로 산출하는 것을 말한다.
즉, 대량의 데이터를 가공해서 하나의 값으로 축소하는 리덕션이라고 볼 수 있다.

스트림이 제공하는 기본 집계

스트림은 카운팅, 최대. 최소, 평균, 합계 등을 처리하는 다음과 같은 최종 처리 메소드를 제공한다.

리턴 타입메소드(매개변수)설명
longcount()요고 개수
OptionalXXXfindFirst()첫 번째 요소
Optional<T>, OptionalXXXmax(Comparator<T>), max()최대 요소
Optional<T>, OptionalXXXmin(Comparator<T>), min()최소 요소
OptionalDoubleaverage()요소 평균
int, long, doublesum()요소 총합

집곔 메소드가 리턴하는 OptionalXXX는 최종값을 저장하는 객체로, get(), getAsDouble(), getAsInt(), getAsLong()을 호출하면 최종값을 얻을 수 있다.

Optional 클래스

Optional, OptionalDouble, OptionalInt, OptionalLong 클래스는 단순히 집계값만 저장하는 것이 아니라, 집계값이 존재하지 않을 경우 디폴트 값을 설정하거나, 집계값을 처리하는 Consumer를 등록할 수 있다.

리턴타입메소드(매개변수)설명
booleanisPresent()집계값이 있는지 여부
T, double, int, longorElse(T)집계값이 없을 경우 디폴트 값 설정
orElse(double)
orElse(int)
orElse(long)
voidifPresent(Consumer)집계값이 있을 경우 Consumer 에서 처리
ifPresent(DoubleConsumer)
ifPresent(IntConsumer)
ifPresent(LongConsumer)

컬렉션의 요소는 동적으로 추가되는 경우가 많다. 만약 컬렉션에 요소가 존재하지 않으면 집계 값을 산출할 수 없으므로 NoSuchElementException 예외가 발생한다.
하지만 앞의 표에 언급되어 있는 메소드를 이용하면 예외 발생을 막을 수 있다.

1) isPresent() 메소드가 true를 리턴할 때만 집계값을 얻는다.

OptionalDouble optional = stream
	.average();
if(optional.isPresent()) {
	System.out.println("평군: " + optional.getAsDouble());
} else {
	System.out.println("평균: 0.0");
}

2) orElse() 메소드로 집계값이 없을 경우를 대비해서 디폴트 값을 정해놓는다.

double avg = stream
	.average()
    .orElse(0.0);
System.out.println("평균: " + avg);

3) isPresent() 메소드로 집계값이 있을 경우에만 동작하는 Consumer 람다식을 제공한다.

stream
	.average()
    .ifPresent(a -> System.out.println("평균: " + a));

요소 커스텀 집계

스트림은 기본 집계 메소드인 sum(), average(), count(), max, min() 을 제공하지만, 다양한 집계 결과물을 만들 수 있도록 reduce() 메소드도 제공한다.

매개값인 BinaryOperator는 ㅎ마수형 인터페이스이다. BinaryOperator는 두 개의 매개값을 받아 하나의 값을 리턴하는 apply() 메소드를 가지고 있기 때문에 다음과 같이 람다식을 작성할 수 있다.

(a, b) -> { ... return; }
or
(a, b) ->//return 문만 있을 경우 중괄호와 return 키워드 생략 가능

reduce()는 스트림에 요소가 없을 경우 예외가 발생하지만, identity 매개값이 주어지면 이 값을 디폴트 값으로 리턴한다.
다음 중 첫번째 코드는 스트림에 요소가 없을 경우 NoSuchElementException 을 발생시키지만, 두번째 코드는 디폴트 값(identity) 0을 리턴한다.

int sum = stream
	.reduce((a, b) -> a + b)
    .getAsInt();
int sum = stream
	.reduce(0, (a, b) -> a +b )

요소 수집

스트림은 요소들을 필터링 또는 매피ㅗㅇ한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공한다. 이 메소드를 이용하면 필요한 요소만 컬렉션에 담을 수 있고, 요소들을 그룹핑한 후에 집계도를 할 수 있다.

필터링한 요소 수집

Stream 의 collect(Collector<T,A,R> collector) 메소드는 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고, 이 컬렉션을 리턴한다.
매개값인 Collector는 어떤 요소를 어떤 컬렉션에 수집할 것인지를 결정한다.

리턴타입메소드(매개변수)인터페이스
Rcollect(Collector<T,A,R> collector)Stream

타입 파라미터의 T 는 요소, A는 누적기, 그리고 R은 요소가 저장될 컬렉션이다.
플어서 해석하면 T 요소를 A누적기가 R 에 저장한다는 의미이다.
Collector의 구현 객체는 다음과 같이 Collectors 클래스의 정적 메소드로 얻을 수 있다.

리턴 타입메소드설명
Collector<T, ?, List<T>>toList()T를 List에 저장
Collector<T, ?, Set<T>>toSet()T를 Set에 저장
Collector<T, ?, Map<K, U>>toMap(Function<T, K> keyMapper, Function<T, U>valueMapper)T를 K와 U로 매핑하여 k를 키로, U를 값으로 Map에 저장

리턴값인 Collector를 보면 A(누적기)가 ?로 되어 있는데, 이것은 Collector가 List, Set, Map 컬렉션에 요소를 저장하는 방법을 알고 있어 별도의 누적기가 필요 없기 때문이다.

다음은 Student 스트림에서 남학생만 필터링해서 별도의 list 로 생성하는 코드이다.

List<Student> maleList = totalList.stream()
	.filter(s->s.getSex().equals("남"))	//남학생만 필터링
    .collect(Collectors.toList());
Map<String, Integer> map = totalList.stream()
	.collect(
    	Collectors.toMap(
        	s->s.getName(),		//Student 객체에서 키가 될 부분 리턴
            s->s.getScore(),	//Student 객체에서 값이 될 부분 리턴
        )
    );

Java16 부터는 좀 더 편리하게 요소 스트림에서 List 컬렉션을 얻을 수 있다. 스트림에서 바로 toList() 메소드를 다음과 같이 사용하면 된다.

List<Student> maleList = totalList.stream()
	.filter(s -> s.getSex().equals("남"))
    .toList();

요소 그룹핑

collect() 메소드는 단순히 요소를 수집하는 기능 이외에 컬렉션의 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공한다.
Collectors.groupingBy() 메소드에서 얻은 Collector를 collect() 메소드를 호출할 때 제공하면 된다.

리턴 타입메소드
Collector<T,?,Map<K,List<T>>>groupingBy(Function<T,K> classifier)

groupingBy()는 Function을 이용해서 T를 K로 매핑하고 K를 키로 해 List<T>를 값으로 갖는 Map 컬렉션을 생성한다.

요소 병렬 처리

요소 병렬 처리란 멀티 코어 CPU 환경에서 전체 요소를 분할해서 각각의 코어가 병렬적으로 처리하는 것을 말한다.
요소 병렬 처리의 목적은 작업 처리 시간을 줄이기 위한 것에 있다.
자바는 요소 병렬 처리를 위해 병렬 스트림을 제공한다.

동시성과 병렬성

멀티 스레드는 동시성 또는 병렬성으로 실행되기 때문에 이들 용어에 대해 정확히 이해하는 것이 좋다.
동시성 또는 병렬성으로 실행되기 때문에 이들 용어에 대해 정확히 이해하는 것이 좋다.

동시성은 멀티 작업을 위해 멀티 스레드가 하나의 코어에서 번갈아 가며 실행하는 것을 말하고, 병렬성은 멀티 작업을 위해 멀티 코어를 각각 이용해서 병렬로 실행하는 것을 말한다.

동시성은 한 시점에 하나의 적업만 실행한다.
번갈아 작업을 실행하는 것이 워낙 빠르다보니 동시에 처리되는 것처럼 보일 뿐이다.
병렬성은 한 시점에 여러 개의 작업을 병렬로 실행하기 때문에 동시성보다 좋은 성능을 낸다.

병렬성은 데이터 병렬성과 작업 병렬성으로 구분할 수 있다.

데이터 병렬성

전체 데이터를 분할해서 서브 데이터셋으로 만들고 이 서브 데이터셋들을 병렬 처리해서 작업을 빨리 끝내는 것을 말한다.
자바 병렬 스트림은 데이터 병렬성을 구현한 것이다.

작업 병렬성

서로 다른 작업을 병렬 처리하는 것을 말한다. 작업 병렬성의 대표적인 예는 서버 프로그램이다.
서버는 각각의 클라이언트에서 요청한 내용을 개별 스레드에서 병렬로 처리한다.

포크조인 프레임워크

자바 병렬 스트림은 요소들을 병렬 처리하기 위해 포크조인 프레임워크를 사용한다.
포크조인 프레임워크는 포크 단계에서 전체 요소들을 서브 요소셋으로 분할하고, 각각의 서브 요소셋을 멀티 코어에서 병렬로 처리한다.
조인 단계에서는 서브 결과를 결합해서 최종 결과를 만들어낸다.

포크조인 프레임워크는 병렬 처리를 위해 스레드풀을 사용한다.
각각의 코어에서 서브 요소셋을 처리하는 것은 작업 스레드가 해야 하므로 스레드 관리가 필요하다.
포크조인 프레임워크는 ExecutorService 의 구현 객체인 ForkJoinPool 을 사용해서 작업 스레드를 관리한다.

병렬 스트림 사용

자바 병렬 스트림을 이용할 경우에는 백그라운드에서 포크조인 프레임워크가 사용되기 때문에 개발자는 매우 쉽게 병렬 처리를 할 수 있다.
병렬 스트림은 다음 두 가지 메소드로 얻을 수 있다.

리턴 타임메소드제공 컬렉션 또는 스트림
StreamparallelStream()List 또는 Set 컬렉션
Streamparallel()Java.util.Stream
IntStreamjava.util.IntStream
LongStreamjava.util.LongStream
DoubleStreamjava.util.DoubleStream

parallelStream() 메소드는 컬렉션(List, Set)으로부터 병렬 스트림을 바로 리턴한다.
parallel() 메소드는 기존 스트림을 병렬 처리 스트림으로 변환한다.

병렬 처리 성능

스트림 병렬 처리가 스트림 순차 처리보다 항상 실행 성능이 좋다고 판단해서는 안된다.
그 전에 먼저 병렬 처리에 영향을 미치는 다음 3가지 요인을 잘 살펴보아야 한다.

요소의 수와 요소당 처리 시간

컬렉션에 전체 요소의 수가 적고 요소당 처리 시간이 짧으면 일반 스트림이 병렬 스트림보다 빠를 수 있다.
병렬 처리는 포크 및 조인 단계가 있고, 스레드 풀을 생성하는 추가적인 비용이 발생하기 때문이다.

스트림 소스의 종류

ArrayList 와 배열은 인덱스로 요소를 관리하기 때문에 포크 단계에서 요소를 쉽게 분리할 수 있어 병렬 처리 시간이 절약된다.
반면에 HashSet, TreeSet은 요소 분리가 쉽지 않고, LinkedList 역시 링크를 따라가야 하므로 요소 분리가 쉽지 않다.

코어(Core)의 수

CPU 코어의 수가 많으면 많을수록 병렬 스트림의 성능은 좋아진다.
하지만 코어의 수가 적을 경우에는 일반 스트림이 더 빠를 수 있다.
병렬 스트림은 스레드 수가 증가하여 동시성이 많이 일어나므로 오히려 느려진다.

문제

  1. 스트림에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 스트림은 내부 반복자를 사용하기 때문에 코드가 간결해진다.
    ➋ 스트림은 요소를 분리해서 병렬 처리시킬 수 있다.
    ➌ 스트림은 람다식을 사용해서 요소 처리 내용을 기술한다.
    ➍ 스트림은 요소를 모두 처리하고 나서 처음부터 요소를 다시 반복시킬 수 있다.
  • 답 : ➍
  1. 스트림을 얻을 수 있는 소스가 아닌 것은 무엇입니까?
    ➊ 컬렉션(List)
    ➋ int, long, double 범위
    ➌ 디렉토리
    ➍ 배열
  • 답 : ➋
  1. 스트림 파이프라인에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 스트림을 연결해서 중간 처리와 최종 처리를 할 수 있다.
    ➋ 중간 처리 단계에서는 필터링, 매핑, 정렬, 그룹핑을 한다.
    ➌ 최종 처리 단계에서는 합계, 평균, 카운팅, 최대값, 최소값 등을 얻을 수 있다.
    ➍ 최종 처리가 없더라도 중간 처리를 할 수 있다.
  • 답 : ➍
  1. 스트림 병렬 처리에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 전체 요소를 분할해서 처리한다.
    ➋ 내부적으로 포크조인 프레임워크를 이용한다.
    ➌ 병렬 처리는 순차적 처리보다 항상 빠른 처리를 한다.
    ➍ 내부적으로 스레드풀을 이용해서 스레드를 관리한다.
  • 답 : ➌
  1. List에 저장되어 있는 String 요소에서 대소문자와 상관없이 ‘java’라는 단어가 포함된 문자열만
    필터링해서 출력하려고 합니다. 빈칸에 알맞은 코드를 작성해보세요.

  • 답 :
.filter(a -> a.toLowerCase().contains("java"))
.forEach(a -> System.out.println(a));
  1. List에 저장되어 있는 Member의 평균 나이를 출력하려고 합니다. 빈칸에 알맞은 코드를 작성해
    보세요.

  • 답 :
.mapToInt(Member::getAge)
.average()
.getAsDouble();
  1. List에 저장되어 있는 Member 중에서 직업이 ‘개발자’인 사람만 별도의 List에 수집하려고 합니
    다. 빈칸에 알맞은 코드를 작성해보세요.

  • 답 :
.filter(m -> m.getJob().equals("개발자"))
.collect(Collectors.toList());
  1. List에 저장되어 있는 Member를 직업별로 그룹핑해서 Map<String,List<Member>> 객체로
    생성하려고 합니다. 키는 Member의 직업이고, 값은 해당 직업을 갖는 Member들을 저장하고 있
    는 List입니다. 실행 결과를 보고 빈칸에 알맞은 코드를 작성해보세요.

  • 답 :
.collect(Collectors.groupingBy(m -> m.getJob()));
groupingMap.get("개발자").stream()
	.forEach(m -> System.out.println(m));
groupingMap.get("디자이너").stream()
	.forEach(m -> System.out.println(m));
profile
사는게 쉽지가 않네요

0개의 댓글