[Java] 자바 스트림 API(Java8 Stream API)

Harry park·2022년 5월 15일
0

Java

목록 보기
4/8

Stream API

📌 Stream API 정의

📖 Java의 Stream은 컬렉션(Collection)의 요소롤(element)를 하나씩 참조하여 순차적으로 요소에 접근하는 메소드이다.
↳ 요소를 하나씩 참조하여, 람다식(함수형 인터페이스)으로 처리할 수 있도록 해주는 일종의 반복자
↳ java.io 패키지는 Stream API와는 관련이 없으며, Stream API는 java.util 패키지 하위에 있는 API이다.

📌 Stream API의 특징

📖 스트림은 외부 반복을 통해 작업하는 Collection과는 달리 내부 반복을 통해 작업을 수행한다.
📖 스트림은 재사용이 가능한 컬렉션과는 달리 단 한번만 사용할 수 있다.
↳ 아래 코드와 같이 nameStream 객체를 한 번 사용하는 경우, 반복문 종료와 함게 사용이 불가능하다.

String[] nameArr = {"IronMan", "Captain", "Hulk", "Thor"} 
List<String> nameList = Arrays.asList(nameArr);
Stream<String> nameStream = nameList.stream();
nameStream.sorted().forEach(System.out::println);
int count = nameStream.count();
/* 아래의 에러가 발생하게 된다. 
java.lang.IllegalStateException: 
stream has already been operated upon or closed
*/

📖 스트림은 원본데이터를 변경하지 않는다.
↳ Stream API는 원본의 데이터를 조회하여 원본의 데이터가 아닌 별도의 요소들로 Stream을 생성한다.(원본의 데이터를 읽기만 함.)

📖 스트림의 연산은 필터-맵(filter-map) 기반의 API를 사용하여 지연(lazy) 연산을 통해 성능을 최적화한다.

📌 Stream API의 동작흐름(동작과정)

  1. 스트림의 생성
  2. 스트림의 중개연산(변환)
  3. 스트림의 최종연산(사용)

📖 스트림의 생성

↳ Stream의 연산을 위해서는 Stream 객체를 생성해주어야 한다.
Stream은 컬렉션, 배열, 가변매개변수, 지정된 범위의 연속된 정수, 난수, 람다식, 파일, 빈스트림에서 생성이 가능하다.
다만, Stream연산시 연산이 끝나면 Stream이 닫히기 때문에 Stream이 닫혔을 경우 다시 Stream을 생성해야 한다.

💻 출력(코드예시)

// 1. Collection (ArrayList)
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(4);
list.add(2);
list.add(3);
list.add(1);

Stream<Integer> stream = list.stream();
// forEach() 메소드를 이용한 스트림 요소의 순차 접근
stream.forEach(System.out::println);
String[] arr = new String[]{"넷", "둘", "셋", "하나"};

// 2. 배열에서 스트림 생성
Stream<String> stream1 = Arrays.stream(arr);
stream1.forEach(e -> System.out.print(e + " "));
System.out.println();

// 배열의 특정 부분만을 이용한 스트림 생성
Stream<String> stream2 = Arrays.stream(arr, 1, 3);
stream2.forEach(e -> System.out.print(e + " "));

// 3. 람다 표현식
IntStream stream = Stream.iterate(2, n -> n + 2); // 2, 4, 6, 8, 10, ...

📖 스트림의 변환

↳ 원본의 데이터를 별도의 데이터로 가공하기 위한 중간 연산 단계이다.
↳ 스트림 API에 의해 생성된 초기 스트림은 중개연산을 통해 또 다른 스트림으로 변환되는데, 반환되는 값도 스트림이기 때문에 계속해서 중개 연산을 이어갈 수 있다.

✍ Stream API의 변환 메소드

