[Java] Stream 기본 활용

3Beom's 개발 블로그·2022년 11월 8일

본 글은 우아한 테크코스 프리코스 1주차 미션 중 공부한 내용을 기록한 것이다.
-> 우아한 테크코스 프리코스 1주차 미션 java-onboarding
-> 필자가 제출한 코드
-> 1주차 미션 회고


Stream

  • 기존에 알아보고 싶었던 stream에 대해 알아보았고, 미션에서 활용해 보았다.
  • Stream은 다음과 같은 과정으로 수행된다.
    • ??.Stream생성.가공1.가공2•••.결과생성
  • 따라서 다음 3가지 과정에 대해 알아볼 필요가 있다.
    -> Stream 생성 과정, 가공 과정, 결과 생성 과정

<Stream 생성>

  1. 배열 스트림

    • 배열로 Stream을 생성할 수 있으며, 방법은 다음과 같다.
    Stream<String> stringStream = Arrays.stream(new String[]{"a", "b"});
  2. 컬렉션 스트림

    • Collection, List, Set 등으로 Stream을 생성할 수 있으며, 방법은 다음과 같다.
    List<String> list = new ArrayList<>(List.of("a", "b"));
    Stream<String> stream = list.stream();
  3. Stream.builder()

    • 다음과 같이 스트림에 원하는 값을 직접 넣어줄 수도 있다.
    Stream<String> builderStream = Stream.<String>builder()
                                         .add("a").add("b")
                                         .build();
  4. Stream.generate()

    • 매개변수는 없고 리턴값만 있는 형태로 값을 채울 수 있다.
    Stream<String> generateStream = Stream.generate(() -> "a").limit(4);
    // "a" 가 4개 들어감
    • 다음과 같이 같은 값이 반복되는 리스트를 생성할 때 요긴하게 쓸 수도 있을 것 같다.
    int numberOfFalse = 10;
    List<Boolean> booleanList = new ArrayList<>();
    booleanList = Stream.generate(() -> false).limit(numberOfFalse)
      					.collect(Collectors.toList());
  5. Stream.iterate()

    • "초기 값"과 "해당 값을 가리키는 변수"를 매개변수로 전달하여 리턴 값을 만들어 낼 수 있는 형태로 값을 채울 수 있다.
    Stream<Integer> iterateStream = Stream.iterate(1, n -> n + 1).limit(4);
    // 1, n : 1은 초기 값, 이후 n이 리턴 형태에 맞춰 반환된다.
    // 1, 2, 3, 4 
    • 다음과 같이 일정하게 증가하는 값을 갖도록 리스트를 생성할 때 쓰일 수 있을 것 같다.
    int numberOfIds = 10;
    List<Integer> idList = new ArrayList<>();
    idList = Stream.iterate(1, n -> n + 1).limit(numberOfIds).collect(Collectors.toList());
  6. 기본 타입형 스트림 (IntStream, LongStream, DoubleStream)

    • primitive 타입의 스트림을 생성할 수도 있다. 여기서 .range()가 유용하다.
    IntStream intStream = IntStream.range(1, 5);
    LongStream longStream = LongStream.range(1, 5);
    //1, 2, 3, 4
    • 기본 타입형 스트림은 그냥 Stream과는 다른 타입이다.
    • 기본 타입형 스트림으로 값을 생성하고, Stream으로 만들어주려면 끝에 .boxed()를 붙여주면 된다.
    IntStream intStream = IntStream.range(1, 5);
    
    Stream<Integer> integerStream = intStream.boxed();
    • 특정 범위의 값을 갖는 리스트를 만들 때 유용하게 쓰일 수 있을 것 같다.
    int numberOfIds = 10;
    List<Integer> idList = new ArrayList<>();
    idList = IntStream.range(1, numberOfIds+1).boxed().collect(Collector.toList());
  7. 스트림 연결하기

    • Stream.concat()을 활용해 두 개의 스트림을 연결하여 새로운 스트림을 만들어 낼 수 있다.
    Stream<Integer> stream1 = Stream.of(1, 2, 3);
    Stream<Integer> stream2 = Stream.of(4, 5, 6);
    Stream<Integer> concatStream = Stream.concat(stream1, stream2);
    //1, 2, 3, 4, 5, 6

