기초 지식/Java

Java 1.8 Stream 정리

MarrRang 2020. 10. 27. 19:00

Java Stream

자바 1.8 버전에서는 Stream 기능이 추가 되었습니다. 기존에는 for, foreach를 사용하여 컬렉션과 배열을 반복하며 인자들을 가공해왔을텐데 이 부분을 Stream으로 대체할 수 있게 되었습니다. 이로 인해 코드의 양은 줄어들고 가독성은 좋아지게 되었습니다.

 

장점으로는 코드량이 줄어든다는 점과 가독성 외에도 멀티 쓰레딩(multi threading)이 가능하다는 것입니다.

 

하지만 단점도 있겠죠 많은 양의 데이터를 처리하는데 foreach를 대체하려고 Stream을 사용하면 속도면에서 조금 느리게 됩니다. 따라서 많은 양을 처리하고 효율성이 중요한 부분이라면 foreach를 사용하는 편이 낫습니다.

(참고. madplay.github.io/post/mistakes-when-using-java-streams)

유의점

1. 일회성 사용

 

스트림은 한번 최종연산자를 거쳐서 결과를 도출해 내면 다시 사용할 수 없습니다. 다시 사용하고 싶다면 새로우 스트림을 열어야 합니다.

 

myStream.filter().forEach(System.out::print);
int numOfElement = myStream.map(...); // 에러. 다시 스트림을 열어야 함

 

2. 중간 연산자, 최종 연산자

 

스트림을 사용하는 데 있어서 중간 연산자와 최종 연산자를 유의해야합니다. 두 연산자의 차이는 반환형의 차이입니다.

 

// filter() 코드
@Override
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
    Objects.requireNonNull(predicate);
    return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                 StreamOpFlag.NOT_SIZED) {
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
            return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                @Override
                public void begin(long size) {
                    downstream.begin(-1);
                }

                @Override
                public void accept(P_OUT u) {
                    if (predicate.test(u))
                        downstream.accept(u);
                }
            };
        }
    };
}

 

위의 중간연산자인 filter()의 반환형은 Stream<T> 형태로 반환하고

 

@Override
public final long count() {
    return evaluate(ReduceOps.makeRefCounting());
}

 

최종연산자 중 하나인 count()는 long 형태를 반환하고 있습니다. 즉 Stream이 끝난 것입니다.

 

List<String> strList = lists.stream()
	.filter(...) // 중간연산자
    	.map(...) // 중간연산자
    	.collect(Collectors.toList()); // 최종연산자

3. Lazy 한 처리

 

결과가 필요하기 전까진 Chaining된 연산들을 실행하지 않습니다.

 

4. 원본 데이터를 변경하지 않는다

사용법

1. 배열에서 Stream 사용

String[] array = {"개발", "자바", "스트림"};
Stream<String> strStream = Arrays.stream(array);

strStream.filter(str -> str.length() == 2)
	.forEach(str -> System.out.print(str + " "));

 

2. 컬렉션에서 Stream 사용

List<String> strList = Arrays.asList("개발자", "자바", "백엔드", "프론트엔드", "스트림");

	long strCount = strList.stream()
		.map(str -> str + "!")
		.filter(str -> str.length() > 3)
		.peek(System.out::println)
		.count();

 

메소드

순서

  1. empty
  2. build
  3. filter
  4. map
  5. flatMap
  6. sorted
  7. distinct
  8. forEach
  9. peek
  10. limit
  11. skip
  12. concat
  13. count, sum, min, max, average
  14. collect
  15. anyMatch, allMatch, noneMatch

empty()

Stream이 null인지 아닌지 체크합니다.

 

Stream stream = Stream.empty();

build()

스트림 builder를 통해 스트림을 반환합니다.

 

Stream<String> stream = Stream.<String>builder()
        	.add("개발자")
        	.add("자바")
        	.build();

filter()

원하는 조건에 맞는 데이터만 추출할 수 있습니다. boolean 반환형을 가진 람다식을 인자로 주면 됩니다.

 

long strCount = strList
		.stream()
		.filter(str -> str.length() > 3)
		.count();

map()

데이터를 가공하는 메소드입니다. 가공하는 람다식을 인자로 주면 됩니다.

 

long strCount = strList.stream()
	.map(str -> str + "!")
	.count();

flatMap()

중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어줍니다. 이차원 배열등 중첩 구조를 stream으로 처리할 때 사용합니다.

 

String[][] arr = {{"a", "b", "c"}, {"d", "e"}};