- filter(), distinct(), map(), skip(), limit(), sorted(), peek()
  1. 필터링 : filter(), distinct()

    1-1. filter()
    ↳ 주어진 조건에 맞는 요소만 구성된 새로운 스트림을 반환.

    List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
    myList.stream().filter(s -> s.startWith("c");

    1-2. distinct()
    ↳ 해당 스트림에 중복된 요소가 제거된 새로운 스트림을 반환.
    (내부적으로 Object 클래스의 equals() 메소드를 사용하여 중복을 비교한다.)

    List<String> myList = Arrays.asList("a1", "a1", "b1", "c2", "c1");
    myList.stream().distinct();
  2. 변환 : map(), flatMap()
    2-1. map()
    ↳ 해당 스트림에 요소들을 특정 조건에 해당하는 값으로 변환

    Stream<String> stream = Stream.of("HTML", "CSS", "JAVA", "JAVASCRIPT");
    stream.map(s -> s.length()).forEach(System.out::println);
  3. 제한 : limit(), skip()
    3-1. limit()
    ↳ 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림을 반환.

    IntStream stream1 = IntStream.range(0, 10);
    stream1.limit(5).foreach(n -> System.out.println(n + " "));
    // 0 1 2 3 4 

    3-2. skip()
    ↳ 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소를 제외한 나머지 요소만으로 이루어진 새로운 스트림을 반환.

    IntStream stream1 = IntStream.range(0, 10);
    stream1.skip(4).foreach(n -> System.out.println(n + " "));
    // 4 5 6 7 8 9  
  4. 정렬 : sorted()
    4-1. sorted()
    ↳ 해당 스트림을 주어진 비교자(comparator)를 이용하여 정렬한다.(default : 사전 편찬순)

    Stream<String> stream1 = Stream.of("JAVA", "HTML", "CSS", "JAVASCRIPT");
    stream1.sorted.foreach(System.out::println);
    /* 
    CSS
    HTML
    JAVA
    JAVASCRIPT
    */
    
  5. 연산 결과 확인 : peek()
    5-1. peek()
    ↳ 결과 스트림으로부터 요소를 소모하여 추가로 명시된 동작을 수행.
    주로, 원본 스트림에서 요소를 소모하지 않으므로 연산과 연산 사이에 결과를 확인하고 싶을 때 사용한다.

    IntStream stream = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
    stream.peek(s -> System.out.println("원본 스트림 : " + s))
    	.skip(2)
    	.peek(s -> System.out.println("skip(2) 실행 후 : " + s))
       .limit(5)
    	.peek(s -> System.out.println("limit(5) 실행 후 : " + s))
    	.sorted()
    	.peek(s -> System.out.println("sorted() 실행 후 : " + s))
    	.forEach(n -> System.out.println(n));

📖 스트림의 사용(최종연산)

✍ Stream API의 연산 메소드

- forEach(), reduce(), findFirst(), findAny(), anyMatch(), allMatch(), noneMatch(), 
  count(), min(), max(), sum(), average(), collect()
  1. 출력 : forEach()
    ↳ 반환 타입 void이며, 보통 스트림의 출력하는 용도로 많이 사용한다.
  2. 소모 : reduce()
    ↳ 스트림의 최종연산은 보통 각 요소를 소모하며 연산을 수행하는데,
    reduce 메소드는 첫 번째와 두 번째 요소를 가지고 연산을 수행한 뒤,
    세 번째와 네 번째 요소를 가지고 연산을 수행한다.
Stream<String> stream1 = Stream.of("넷", "둘", "셋", "하나");
Optional<String> result = stream1.reduce(s1,s2 -> s1 + "+" + s2);
// 넷+둘+셋+하나 
  1. 검색 : findFirst(), findAny()
    ↳ 해당 스트림에서 첫 번째 요소를 참조하는 Optional객체를 반환한다.
    병렬 스트림인 경우에는 findAny() 메소드를 사용해야만 정확한 결과를 반환 받을 수 있다.
    Optional 클래스 보러가기
  2. 검사 : anyMatch(), allMatch(), noneMatch()
    ↳ 특정 조건을 만족하는 요소가 스트림에 있는지, 아니면 모두 만족하거나 모두 만족하지 않는지를 검사하는 메소드이다.
    4-1. anyMatch() : 일부 요소가 특정 조건을 만족할 경우 true
    4-2. allMatch() / noneMatch() : 모든 요소가 특정 조건을 만족할 경우 true / 만족하지 않을 경우 false
  3. 통계 : count(), min(), max()
    5-1. count() : 요소의 총 개수를 long타입의 값으로 반환
    5-2. max(), min() 가장 큰 값과 가장 작은 값을 가지는 요소를 참조하는 Optional 객체를 반환한다.
  4. 연산 : sum(), average()
    6-1. IntStream이나 DoubleStream과 같은 기본 타입 스트림에는 모든 요소의 합과 평균을 구할 수 있는 메소드가 정의되어 있다. 이 때, average() 메소드는 기본 타입으로 wrapping된 Optional 객체를 반환한다.
  5. 수집 : collect()
    7-1. 스트림을 배열이나 컬렉션으로 변환
    ↳ toArray(), toCollection(), toList(), toSet(), toMap()
    7-2. 요소의 통계와 연산 메소드와 같은 동작을 수행
    ↳ counting(), maxBy(), minBy(), summingInt(), averagingInt() 등
    7-3. 요소의 소모와 같은 동작을 수행
    ↳ reducing(), joining()
    7-4. 요소의 그룹화와 분할
    ↳ groupingBy(), partitioningBy()


  1. 참고사이트 : TCP School
  2. dev.nitro

개인적으로 공부하며 기록한 내용으로, 틀린 내용이 있는 경우 덧글을 달아주시면 감사하겠습니다. 😍

profile
Jr. Backend Engineer

0개의 댓글