Stream, Stream API

JONGCHAN SEO·2024년 8월 12일

자바 기본

목록 보기
7/10

자바에서는 많은 양의 데이터를 저장하기 위해서 배열이나 컬렉션을 이용한다. 이렇게 저장된 데이터에 접근하기 위해서는
반복문이나 반복자(iterator)를 사용하여 매번 새로운 코드를 작성해왔다.

또 다른 문제는 데이터 소스마다 다른 방식으로 다뤄야한다는 점이다.
Collection이나 Iterator와 같은 인터페이스의 각 컬렉션 클래스에는 같은 기능의 메서드들이 중복해서 정의되어 있다.

예를 들면,
List를 정렬할 때는 Collections.sort()를 사용하고, 배열을 정렬할 때는 Arrays.sort()를 사용한다는 점이다.

이러한 문제를 극복하기 위해 나온 것이 바로 Stream이다.
프로그래밍에서의 Stream은 '데이터의 흐름'을 말한다.

스트림은 데이터 소스를 추상화하여 데이터 소스가 무엇이던 간에 같은방식으로 다룰 수 있게 되어, 코드의 재사용성이 높다.

스트림을 이용하면, 배열이나 컬렉션뿐만 아니라 파일에 저장된 데이터도 모두 같은 방식으로 다룰 수 있다.

스트림의 특징

  1. 스트림은 데이터 소스를 변경하지 않는다.
    데이터 소스를 읽기만 할뿐, 데이터 소스를 변경하지 않는다.

  2. 스트림은 일회용이다.
    Iterator처럼 일회용이며, Iterator로 컬렉션의 요소를 모두 읽고 나면 사용할 수 었는 것 처럼, 스트림도 사용하면 다시 사용할 수 없고, 다시 생성해야 한다.

  3. 스트림은 작업을 내부 반복으로 처리한다.
    스트림을 이용한 작업이 간결할 수 있는 이유중 하나가 '내부반복'이다.

    (내부 반복 : Stream과 같이 개발자가 직접 반복을 제어하지 않고 컬렉션 내부적으로 반복을 처리. 사용자는 각 요소에 대한 처리 로직에만 집중할 수 있으며 코드의 가독성을 높인다.)

스트림은 이 3단계를 걸친다.
Stream생성().중간연산().최종연산(); 이다.

Collection으로부터 생성

List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream();

Array로부터 생성

String[] arr= {"a","b","c"};
Stream<String> stream = Arrays.stream(arr);

Stream.of() 이용

Stream<String> stream = Stream.of("a","b","c");

Stream 중간 연산

중간 연산은 Stream을 반환하기 때문에, 여러 개의 중간 연산을 연결하여 사용할 수 있다.

filter() / distinct()

filter
조건에 맞는 요소만을 선택하여 Stream으로 반환한다.

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

distinct()

list.stream().distinct()

Stream 요소들의 중복 제거

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

public class FilteringEx {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "a", "b", "b", "c", "c");

        // 중복 제거
        list.stream()
            .distinct()
            .forEach(System.out::println);

        // "a"인 리스트만 출력
        list.stream()
            .filter(str -> str.equals("a"))
            .forEach(System.out::println);

        // "a"인 리스트를 중복 제거하여 출력
        list.stream()
            .distinct()
            .filter(str -> str.equals("a"))
            .forEach(System.out::println);
    }
}

map()

Stream의 요소들을 내가 사용할 형태로 바꾸거나,사용할 요소를 뽑아내는 것 기존의 stream 요소들을 변환하여 새로운 stream을 형성하는 연산이다.

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

public class MapEx {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "java", "world");

        list.stream()
            .map(s -> s.toUpperCase())
            .forEach(System.out::println);
    }
}

Sorted()

sorted() 메서드는 Stream 요소들을 정렬하기 위해 사용한다.
내림차순 정렬을 하기 위해서는 Comparator의 reverseOrder()를 사용한다.

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

public class SortedEx {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        // 오름차순 정렬
        list.stream()
            .sorted()
            .forEach(System.out::print);

        System.out.println();

        // 내림차순 정렬
        list.stream()
            .sorted(Comparator.reverseOrder())
            .forEach(System.out::print);
    }
}

Peek()

peek()메서드와 foreach() 메서드는 요소를 하나씩 돌면서 출력하는 동일한 기능이다. 하지만, 세부적인 동작 방식에 차이가 있다.
peek()는 중간 연산자 forEach()는 최종연산이기 떄문에

