본문 바로가기

Java

[스트림 활용] 중간 연산

중간 연산

생성된 스트림을 필터링하거나 원하는 형태로 가공하는 과정

중간 연산은 스트림을 반환하기 때문에 여러 작업을 이어서 호출하는 Chaining이 가능하다.

 

스트림 필터링

filter

스트림 내 요소들을 특정 조건(predicate)에 맞게 걸러낸 요소만으로 구성된 새로운 스트림 반환

조건에 해당하는 Predicate<T>  를 인자로 받는다. 

더보기

매개변수 하나를 입력받아 boolean 타입으로 반환하는 함수형 인터페이스

조건식을 표현하는데 사용된다.

List<String> language = Arrays.asList("java", "kotlin", "python", "c", "go");
language.stream().filter(s -> s.contains("o")).forEach(lang -> System.out.printf("%s ", lang));
// kotlin python go 

distinct

스트림 내 요소의 중복 제거한 후 새로운 스트림을 반환

Stream<String> duplicateStream = Arrays.stream(new String[]{"a", "b", "c", "a", "d", "b"});
duplicateStream.distinct().forEach(s -> System.out.printf("%s ", s));

기본 타입이 아닌 custom class (VO class, object)의 경우 equals(), hashCode()를 재정의 해야한다.

// distinct
List<Person> persons = Arrays.asList(new Person("Joy"), new Person("Joe"),
new Person("Zoy"), new Person("Joy"));
Stream<Person> personsStream = persons.stream();
personsStream.distinct().forEach(p -> System.out.printf("%s ", p.name));
// Joy Joe Zoy 

// custom class
    private static class Person {
        private final String name;

        public Person(String name) {
            this.name = name;
        }

        @Override
        public int hashCode() {
            return name.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Person)
                return name.equals(((Person) obj).name);
            return false;
        }
    }

스트림 변환

map

단일 스트림의 요소들을 주어진 함수에 인수로 전달하여 매핑시킨 후, 매핑된 값을 다시 스트림으로 반환

Function<T, R> 을 인자로 받는다.

더보기

T 타입의 인자를 받아 R 타입의 객체를 반환하는 함수형 인터페이스

형변환 시 사용한다.

List<String> language = Arrays.asList("java", "kotlin", "python", "c", "go");
language.stream().map(String::toUpperCase).forEach(lang -> System.out.printf("%s ", lang));
// JAVA KOTLIN PYTHON C GO

 

flatMap

Array나 Object로 감싸져 있는 모든 원소를 단일 원소 스트림으로 반환

 

flattening + map() 

Flattening

여러 컬렉션 / 배열을 하나로 병합하는 것

Before flattening : [[1,2,3], [4,5], [6,7,8]]
After flattening : [1,2,3,4,5,6,7,8]

사용 예제

// example 1
String[][] dataArray = new String[][]{{"a", "b"}, {"c", "d"}, {"e", "f"}, {"g", "h"}};
List<String> listOfAllChars = Arrays.stream(dataArray)
                .flatMap(Arrays::stream)
                .collect(Collectors.toList());
System.out.println(listOfAllChars);
// [a, b, c, d, e, f, g, h]

// example2
String[] arr = {"Hello Af3","This is", "flatmap example"};
Stream<String> stream = Arrays.stream(arr);
stream.flatMap(s -> Stream.of(s.split(" +"))).forEach(System.out::println);
// 결과
Hello
Af3
This
is
flatmap
example

활용

  • 중간 연산 메소드는 컬렉션 스트림에서는 동작하지 않기 때문에 중간 메소드 사용 이전에 스트림을 flattening시 사용

flatMap vs map

map : 1:1 반환

flatMap: 1: N 반환

https://stackoverflow.com/questions/26684562/whats-the-difference-between-map-and-flatmap-methods-in-java-8

 

스트림 정렬

sorted

스트램 내 요소를 정렬하여 새 스트림을 반환

인자없이 호출할 경우 오름차순으로 정렬하고, 정렬 조건이 필요할 경우 Comparator<T>를 인자로 줄 수도 있다.

더보기

Comparable이 구현된 클래스들의 기본 정렬 기준과 다르게 정렬하고 싶을 때 사용하는 인터페이스

Compare()를 override 하여 정렬 조건을 구현한다.

IntStream.of(4, 2, 5, 6, 7, 1, 3).sorted().forEach(num -> System.out.printf("%d ", num));
// 1 2 3 4 5 6 7 

// comparator
List<String> language = Arrays.asList("java", "kotlin", "python", "c", "go");
language.stream().sorted(Comparator.comparing(String::length))
                .forEach(lang -> System.out.printf("%s ", lang));
//c go java kotlin python 

스트림 제한

skip 

스트림 내 첫 번째 요소 ~ 전달된 개수 만큼의 요소를 제외한 나머지 요소로 구성된 새로운 스트림을 반환

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
	.filter(i -> i % 2 == 0)
	.skip(2)
	.forEach(i -> System.out.print(i + " "));
// 6 8 10

 

limit

스트림에서 첫 번째 요소부터 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림을 반환

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
	.filter(i -> i % 2 == 0)
	.limit(2)
	.forEach(i -> System.out.print(i + " "));
// 2 4

 

skip과 limit 의 차이점 (https://www.baeldung.com/java-stream-skip-vs-limit)

무한 스트림을 자를 때는 limit(), 유한 스트림을 자를 때는  skip()을 사용하는게 유용하다.

 

스트림 조회

peek

스트림은 재사용이 불가능하기 때문에 종단연산이 호출된 이후 해당 스트림을 호출할 경우, 

java.lang.IllegalStateException: stream has already been operated upon or closed 이런 오류가 발생한다.

그러나 Stream 연산 중간에 결과를 확인하고 싶을 수도 있다. 그럴 때 peek 을 호출하면 된다.

peek은 종단 연산이 아니라 stream을 반환하는 중간 연산이기 때문에 연산 사이에 여러 번 호출해도 문제가 되지 않는다.

대신 종단 연산을 호출하지 않으면 스트림 자체가 동작하지 않기 때문에 주의해야 한다.

List<Integer> numbers = Arrays.asList(2, 3, 4, 5);

numbers.stream()
	.peek(x -> System.out.println("\nStart Debug"))
	.peek(x -> System.out.println("from stream : " + x))
	.map(x -> x + 17)
	.peek(x -> System.out.println("after stream : " + x))
	.filter(x -> x % 2 == 0)
	.peek(x -> System.out.println("after filter : " + x))
	.limit(3)
	.peek(x -> System.out.println("after limit : " + x))
	.forEach(System.out::println);
    
// 결과    
Start Debug
from stream : 2
after stream : 19

Start Debug
from stream : 3
after stream : 20
after filter : 20
after limit : 20
20

Start Debug
from stream : 4
after stream : 21

Start Debug
from stream : 5
after stream : 22
after filter : 22
after limit : 22
22


// [출처] https://goodgid.github.io/Java-8-Stream-Debug-Peek/

 

[참고]

http://tcpschool.com/java/java_stream_intermediate

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

'Java' 카테고리의 다른 글

[스트림 활용] 생성  (0) 2021.05.18
[Effective Java 7장] 람다  (0) 2021.05.09