[Java] Stream 사용법

이신영·2024년 1월 10일
1

Java

목록 보기
1/11
post-thumbnail
post-custom-banner

자바에는 데이터를 가공하는방법이 여러가지가있다. collection을 쓴다거나 배열이나 리스트를 쓴다거나 데이터를 가공하는 여러가지 방법이 있다. 그중에 Java 8 부터 사용이 가능한 야무진 Stream에 대해서 알아보자~ 코테준비하면서 Stream만 쓰는 한줄좌가 늘 있었는데 이거 진짜 편해보이긴하더라구요 ㅎㅎ 그래서 정리하면서 배울라구요

Stream이란?

stream은 실제 데이터 처리의 이상화된 흐름을 의미한다. 예를들어 내가 택배를 배송받을때에는 집화처리→간선상차→간선하차→배송출고→배송완료 순으로 도착하듯이 있듯이 자바에서 stream은 데이터를 처리하는 중간매개자 역할을 한다고 보면된다!

Stream의 동작

먼저 스트림의 동작 흐름을 알아두면 좋은데

  1. Stream 생성
  2. Stream의 중간연산(필터링)
  3. Stream의 최종연산(매핑)

순으로 진행이 된다. 약간 데이터를 처리하려고 포장하는느낌? 이 Stream을 잘 쓴다면 데이터를 처리하는부분은 이 API를 사용하면 될정도로 기능이 다양하고 유용하다! 이제 쓰는방법을 알아보자~


Stream 사용법

기본 stream 생성

String[] myArray = { "apple", "banana", "orange", "grape", "watermelon" };

Stream<String> myStream = Arrays.stream(myArray);

자료형 stream 생성

IntStream intStream = IntStream.range(1, 5); 
// [1, 2, 3, 4]
LongStream longStream = LongStream.rangeClosed(1, 5); 
// [1, 2, 3, 4, 5]
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0, 4.0, 5.0); // [1.0, 2.0, 3.0, 4.0, 5.0)]

여기서 코테풀때 유용하게 쓰이는건

IntStream stream = "Hello,World".chars(); //(72, 101, 108, 108, 111, 44, 87, 111, 114, 108, 100)

이건듯? chars() 메소드를 사용해서 문자열의 문자를 IntStream으로 변환한것

stream에서 표현방법

List<String> strList = Arrays.asList("apple", "orange", "banana");

// 람다식을 이용한 경우
strList.forEach(str -> System.out.println(str));

// 메서드 레퍼런스를 이용한 경우 (:: 연산자 사용)
strList.forEach(System.out::println);

표현방법은 두가지인데 람다식으로 사용하거나 ::연산자를 사용해서 메서드를 참조하면된다!

중간연산

Stream 인터페이스

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    
    // 주어진 조건에 따라 요소를 걸러냅니다.
    Stream<T> filter(Predicate<? super T> predicate);
    
    // 각 요소를 다른 요소로 변환합니다.
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    
    // 요소를 int로 변환한 IntStream을 반환합니다.
    IntStream mapToInt(ToIntFunction<? super T> mapper);
    
    // 요소를 long으로 변환한 LongStream을 반환합니다.
    LongStream mapToLong(ToLongFunction<? super T> mapper);
    
    // 요소를 double로 변환한 DoubleStream을 반환합니다.
    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
    
    // 각 요소를 다른 스트림으로 변환하고 평탄화시킵니다.
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    
    // 요소를 int로 변환한 IntStream을 반환하고 평탄화시킵니다.
    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
    
    // 요소를 long으로 변환한 LongStream을 반환하고 평탄화시킵니다.
    LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
    
    // 요소를 double로 변환한 DoubleStream을 반환하고 평탄화시킵니다.
    DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
    
    // 중복된 요소를 제거한 스트림을 반환합니다.
    Stream<T> distinct();
    
    // 요소를 정렬된 순서로 스트림을 반환합니다.
    Stream<T> sorted();
    
    // 지정된 비교자에 따라 요소를 정렬한 스트림을 반환합니다.
    Stream<T> sorted(Comparator<? super T> comparator);
    
    // 각 요소를 확인하거나 디버깅을 위해 사용됩니다.
    Stream<T> peek(Consumer<? super T> action);
    
    // 최대 크기를 지정하여 스트림의 크기를 제한합니다.
    Stream<T> limit(long maxSize);
    
    // 처음 n개의 요소를 건너뜁니다.
    Stream<T> skip(long n);
    
    ...
}