따라서
peek()는 하나의 스트림에 여러 번 사용 가능하며, forEach()는 한번만 가능하다.

package Section1.stream;

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

public class PeekEx {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        int result = list.stream()
            .filter(num -> num % 2 == 0)
            .peek(num -> System.out.println(num))
            .mapToInt(num -> num)
            .sum();
        System.out.println(result);
    }
}

limit()

limit() 메서드는 스트림에서 입력 값의 숫자만큼 요소들을 가져와 새로운 스트림을 생성한다.

public class LimitEx {
    public static void main(String[] args) {
        IntStream intStream = IntStream.rangeClosed(1, 10);
        intStream.limit(5)
            .forEach(System.out::println);
    }
} // 1 2 3 4 5

skip()

skip() 메서드는 limit() 메서드와 반대로 입력 값의 숫자만큼 요소들을 건너뛰고, 그 이후의 요소들로 이루어진 스트림을 생성한다.

public class SkipEx {
    public static void main(String[] args) {
        IntStream intStream = IntStream.rangeClosed(1, 10);
        intStream.skip(5)
            .forEach(System.out::println);
    }
} // 6 7 8 9 10

Stream 최종 연산

forEach()

Stream의 요소들을 대상으로 어떤 특정한 연산을 수행하고 싶은 경우에는 forEach를 사용할 수 있다.
실제 요소들에 영향을 줄 수 있으며, 반환값이 존재하지 않는다.

names.stream()
	.forEach(System.out::println);
    // :: 는 메소드 참조의 의미
package Section1.stream;

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

public class ForEachEx {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        list.stream()
            .filter(num -> num % 2 == 0)
            .forEach(num -> System.out.println(num));
    }
}

match()

match() 메서드는 Stream의 요소들이 특정한 조건을 충족하는지 검사할 때 사용한다.
match() 메서드는 함수형 인터페이스 Predicate를 받아 조건을 검사하고 검사 결과를 boolean 타입으로 반환한다.

match() 메서드의 종류

  • allMatch() : 모든 요소들이 매개 값으로 주어진 Predicate의 조건 검사
  • anyMatch() : 최소한 한 개의 요소가 매개 값으로 주어진 Predicate의 조건 검사
  • noneMatch() : 모든 요소들이 매개 값으로 주어진 Predicate의 만족하지 않는 조건 검사
import java.util.Arrays;

public class MatchEx {
    public static void main(String[] args) {
        int[] arr = {2, 4, 6, 8, 10};

        boolean result = Arrays.stream(arr).allMatch(num -> num % 2 == 0);
        System.out.println(result);

        result = Arrays.stream(arr).anyMatch(num -> num % 3 == 0);
        System.out.println(result);

        result = Arrays.stream(arr).noneMatch(num -> num % 7 == 0);
        System.out.println(result);
    }
}

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

집계에 대한 메서드로 모두 최종 연산 메서드이다.
sum() : 요소들의 합
count() : 요소들의 개수
average() : 요소들의 평균
max() : 요소들의 최댓값
min() : 요소들의 최솟값

import java.util.Arrays;

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

        long count = Arrays.stream(arr).count();
        System.out.println(count);

        long sum = Arrays.stream(arr).sum();
        System.out.println(sum);

        double avg = Arrays.stream(arr).average().getAsDouble();
        System.out.println(avg);

        int max = Arrays.stream(arr).max().getAsInt();
        System.out.println(max);

        int min = Arrays.stream(arr).min().getAsInt();
        System.out.println(min);
    }
}

reduce()

reduct() 메서드는 집계 결과물을 다양하게 나타내고자 할 때 사용한다.
reduce() 메서드는 하나로 응축하는 방식으로 동작하여 앞의 두 요소의 연산 결과를 바탕으로 다음 요소와 연산한다.

collect()

Stream의 요소를 수집하여 새로운 Collection,List,Set 등으로 반환한다.

Stream<String> stream = Stream.of("apple","banana","cherry","durian");
List<String> list = stream.collect(Colletors.toList());

Optional

자바에서 null 참조시 NullPoniterException을 방지해주는 클래스다.
NullPointerException이 너무 자주 발생해서 데이터가 null이어도 처리가 가능하도록 도와주는 것이 Optional이다.

orElseThrow()

Optional의 인자가 null 일경우 예외 처리를 시킨다.

User user = userRepository.findbyId(id)
	.orElseThrow(() -> new IllegalArgumentException("doesn't exist");
profile
잘 배워가겠습니다.

0개의 댓글