개발자는 기록이 답이다

스트림과 병렬처리(2) - 필터링,매핑, 정렬,루핑 본문

언어/Java

스트림과 병렬처리(2) - 필터링,매핑, 정렬,루핑

slow-walker 2023. 12. 4. 00:35

 

1. 필터링(distinct(), filter())

 

필터링은 중간 처리 기능으로 요소를 걸러내는 역할을 한다.

필터링 메소드인 distinct()와 filter()메소드는 모든 스트림이 가지고 있는 공통 메소드이다.

distinct()메소드는 중복을 제거하는데, Stream의 경우 Object.equals(Object)가 true이면 동일한 객체로 판단하고 중복을 제거한다.

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

package org.example.chapter16.filtering;

import java.util.Arrays;
import java.util.List;

public class FilteringExample {

    public static void main(String[] args) {
        List<String> names = Arrays.asList("홍길동","신용권","김자바","신용권","신민철");

        names.stream()
                .distinct() // 중복 제거
                .forEach(n -> System.out.println(n));

        System.out.println();

        names.stream()
                .filter(n -> n.startsWith("신")) // 필터링
                .forEach(n -> System.out.println(n));

        System.out.println();

        names.stream()
                .distinct() // 중복 제거 후 필터링
                .filter(n -> n.startsWith("신"))
                .forEach(n -> System.out.println(n));
    }
}

 

 

2. 매핑(flatMapXXX(), mapXXX(), asXXXStream(), boxed())

 

매핑(mapping)은 중간처리 기능으로 스트림 요소를 다른 요소로 대체하는 작업을 말한다.

스트림에서 제공하는 매핑 메소드는 flatXXX()와 mapXXX(), 그리고 asDoubleStream(), asLongStream(), boxed()가 있다.

 

 

flatMapXXX()메소드

 

flatMapXXX()메소드는 요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴한다

 

스트림에서 A라는 요소는 A1, A2 요소로 대체되고, B라는 요소는 B1, B2로 대체된다고 가정했을 경우, A1, A2, B1, B2 요소를 가지는 새로운 스트림이 생성된다.

 

아래 예제는 List<String>에 데이터들이 저장되어있다고 가정했을때, 요소별로 단어를 뽑아 단어 스트림으로 재생성한다.

만약 입력된 데이터들이 숫자라면 숫자를 뽑아 숫자 스트림으로 재생성한다.

package org.example.chapter16.flatMapXXX;

import java.util.Arrays;
import java.util.List;

public class FlatMapExample {

    public static void main(String[] args) {
        List<String> inputList1 = Arrays.asList("java8 lamda", "stream mapping");

        inputList1.stream()
                .flatMap(data -> Arrays.stream(data.split(" ")))
                .forEach(word -> System.out.println(word));

        System.out.println();

        List<String> inputList2 = Arrays.asList("10, 20, 30","40, 50, 60");
        inputList2.stream()
                .flatMapToInt(data -> {
                    String[] strArr = data.split(",");
                    int[] intArr = new int[strArr.length];
                    for (int i = 0; i < strArr.length; i++) {
                        intArr[i] = Integer.parseInt(strArr[i].trim());
                    }
                    return Arrays.stream(intArr);
                })
                .forEach(number -> System.out.println(number));
    }
}

 

mapXXX()메소드

 

mapXXX()메소드는 요소를 대체하는 요소로 구성된 새로운 스트림을 리턴한다.

 

스트림에서 A요소는 C요소로 대체되고, B요소는 D요소로 대체된다고 했을 경우 C, D요소를 가지는 새로운 스트림이 생성된다.

 

아래 예제는 학생List에서 학생의 점수를 요소로 하는 새로운 스트림을 생성하고, 점수를 순차적으로 출력한다.

package org.example.chapter16.mapXXX;

import org.example.chapter16.Student;

import java.util.Arrays;
import java.util.List;

public class mapExample {
    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(
                new Student("홍길동",10),
                new Student("신용권",20),
                new Student("유미선",30)
                );

        studentList.stream()
                .mapToInt(Student :: getScore)
                .forEach(score -> System.out.println(score));
    }
}

 

asDouboleStream(), asLongStream(), boxed()메서드

 

asDouboleStream()메소드는 IntStream의 Int요소 또는 LongStream의 long요소를 double요소로 타입 변환해서 DoubleStream을 생성한다. 마찬가지로 asLongStream()메소드는 IntStream의 int요소를 long요소로 타입 변환해서 LongStream을 생성한다.

boxed()메소드는 int,long,double 요소를 Integer, Long, Double 요소로 박싱해서 Stream을 생성한다.

 

 

아래 예제는 int[] 배열로부터 IntStream을 얻고 난 다음 int요소를 double 요소로 타입 변환해서 DoubleStream을 생성한다.

또한 int요소를 Integer 객체로 박싱해서 Stream<Integer>를 생성한다.

 

package org.example.chapter16.asDoubleStreamAndBoxed;

import java.util.Arrays;
import java.util.stream.IntStream;

public class AsDoubleStreamAndBoxedExample {
    public static void main(String[] args) {
        int[] intArray = {1, 2, 3, 4, 5};

        IntStream intStream = Arrays.stream(intArray);
        intStream
                .asDoubleStream()
                .forEach(d -> System.out.println(d));

        System.out.println();

        intStream = Arrays.stream(intArray);
        intStream
                .boxed()
                .forEach(obj -> System.out.println(obj.intValue()));
    }
}

 

3. 정렬(sorted())

 

스트림은 요소가 최종 처리되기 전에 중간 단계에서 요소를 정렬해서 최종 처리 순서를 변경할 수 있다.

 