Stream.of(arr)
	.flatMap(Stream::of)
	.forEach(System.out::print);
    
 //결과
 abcde

sorted()

Stream의 데이터를 정렬합니다. 아무런 인자를 넣지않으면 오름차순으로 정렬되고 Comparator를 이용해 원하는대로 정렬 할수도 있습니다.

 

// 오름차순
List<String> strList = Arrays.asList("개발자", "자바", "스트림");
strList.stream()
	.sorted()
	.forEach(System.out::println);
    
// 내림차순
strList.stream()
	.sorted(Comparator.reverseOrder())
	.forEach(System.out::println);

distinct()

중복 값을 제거합니다.

 

String[] arr = {"a", "b", "a"};

Stream.of(arr)
	.distinct()
	.forEach(System.out::print);
    
// 결과
ab

forEach()

요소들을 순회하며 특정 작업을 수행합니다. 최종연산자 입니다.

 

Arrays.asList("개발자", "자바", "스트림").stream()
		.forEach(System.out::println);

peek()

각 요소에 연산을 수행하는 메소드입니다. forEach와 같지만 중간 연산자라는 차이점을 가지고 있습니다.

 

List<String> strList = Arrays.asList("개발자", "자바", "스트림");

strList.stream()
	.peek(System.out::print)
	.forEach(System.out::print);

limit()

앞에서 부터 n개의 요소만 반환합니다.

 

Arrays.asList("개발자", "자바", "스트림")
    .stream()
    .limit(2)
    .forEach(System.out::println);

skip()

앞의 n개의 요소를 제외한 나머지를 반환합니다.

 

Arrays.asList("개발자", "자바", "스트림")
    .stream()
    .skip(2)
    .forEach(System.out::println);

concat()

두 stream을 연결합니다.

 

Stream<String> stream1 = Arrays.asList("개발자", "자바", "스트림").stream();
Stream<String> stream2 = Arrays.asList("컴퓨터", "모니터", "마우스").stream();

List<String> concatStream = Stream.concat(stream1, stream2)
				.collect(Collectors.toList());

concatStream.stream().forEach(System.out::println);

count(), sum(), min(), max(), average()

각각 갯수, 합산, 최소값, 최대값, 평균을 내는데 사용됩니다. 모든 함수는 기본 Stream이 아닌 IntStream 같은 숫자형 스트림에서 사용가능합니다.

 

long count = IntStream.of(1, 2, 3, 4, 5).count();
System.out.println(count);
		
int sum = IntStream.of(1, 2, 3, 4, 5).sum();
System.out.println(sum);
		
int max = IntStream.of(1, 2, 3, 4, 5).max().getAsInt();
System.out.println(max);
		
int min = IntStream.of(1, 2, 3, 4, 5).min().getAsInt();
System.out.println(min);
		
double average = IntStream.of(1, 2, 3, 4, 5).average().getAsDouble();
System.out.println(average);

//결과
5
15
5
1
3.0

collect

스트림의 값을 List, Map, Set과 같은 컬렉션 형태로 변환하여 반환합니다 그리고 최종연산자로 많이 사용됩니다.

 

//위의 concat 예시와 비슷하지만 stream은 재사용이 불가한 특성때문에 리스트를 각각 변경
List<String> list1 = Arrays.asList("개발자", "자바", "스트림");
List<String> list2 = Arrays.asList("컴퓨터", "자바", "마우스");

List<String> concatStream = Stream.concat(list1.stream(), list2.stream())
			.collect(Collectors.toList());

Set<String> setStream = Stream.concat(list1.stream(), list2.stream())
			.collect(Collectors.toSet());

concatStream.forEach(System.out::println);
System.out.println();
setStream.forEach(System.out::println);

//결과
개발자
자바
스트림
컴퓨터
자바
마우스

자바
스트림
개발자
마우스
컴퓨터

anyMatch(), allMatch(), noneMatch()

Predicate(boolean값을 리턴)로 조건을 받고 그 조건에 맞는지 boolean으로 반환합니다.

  • anyMatch() : 조건을 하나라도 만족시키면 true
  • allMatch() : 조건을 모두 만족시키면 true
  • noneMatch() : 조건을 모두 만족시키지 못하면 true
List<String> stringList = Arrays.asList("자전거", "자바", "자동차");

System.out.println(stringList.stream().anyMatch(str -> str.length() == 2));
System.out.println(stringList.stream().allMatch(str -> str.charAt(0) == '자'));
System.out.println(stringList.stream().noneMatch(String::isEmpty));

//결과
true
true
true

 

반응형