<Stream 가공>

  • Stream의 전체 요소들 중에서 원하는 값만 뽑아내는 등의 가공 단계를 수행할 수 있다.
  • 가공 단계는 "중간 작업" 이라고도 하며, 해당 작업들은 Stream을 반환하기 때문에 여러 작업을 이어붙여서(chaining) 활용할 수 있다.
  1. Filtering (.filter())

    • Stream의 요소들을 하나씩 판별해서 걸러내는 작업이다.
    • 매개변수에 값이 하나씩 들어가 판별 기준에 적용되고, true에 해당하는 값들만 반환된다.
    List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 11, 12, 13));
    Stream<Integer> integerStream = numbers.stream().filter(number -> number > 10);
    // 11, 12, 13
    List<String> words = new ArrayList<>(List.of("aaa", "abc", "dfe"));
    Stream<String> filtered = words.stream().filter(word -> word.contains("a"));
    // "aaa", "abc"
  2. Mapping (.map())

    • Stream의 요소들을 하나씩 특정 값으로 변환해 준다.
    • 매개변수로 요소들이 하나씩 넘어가며, 특정 작업을 걸어 변화된 값으로 반환시킬 수 있다.
    • 이 때 값을 변환하기 위한 람다를 인자로 받는다. (혹은 그냥 코드도 가능하다)
      • 그냥 코드 : 클래스.메소드(), 객체.메소드()
        • String.toUpperCase()
      • 람다 : 클래스::메소드, 객체::메소드
        • String::toUpperCase
    • 다음은 대문자로 변환하는 예시이다.
    List<String> strings = new ArrayList<>(List.of("a", "b", "c"));
    // 아래 두 방법 동일한 결과를 갖는다.
    Stream<String> upper = strings.stream().map(s -> s.toUpperCase());
    Stream<String> upper = strings.stream().map(String::toUpperCase);
    //A, B, C
    • 미션 Problem 7에서 Mapping을 사용하였다. (람다 O)
    List<String> sortedRecommendList = sortedRecommendScore.stream()
      .map(Map.Entry::getKey).collect(Collectors.toList());
    • 이는 아래와 같이 쓰일 수도 있다. (람다 X)
    List<String> sortedRecommendList = sortedRecommendScore.stream()
      .map(score -> score.getKey()).collect(Collectors.toList());
  3. Sorting (.sorted())

    • 매개변수 없이 쓸 경우, 오름차순으로 정렬된다.
    List<Integer> numbers = new ArrayList<>(List.of(3, 1, 2, 4));
    Stream<Integer> sorted = numbers.stream().sorted();
    //1, 2, 3, 4
    • 매개변수를 통해 Comparator를 넣어줄 수 있다.
    List<Integer> numbers = new ArrayList<>(List.of(3, 1, 2, 4));
    Stream<Integer> sorted = numbers.stream().sorted(Comparator.reverseOrder());
    //4, 3, 2, 1
    List<String> strings = new ArrayList<>(List.of("aa", "aaa", "a", "aaaa"));
    // 아래는 모두 같은 방법들이다.
    // 방법 1
    Stream<String> sorted = strings.stream().sorted(Comparator.comparingInt(String::length));
    // 방법 2
    Comparator<String> comparator = new Comparator<String>() {
      @Override
      public int compare(String o1, String o2) {
        if (o1.length() > o2.length()) {
          return 1;
        } else if (o1.length() < o2.length()) {
          return -1;
        } else {
          return 0;
        }
      }
    };
    Stream<String> sorted = strings.stream().sorted(comparator);
    // 방법 3
    Stream<String> sorted = strings.stream().sorted((s1, s2) -> s1.length() - s2.length());
    // "a", "aa", "aaa", "aaaa"
  4. Iterating (.peek())

    • Stream 내 요소들 각각을 대상으로 특정 작업을 수행한다.
    • 작업은 수행되지만, 결과는 반환되지 않는다. (void)
    • 즉, 특정 작업만 수행할 뿐, 결과에 영향을 미치진 않는다.
    List<String> strings1 = new ArrayList<>(List.of("aa", "aaa", "a", "aaaa"));
    // 아래 모두 같은 방법들
    // 방법 1
    List<String> sorted11 = strings.stream()
        .sorted(Comparator.comparingInt(String::length))
        .peek(System.out::println)
        .collect(Collectors.toList());
    //방법 2
    List<String> sorted11 = strings.stream()
        .sorted(Comparator.comparingInt(String::length))
        .peek(s -> System.out.println(s))
        .collect(Collectors.toList());