종류가 엄청많죠? 중간연산은 보통 람다식을 혼합해서 쓰는경우가 많이 있다. 반환값으론 stream을 리턴하기때문에 이어서 호출하기 유용하다. 그 후 모든 중간연산을 합치고 마지막에 한번에 처리한다. = 지연(lazy)연산을 한다!

중간연산 예시

filter 메서드로 필터링

List<String> myList = Arrays.asList("apple", "banana", "orange", "grape", "watermelon");

Stream<String> filteredStream = myList.stream().filter(s -> s.startsWith("a"));
// "apple"

myList를 생성한다음 filter() 메소드안에 람다식을 통해 "a"로 시작하는 문자열만 filteredStream에 담는다 즉 apple만 담기겠죠?

map 메서드로 형태 변환

List<String> myList = Arrays.asList("apple", "banana", "orange", "grape", "watermelon");

Stream<String> mappedStream = myList.stream().map(String::toUpperCase);
// "APPLE", "BANANA", "ORANGE", "GRAPE", "WATERMELON"

이 외에도 특정 데이터타입으로 변환할때도 쓰이는데 방법을 보여주자면

List<String> numbersAsString = Arrays.asList("1", "2", "3", "4", "5");

// 문자열을 정수로 변환하는 예시
IntStream intStream = numbersAsString.stream()
                                    .mapToInt(Integer::parseInt);

// 정수로 변환된 스트림 요소들을 합산하는 예시
int sum = numbersAsString.stream()
                         .mapToInt(Integer::parseInt)
                         .sum();

주로 Integer인 arraylist를 int배열로 바꾸거나 할때 쓰이기도한다!

list.stream()
            .mapToInt(i->i)
            .toArray();

람다를 활용하자면 요로코롬 말이죠

sort 메서드로 정렬

List<String> myList = Arrays.asList("apple", "banana", "orange", "grape", "watermelon");

Stream<String> sortedStream = myList.stream().sorted();

//"apple", "banana", "grape", "orange", "watermelon"

limit 메서드로 뒤에 생략하기

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 처음 5개의 요소를 가져오는 스트림
List<Integer> limitedList = numbers.stream()
                                   .limit(5)
                                   .collect(Collectors.toList());
//[1, 2, 3, 4, 5]

skip 메서드로 건너뛰기

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 처음 5개의 요소를 건너뛴 스트림
List<Integer> skippedList = numbers.stream()
                                   .skip(5)
                                   .collect(Collectors.toList());
//[6, 7, 8, 9, 10]

distinct 메서드로 중복제거

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


// 중복을 제거한 요소로 이루어진 스트림
List<Integer> distinctList = numbers.stream()
                                     .distinct()
                                     .collect(Collectors.toList());
//[1, 2, 3, 4, 5]

최종 연산

public interface Stream<T> extends BaseStream<T, Stream<T>> {

    // 각 요소에 대해 주어진 작업을 수행합니다.
    void forEach(Consumer<? super T> action);
    
    // 요소의 순서가 유지된 상태로 각 요소에 대해 주어진 작업을 수행합니다.
    void forEachOrdered(Consumer<? super T> action);
    
    // 스트림의 요소를 배열로 변환하여 반환합니다.
    Object[] toArray();
    
    // 지정된 생성자로 배열을 생성하여 스트림의 요소를 반환합니다.
    <A> A[] toArray(IntFunction<A[]> generator);
    
    // 스트림의 요소를 줄여 하나의 값을 반환합니다.
    T reduce(T identity, BinaryOperator<T> accumulator);
    
