1. 스트림이란

 

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

java 8ㅂ터는 또 다른 방법으로 컬렉션 및 배열의 요소를 반복 처리하기 위해 스트림을 사용할 수 있다.

스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다.

 

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

public static void main(String[] args) {
//Set 컬렉션 생성
Set<String> set = new HashSet<>();
set.add("홍길동");
set.add("신용권");
set.add("김자바");

//Stream을 이용한 요소 반복 처리
Stream<String> stream = set.stream();
stream.forEach(name -> System.out.println(name));

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

 

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

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

 

2. 내부 반복자

 

for문과 Iterator는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리하는데, 이것을 외부 반복자라고 한다.

반면 스트림은 요소 처리 방법을 컬렉션 내부로 주입시켜서 요소를 반복처리하는데, 이것을 내부 반복자라고 한다. 다음 그림을 보면서 외부 반복자와 내부 반복자를 이해해보자.

 

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

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

 

내부 반복자는 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있다.

하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있는 장점이 있다.

 

public static void main(String[] args){

List<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("a");
list.add("a");
list.add("a");

Stream<String> parallelStream = list.parrallelStream();
parallelStream.forEach( name -> {
	System.out.println(name + ": " + Thread.currentThread().getName());
    });

 

3. 중간처리와 최종 처리

 

스트림은 하나 이상 연결될 수 있다. 다음 글미을 보면 컬렉션의 오리지널  스트림 뒤에 필터링 중간 스트림이 연결될 수 있고,

그 뒤에 매핑 중간 스트림이 연결될 수 있다. 이와 같이 스트림이 연결되어 있는 것을 스트림 파이프라인이라고 한다.

 

오리지널 스트림고 집계 처리 사이의 중간 스트림들은 최종 처리를 위해 요소를 걸러내거나(필터링), 요소를 반환시키거나(매핑), 정렬하는 작업을 수행한다. 최종 처리는 중간 처리에서 정제된 요소들을 반복하거나, 집계(카운팅, 총합, 평균) 작업을 수행한다

다음 그림은 Student 객체를 요소로 가지는 컬렉션에서 Student 스트림을 얻고, 중간 처리를 통해 score 스트림으로 변환한 후 최종 집계 처리로 score 평균을 구하는 과정을 나타낸 것이다.

 

//Student 스트림
Stream<Student> studentStream = list.stream();

//score 스트림
IntStream scoreStream = studentStream.mapToInt( student -> student.getScore() );

//평균 계산
double avg = scoreStream.average().getAsDouble();

//메소드 체이닝 패턴을 이용하면 앞의 코드를 다음과 같이 더 간결하게 작성할 수 있다.
double avg = list.stream()
.mapToInt(student -> student.getScore())
.average()
.getAsDouble();

 

mapToInt 메소드는 객체를 int값으로 매핑해서 Intstream으로 변환시킨다.

 

4. 리소스로부터 스트림 얻기

java.util.stream 패키지에는 스트림 인터페이스들이 있다.

BaseStream 인터페이스를 부모로 한 자식 인터페이스들은 다음과 같은 상속 관계를 이루고 있다.

 

                         

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

Stream은 객체 요소를 처리하는 스트림이고, int, long, double 요소를 처리하는 스트림이다.

이 스트림 인터페이스들의 구현 객체는 다양한 리소스로부터 얻을 수 있다.

주로 컬렉션과 배열에서 얻지만 다음과 같은 리소스로부터 구현 객체를 얻을 수 있다.

 

l

 

1.컬렉션으로 스트림 얻기 - Stream<Product> stream = list.stream();

  컬렉션으로 스트림 얻기 - Stream<Product>parallelStream = set.parrallelStream();

 

2.배열로부터 스트림얻기 -

 

public class StreamExample {

    public static void main (String[] args) {

        String[] strArray = { "홍길동", "신용권", "김미나"};

        Stream<String> strStream = Arrays.stream(strArray);

        strStream.forEach(item -> System.out.print(item + ","));

 

      int[] intArray { 1, 2, 3, 4, 5};

     IntStream intStream = Arrays.stream(intArray);

    stream.forEach(item -> System.out.print(item _ ",));

    }

}

 

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

public class StreamExample {

    public static void main (String[] args) {

        public static int sum;

 

        IntStream stream = IntStream.rangeClosed(1, 100);

        IntStream stream = IntStream.rangeClosed(1, 100);

        stream.forEach(a -> sum += a);

        System.out.println("총합 " + sum);

    }

 

 

5. 요소 걸러내기

 

 

 

1. 중복, 필터링

 

List<String> list = new ArrayList<>();

 

//중복요소제거

list.stream

    .distinct()

    .filter(n -> n.startsWith("신"))

    .forEach(n -> System.out.println(n));

 

//신으로 시작하는 요소만 필터링

list..stream()

    .filter(n -> n.startsWith("신")

    .forEach(n -> System.out.println(n));

System.out.println

 

//중복 요소를 먼저 제거하고, 신으로 시작하는 요소만 필터링

list.stream()

    .distinct()

    .filter(n -> n.startWith("신"))

    .forEach(n -> System.out.println(n));

 

6. 요소변환(매핑)

 

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

 

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

 

public class MapExample {

    public static void main(String[] args){

        List<Student> studentList = new ArrayList<>();

 

        studentList.stream()

            .mapToInt( s -> s.getScore())

            .forEach

    }

}

 

 

 

public class MapExample {

    public static void main(String[] args){

        int[] intArray = { 1, 2,3, 4, 5};

 

        IntStream intStream = Arrays.stream(intArray);

        intStream

            .asDoubleStream()

            .forEach(d -> System.our.println(d));

 

        intStream = Arrays.stream(intArray);

        intStream

            .boxed()

            .forEach(obj -> System.out.println(obj.intvalue()));

        }

}

 

 

7. 요소 정렬

 

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

요소를 정렬하는 메소드는 다음과 같다.

 

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

 

 

Comparable 구현 객체의 정렬

스트림 요소가 객체일 경우 객체가 Comparabled을 구현하고 있어야만 sorted() 메소드를 사용하여 정렬할 수 있다.

그렇지 않다면 classCastException이 발생한다. Comparable을 구현하는 자세한 방법은 15.5절을 참고하자

 

public Xxx implements Comparable {

 

}

 

List<Xxx> listt = new ArrayList<>();

Stream<Xxx> stream = list.stream;

Stream<Xxx> Orderedstream = stream.sorted();

 

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

 

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

다음은 student 스트림을 score 기준으로 올림차순 또는 내림차순으로 정렬한 새로운 Student 스트림을 생성하는 방법을 보여준다. 정렬을 하기 위해 Student 클래스가 Comparable을 구현하고 있는 것을 볼 수 있다.

 

public class Student implements Comparable<Student>{

  

}

 

public static void main(String[] args){

//List 컬렉션 생성

    List<Student> studentList = new ArrayList<>();

 

//점수를 기준으로 오름차순으로 정렬한 새 스트림 얻기

    studentList.stream()

    .sorted()

    .forEach(s -> 출력)

 

//점수를 기준으로 내림차순으로 정렬한 새 스트림 얻기

studentList.Stream()

    .sorted(Comparator.reverseOrder())

    .forEach(s -> 출력)

 

//comparable이 없어서 점수를 기준으로 오름차순으로 정렬한 새 스트림 얻기

studentList.stream()

    .sorted(s1, s2) -> Integer.compare(s1.gertScore(), s2.getScore()))

    .forEach

 

////comparable이 없어서 점수를 기준으로 내림차순으로 정렬한 새 스트림 얻기

studentList.stream()

    .sorted(s1, s2) ->Integer.compare(s2.getScore(), s1.getScore()))

    .forEach(s -> 출력)

 

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

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

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

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

매개타입인 Consumer는 함수형 인터페이스로, 다음과 같은 종류가 있다.

모든 Consumer는 매개값을 처리(소비)하는 accept() 메소드를 가지고 있다.

Consumer<? super T>를 람다식으로 표현하면 다음과 같다.
T -> {...}
또는
T -> 실행문;

 

 

 

int intArr = {1, 2, 3, 4, 5} ;

 

//잘못 작성한 경우

Array.strem(intArr)

    .filter(a -> a % 2 == 0)

    .peer(n -> System.out.println(n)); //최종 처리가 없으므로 동작하지 않음

 

//중간 처리 메소드 peek()을 이용해서 반복 처리

int total = Arrays.stream(intArr)

    .filter(a -> a%2==0)

    .peek(n -> System.out.println(n)). //동작함

    .sum(); //최종 처리

System.out.println(total); //총합 출력

 

//최종 처리 메소드 forEach()를 이용해서 반복 처리

Arrays.stream(intArr)

    .filter(a -> a%2==0)

    .forEach(n -> System.out.println(n)); //최종처리이므로 동작함

 

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

 

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

 

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

 

Predicate는 함수형 인터페이스로 위의 4개의 종류가 있고

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

ex) filter(n -> n.startWith("신)) -> 주어진 문자열로 시작하면 true 그렇지 않으면 false return

 

boolean result = Arrays.stream(intArr)

    .allMatch(a -> a%2==0);

 

result = Arrays.stream(intArr)

    .anyMatch(a -> a%3 ==0);

 

retult = Arrays.stream(intArr)

    .noneMatch(a -> a%3==0)

 

10. 요소 기본 집계

 

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

 

예를 들어 평균을 구하는 average를 최종 처리에서 사용할 경우, 다음과 같이 3가지 방법으로 요소(집계값)가 없는 경우를 대비할 수 있다.

 

 

11.요소 커스텀 집계

 

reduce()는 스트림에 요소가 없을 경우 예외가 발생하지만, identity 매개값이 주어지면 이 값을 디폴트 값으로 리턴한다.

다음 중 왼쪽 코드는 스트림에 요소가 없을 경우 NoSuchElementException을 발생시키지만, 오른쪽 코드는 디폴트 값(identity)인 0을 리턴한다.

 

//방법1

int sum1 = studentList.stream()

                      .mapToInt(Student :: getScore)

                      .sum();

 

//방법2

int sum2 = studentList.stream

                   .map(Student :: getScore)

                   .reduce(0, (a, b) -> a+b);

 

 

12.요소 수집

스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공한다.

이 메소드를 이용하면 필요한 요소만 컬렉션에 담을 수 있고, 요소들을 그룹핑한 후에 집계도 할 수 있다.

 

필터링한 요소 수집

 

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

 

 

'코딩 > 자바' 카테고리의 다른 글

김영한 스프링 기본편 섹션2  (1) 2023.11.30
김영한 스프링 기본편 섹션1  (2) 2023.11.28
이것이 자바다 람다  (0) 2023.11.26
이것이 자바다 generic 정리  (1) 2023.11.26
String 메소드 자바 17 정리  (1) 2023.11.26

+ Recent posts