<결과 만들기>

  • 가공한 Stream을 결과값으로 만들어 내는 단계이다.
  • Stream을 끝내는 '최종 작업' 단계이다.

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

  • 최소, 최대, 개수, 합, 평균을 구할 수 있다.
long count = IntStream.of(1, 2, 3).min();
long count = IntStream.of(1, 2, 3).max();
long count = IntStream.of(1, 2, 3).count();
long count = IntStream.of(1, 2, 3).sum();
long count = IntStream.of(1, 2, 3).average();

(Collecting(collect))

  • Collector 타입의 매개변수를 받아 처리한다. 주로 Collectors.~~ 로 처리할 수 있다.
  1. Collectors.toList()

    • Stream 가공 결과를 리스트에 담아서 반환해 준다.
    List<Integer> intList = new ArrayList<>(List.of(1, 2, 11, 12));
    List<Integer> overTen = intList.stream().filter(n -> n > 10).collect(Collectors.toList());
    • 미션의 Problem 7에서 쓰였다.
    List<String> sortedRecommendList = sortedRecommendScore.stream()
      .map(Map.Entry::getKey).collect(Collectors.toList());
  2. Collectors.joining()

    • Stream 가공 결과를 하나의 스트링으로 이어서 붙일 수 있다.
    List<Integer> intList = new ArrayList<>(List.of(1, 2, 11, 12));
    String overTenString = intList.stream()
                                  .filter(n -> n > 10)
                                  .map(n -> n.toString()
                                  .collect(Collectors.joining());
    // 1112
    • 매개변수로 스트링 내용을 추가해 줄 수도 있다.
      • joining(delimeter, prefix, suffix)
        • delimeter : 각 요소 중간 중간에 들어가는 구분자
        • prefix : 맨 앞에 붙는 문자
        • suffix : 맨 뒤에 붙는 문자
    List<Integer> intList = new ArrayList<>(List.of(1, 2, 11, 12));
    String overTenString = intList.stream()
                                  .filter(n -> n > 10)
                                  .map(n -> n.toString()
                                  .collect(Collectors.joining(", ", "[", "]"));
    // [11, 12]

(Matching(anyMatch, allMatch, noneMatch))

  • 조건을 설정하여 만족하는 요소가 있는지 여부를 boolean 타입으로 반환한다.
  1. anyMatch

    • 조건에 부합하는게 하나라도 있으면 true
    List<String> strings = new ArrayList<>(List.of("abcd", "abcd", "abc"));
    boolean anyMatch = strings.stream().anyMatch(s -> s.length() == 3);
    // true
  2. allMatch

    • 조건에 모두 부합해야 true
    List<String> strings = new ArrayList<>(List.of("abcd", "abcd", "abc"));
    boolean allMatch = strings.stream().allMatch(s -> s.contains("abc"));
    // true
  3. noneMatch

    • 조건에 부합하는게 없어야 true
    List<String> strings = new ArrayList<>(List.of("abcd", "abcd", "abc"));
    boolean noneMatch = strings.stream().noneMatch(s -> s.contains("e"));
    // true

(Iterating(forEach))

  1. forEach

    • peek의 최종 작업 단계이다.

    • peek는 중간에 가공 단계로 삽입될 수 있고, forEach는 최종으로 삽입될 수 있다.

    List<String> strings = new ArrayList<>(List.of("abcd", "abcd", "abc"));
    strings.stream().forEach(System.out::println);

내용 출처

profile
경험과 기록으로 성장하기

0개의 댓글