객체 요소일 경우에는 클래스가 Comparable을 구현하지 않으면 sorted()메소드를 호출했을 때 ClassCastException이 발생하기 때문에 Comparable을 구현한 요소에서만 sorted()메소드를 호출해야 한다.

 

다음은 점수를 기준으로 Student 요소를 오름차순으로 정렬하기 위해 Comparable을 구현했다.

package org.example.chapter16;

public class Student implements  Comparable<Student> {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }

    @Override
    public int compareTo(Student o) {
        return Integer.compare(score, o.score);
    }
    // score < o.score : 음수리턴
    // score == o.score : 0 리턴
    // socre > o.score : 양수리턴
}

 

객체 요소가 Comparable을 구현한 상태에서 기본 비교(Comparable)방법으로 정렬하고 싶다면 다음 세가지 방법 중 하나를 선택하면 된다.

sorted();
sorted( (a,b) -> a.compareTo(b));
sorted( Comparator.naturalOrder() );

 

만약 객체 요소가 Comparable을 구현하고 있지만, 기본 비교 방법과 정반대 방법으로 정렬하고 싶다면 아래처럼 하면 된다

sorted( (a,b) -> b.compareTo(a));
sorted( Comparator.reverseOrder() );

 

객체 요소가 Comparable를 구현하지 않았다면 Comparator를 매개값으로 갖는 sorted()메소드를 사용하면 된다.

Comparator는 함수적 인터페이스 이므로 아래와 같이 람다식으로 매개값을 작성할 수 있다.

sorted( (a,b) -> {...});

 

중괄호{} 안에는 a와 b를 비교해서 a가 작으면 음수, 같으면0, a가 크면 양수를 리턴하는 코드를 작성하면 된다.

 

 

아래 예제는 숫자 요소일 경우 오름차순으로 정렬한 후 출력한다. Student요소일 경우에는 Studnet의 기본 비교(Comparable)방법을 이용해서 점수를 기준으로 오름차순으로 정렬한 후 출력한다. 그리고 Comparator를 제공해서 점수를 기준으로 내림차순으로 정렬한 후 출력한다.

package org.example.chapter16.sorting;

import org.example.chapter16.Student;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.IntStream;

public class SortingExample {
    public static void main(String[] args) {
        //숫자 요소일 경우
        IntStream intStream = Arrays.stream(new int[]{5, 3, 2, 1, 4});
        intStream
                .sorted() // 숫자를 오름차순으로 정렬
                .forEach(n -> System.out.print(n + ","));
        System.out.println();

        //객체 요소일 경우
        List<Student> studentList = Arrays.asList(
                new Student("홍길동", 30),
                new Student("신용권", 10),
                new Student("류미선", 20)
                );

        studentList.stream()
                .sorted() // 점수를 기준으로 오름차순 Student 정렬
                .forEach(s -> System.out.print(s.getScore() + ","));
        System.out.println();

        studentList.stream()
                .sorted(Comparator.reverseOrder()) // 점수를 기준으로 내림차순 Student 정렬
                .forEach(s -> System.out.print(s.getScore() + ","));
    }
}

 

4. 루핑(peek(), forEach())

 

루핑(looping)은 요소 전체를 반복하는 것을 말한다.

루핑하는 메소드에는 peek(), forEach()가 있다. 이 두 메소드는 루핑한다는 기능에서는 동일하지만, 동작 방식은 다르다.

peek()는 중간 처리 메소드이고, forEach()는 최종 처리 메소드이다.

 

peek()는 중간 처리 단계에서 전체 요소를 루핑하면서 추가적인 작업을 하기 위해 사용한다. 최종 처리 메소드가 실행되지 않으면 지연되기 때문에 반드시 최정 처리 메소드가 호출되어야 동작한다.

 

예를 들어, 필터링 후 어떤 요소만 남았는지 확인하기 위해 다음과 같이 peek()를 마지막에서 호출할 경우, 스트림은 전혀 동작하지 않는다.

intStream
	.filter(a -> a%2==0)
	.peek(a -> System.out.println(a))

 

요소 처리의 최종 단계가 합을 구하는 것이라면, 최종 처리 메소드인 sum()을 호출해야 정상적으로 동작한다.

intStream
	.filter(a -> a%2==0)
	.peek(a -> System.out.println(a))
  	.sum()

 

하지만 forEach()는 최종 처리 메소드이기 때문에 파이프라인 마지막에서 루핑하면서 요소를 하나씩 처리한다.

forEach()는 요소를 소비하는 최종 처리 메소드이므로 이후에 sum()과 같은 다른 최종 메소드를 호출하면 안된다.

package org.example.chapter16.looping;

import java.util.Arrays;

public class LoopingExample {
    public static void main(String[] args) {
        int[] intArr = {1, 2, 3, 4, 5};

        System.out.println("[peek()를 마지막에 호출한 경우]");
        Arrays.stream(intArr)
                .filter(a -> a%2 == 0)
                .peek(n -> System.out.println(n)); // 동작하지 않음

        System.out.println("[최종 처리 메소드를 마지막에 호출한 경우]");
        int total = Arrays.stream(intArr)
                .filter(a -> a%2 == 0)
                .peek(n -> System.out.println(n)) // 동작함
                .sum();// 최종 처리 메소드

        System.out.println("총합: " + total);

        System.out.println("[forEach()를 마지막에 호출한 경우]");
        Arrays.stream(intArr)
                .filter(a -> a%2 == 0)
                .forEach(n -> System.out.println(n)); // 최종 처리 메서드로 동작함
    }
}