    // 스트림의 요소를 반복적으로 처리하여 최종 값을 반환합니다.
    Optional<T> reduce(BinaryOperator<T> accumulator);
    
    // 병렬 실행 시 스트림의 요소를 반복적으로 처리하고 병합하여 최종 값을 반환합니다.
    <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
    
    // 스트림의 요소를 수집하여 결과를 반환합니다.
    <R, A> R collect(Collector<? super T, A, R> collector);
    
    // 지정된 비교자에 따라 스트림의 최소 요소를 찾아 반환합니다.
    Optional<T> min(Comparator<? super T> comparator);
    
    // 지정된 비교자에 따라 스트림의 최대 요소를 찾아 반환합니다.
    Optional<T> max(Comparator<? super T> comparator);
    
    // 스트림의 요소 개수를 반환합니다.
    long count();
    
    // 스트림의 요소 중 주어진 조건에 부합하는 요소가 하나라도 있는지 확인합니다.
    boolean anyMatch(Predicate<? super T> predicate);
    
    // 스트림의 모든 요소가 주어진 조건을 만족하는지 확인합니다.
    boolean allMatch(Predicate<? super T> predicate);
    
    // 스트림의 모든 요소가 주어진 조건을 만족하지 않는지 확인합니다.
    boolean noneMatch(Predicate<? super T> predicate);
    
    // 스트림에서 첫 번째 요소를 찾아 반환합니다.
    Optional<T> findFirst();
    
    // 스트림에서 아무 요소나 찾아 반환합니다.
    Optional<T> findAny();
    
    // ...
}

중간연산은 stream을 리턴하는 반면 최종연산은 stream을 리턴하지않아서 메소드를 연결할수없다.

최종연산 예시

forEach 메서드로 순회

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 각 요소에 대해 작업을 수행
names.stream().forEach(System.out::println);

근데 forEach는 연산순서를 보장하지않기때문에 다수의 스트림을 사용할 때 순서를 보장받고싶다면 forEachOrdered를 사용하면된다!

toArray 메서드로 스트링을 배열로 변환

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// String 배열로 변환
String[] namesArray = names.stream().toArray(String[]::new);

reduce 메서드로 요소들 합 구하기

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

// 모든 요소의 합을 구함
int sum = numbers.stream().reduce(0, Integer::sum);

collect 메서드로 요소들 모으기

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 리스트로 수집
List<String> collectedNames = names.stream()
                                  .collect(Collectors.toList());

// 집합으로 수집
Set<String> collectedSet = names.stream()
                                .collect(Collectors.toSet());

// 문자열로 결합
String concatenatedNames = names.stream()
                                .collect(Collectors.joining(", "));

min, max 메서드로 최소,최대 출력

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

// 최소값 찾기
Optional<Integer> min = numbers.stream().min(Integer::compareTo);

// 최대값 찾기
Optional<Integer> max = numbers.stream().max(Integer::compareTo);

anyMatch, allMatch, noneMatch 메서드로 요소 조건 확인

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

// 조건에 부합하는 요소가 하나라도 있는지 확인
boolean anyGreaterThanFive = numbers.stream().anyMatch(num -> num > 5);

// 모든 요소가 조건을 만족하는지 확인
boolean allGreaterThanZero = numbers.stream().allMatch(num -> num > 0);

// 조건을 만족하는 요소가 없는지 확인
boolean noneGreaterThanTen = numbers.stream().noneMatch(num -> num > 10);

findFirst, findAny 메서드로 요소찾기

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 첫 번째 요소 찾기
Optional<String> first = names.stream().findFirst();

// 아무 요소나 찾기
Optional<String> any = names.stream().findAny();

stream의 가장 큰 장점은 가독성도 높고 잘 알고 쓸수록 다양하게 표현할 수 있는 좋은 API인듯! Java 8 이니까 실무에서도 쓰면 좋지않을까? 하는생각이 들기도한다~ 잘 써봐야지 ㅎㅎ

profile
후회하지 않는 사람이 되자 🔥
post-custom-banner

0개의 댓글