[Java] 스트림(Stream API)

김도윤·2025년 1월 17일

추가 공부

목록 보기
7/8
post-thumbnail

🌥️2025-01-17


🎈스트림(Stream) API


  • 컬렉션(배열)의 저장 요소들을 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이다.

  • 내부 반복자를 사용해서 병렬 처리와 중간 처리, 최종 처리 작업을 수행할 수 있다.


✏️ 스트림의 생성

  • java.util.stream 패키지에 존재하고 BaseStream 인터페이스를 부모로 해서 자식 인터페이스들이 상속 관계를 이루고 있다.
public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable {

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

public interface IntStream extends BaseStream<Integer, IntStream> {
}

public interface LongStream extends BaseStream<Long, LongStream> {
}

public interface DoubleStream extends BaseStream<Double, DoubleStream> {
}

  • IntStream, LongStream의 range(), rangeColsed() 메소드를 이용해 숫자 범위로 스트림 생성
    // 숫자 범위로 스트림을 생성하는 방법
    public void method1() {
        int sum = 0;
        IntStream stream = null;

        // 첫 번째 매개값 ~ 두 번째 매개값 이전까지의 값을 요소로 가지는 스트림 객체 생성
//        stream = IntStream.range(1,10);

        // 첫 번째 매개값 ~ 두 번째 매개값까지의 값을 요소로 가지는 스트림 객체 생성
        stream = IntStream.rangeClosed(1,10);
      
        // sum = stream.sum();
        //sum = stream.peek(  value -> System.out.println(value)).sum();
        sum = stream.peek(System.out::println).sum();

        System.out.printf("sum = %d\n", sum);
        System.out.println();
    }
  • range(1,10) : 1~9 까지의 원소와 그 원소의 합인 45 출력

  • rangeCloser(1,10) : 1~10 까지의 원소와 그 원소의 합인 55 출력

  • Arrays.stream(배열) 메소드를 이용해서 배열로부터 스트림을 생성

      // 배열로부터 스트림을 생성하는 방법
        public void method2() {
            String[] names = {"홍길동", "이몽룡", "성춘향", "임꺽정", "성춘향"};
    
            // for 문을 사용하여 출력
            for(String name : names) {
                System.out.println(name + " ");
            }
            System.out.println();
    
            // 스트림을 사용하여 출력
            // Stream<String> stream = Arrays.stream(names);
            Stream<String> stream = Arrays.<String>stream(names);
    
            //stream.forEach(str -> System.out.print(str + " "));
            // 중복제거
            //stream.distinct().forEach(str -> System.out.print(str + " "));
            stream.parallel().forEach(str -> System.out.print(str + " "));
            System.out.println();
        }
    • for문 사용하여 출력하는 것과 동일한 결과
    • Stream을 사용하여 훨씬 더 간결하게 코드 작성 가능하다.

  • 컬렉션의 stream() 메소드를 이용해서 컬렉션으로부터 스트림을 생성
      public void method3() {
            List<String> names = Arrays.<String>asList("홍길동", "이몽룡", "성춘향", "임꺽정", "성춘향");
    
            // for문을 사용하여 출력
            for (int i = 0; i < names.size(); i++) {
               System.out.print(names.get(i) + " ");
            }
            System.out.println();
    
            // Stream을 사용하여 출력
            Stream<String> stream = names.stream();
    
            //stream.forEach( name-> System.out.print(name + " "));
          stream.forEach(System.out::println);
    
            System.out.println();
        }
    }

✏️ 중간 처리 메소드

  • 스트림은 데이터의 필터링, 정렬, 매핑 등의 처리를 할 수 있는 중간 처리 메소드를 제공한다.


  • 리턴 타입이 스트림이라면 중간 처리 메소드이다.


  1. 필터링
       //필터 메소드 테스트
        public void method2() {
            List<Student> students = Arrays.asList(
                    new Student("홍길동",24,"남자",80,50),
                    new Student("김철수",29,"남자",50,50),
                    new Student("김영희",27,"여자",90,90),
                    new Student("홍길동",29,"남자",80,50),
                    new Student("이몽룡",26,"남자",88,80)
            );
    
            // 성별이 여자인 학생만 출력
            students.stream()
                    .distinct().filter((s -> s.getGender().equals("여자"))).forEach(System.out::println);
    
      // 수학 영어 점수가 둘다 60점 이상인 학생만 출력
            students.stream()
                    .distinct()
                    .filter((s -> s.getMath() >=60 && s.getEnglish() >= 60 ))
                    .forEach(System.out::println);
            System.out.println();
      }
  2. 정렬
  • 스트림의 요소들이 최종 처리가 되기 전에 중간 단계에서 요소들을 정렬할 수 있다.

  • IntSteam, LongStream, DoubleStream은 요소를 오름차순으로 정렬한다.

  • Stream은 요소 객체가 Comparable 인터페이스를 구현해야 정렬할 수 있다.

    1) 요소가 기본 자료형
      // 요소가 기본 자료형일 때
        public void method1() {
            // 오름차순 정렬
            IntStream.of(3, 5, 1, 4, 2)
                    .sorted()
                    .forEach(value -> System.out.printf(value + " "));
            System.out.println();
    
            // 내림차순 정렬
            IntStream.of(3, 5, 1, 4, 2)
                    .boxed()
                    .sorted(Comparator.reverseOrder())
                    // .mapToInt(Integer::intValue)
                    .forEach(value -> System.out.printf(value + " "));
        }
    • Java의 스트림 API에는 기본형 스트림과 객체 스트림이 존재한다.

    • 내림차순으로 종렬할 때는 기본형 스트림(IntStream)에서 sorted(Comparator) 같은 메소드를 사용 불가능하다.

    • 따라서 기본형을 래퍼 클래스(Integer)로 변환해야 하는데, 이를 boxed()를 사용하여 구현한다.

2) 요소가 기본 객체형

  // 요소가 객체일 때
    public void method2() {
        List<Student> students = Arrays.asList(
                new Student("홍길동",24,"남자",80,50),
                new Student("김철수",29,"남자",50,50),
                new Student("김영희",27,"여자",90,90),
                new Student("홍길동",29,"남자",80,50),
                new Student("이몽룡",26,"남자",88,80)
        );

        // 오름차순 정렬
        students.stream()
                .distinct()
                .sorted((o1,o2) -> o1.getName().compareTo(o2.getName()))
                .forEach(System.out::println);
        System.out.println();

        // 내림차순 정렬
        students.stream()
                .distinct()
                //.sorted(COmparator.reverseOrder)
                //.sorted((o1,o2) -> o2.compareTo(o1))
                .sorted((o1,o2) -> o2.getName().compareTo(o1.getName()))
                .forEach(System.out::println);
        System.out.println();
    }
  • Comparable을 활용하여 원하는 객체를 정렬 가능하다.

3. 매핑
  • 중간 처리 기능으로 스트림의 요소를 다른 요소로 대체하는 역할을 한다.
    public void method2() {
            int[] iNumber = {1, 2, 3, 4, 5};
            double[] dNumber = {1.1, 2.2, 3.3, 4.4, 5.5};
    
            Arrays.stream(iNumber)
                    .asLongStream()
                    .forEach(System.out::println); // 최종 처리 메소드
    
            System.out.println();
    
        
            // 중간 처리 메소드는 최종 처리 메소드가 호출되어야 동작된다.
          
            double sum = Arrays.stream(dNumber)
                    // 내림차순 정렬을 사용하기 위해 객체 스트림로 변경
                    .boxed()
                    .sorted(Comparator.reverseOrder())
                    // 객체 스트림은 sum 같은 함수를 지원하지 않는다
                    // 따라서 기본형 스트림으로 다시 변경
                    //.mapToDouble(number ->number)
                    //.mapToDouble(number -> number.doubleValue())
                    .mapToDouble(Double::doubleValue)
                    .peek(System.out::println) // 중간 처리 메소드
                    .sum();
            System.out.println(sum);
            System.out.println();
        }

    ✏️ 최종 처리 메소드

  • 스트림은 데이터의 집계, 수집, 반복 처리 등의 처리를 할 수 있는 최종 처리 메소드를 제공

  • 리턴 타입이 기본 타입이거나 Optional 타입이라면 최종 처리 메소드이다.


+) 매핑

  • 최종 처리 단계에서 요소들이 특정 조건에 만족하는지 조사하는 역할을 한다.

  • allMatch(Predicate)
    모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하는지 조사한다.

  • anyMatch(Predicate)
    최소한 한 개의 요소가 매개값으로 주어진 Predicate의 조건을 만족하는지 조사한다.

  • noneMatch(Predicate)
    모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하지 않는지 조사한다.


    1) 기본 집계

  • 최종 처리 기능으로 요소들의 개수, 합계, 평균값, 최대값, 최소값 등과 같이 하나의 값으로 산출하는 역할을 한다.

    • count()
    • sum()
    • average()
    • max()
    • min()
    • findFirst()
      public void method1() {
            int[] numbers = {1, 2, 3, 4, 5, 6};
    
            long count = Arrays.stream(numbers)
                    .filter(number -> number % 2 == 0).count();
    
            System.out.printf("2의 배수의 개수 : %d\n", count);
            System.out.println();
    
            int sum = Arrays.stream(numbers)
                    .filter(number -> number % 2 == 0).sum();
    
            System.out.printf("2의 배수의 합 : %d\n", sum);
            System.out.println();
    
            OptionalDouble optionalDouble =
                    Arrays.stream(numbers)
                            .filter(number -> number % 2 == 0).average();
        }

    2) 커스텀 집계

  • 기본 집계 이외의 다양한 집계 결과물을 산출하는 역할

    • reduce()
      Arrays.stream(numbers)
                    .filter(number -> number % 2 == 0)
                    .reduce((left, right) -> left * right)
                    .ifPresent(value -> System.out.printf("2의 배수의 곱 : %d\n", value));
            System.out.println();
    
            // 조건에 맞는 요소가 없는경우 reduce() 첫 번째 파라미터인 identity값 반환
            int result = Arrays.stream(numbers)
                    .filter(number -> number % 2 == 0)
                    .reduce(1,(left, right) -> left * right);
            System.out.println(result);

    3) Optional 클래스

  • 스트림의 최종 결과 값을 저장하는 객체

  • 단순히 값만 저장하는 것이 아니라, 값의 존재 여부를 확인하고 값이 존재하지 않을 경우
    기본 값을 설정할 수 있는 객체이다.


    1) 수집


  • 최종 처리 기능으로 필터링 또는 매핑한 요소들을 새로운 컬렉션으로 담아서 리턴 받을 수 있다.

       public void method1() {
            List<Student> students = Arrays.asList(
                    new Student("홍길동", 24, "남자", 80, 45),
                    new Student("김철수", 20, "남자", 50, 50),
                    new Student("김영희", 20, "여자", 90, 90),
                    new Student("이몽룡", 26, "남자", 80, 85),
                    new Student("성춘향", 20, "여자", 100, 100)
            );
    
            //학생들의 이름만 List 컬렉션으로 추출
            List<String> names = students.stream()
                    // .map(student
                    .map(Student::getName)
                   .collect(Collectors.toList());
            System.out.println(names);
            System.out.println();
    
            // 남학생들만 List 컬렉션으로 추출
            List<Student> list = students.stream()
                    .filter(student -> student.getGender().equals("남자"))
                    .toList();
    
           list.forEach(System.out::println);
            System.out.println();
    
            // 여학생들만 Set 컬렉션으로 추출
            Set<Student> set = students.stream()
                    .filter(student -> student.getGender().equals("여자"))
                    .collect(Collectors.toSet());
            set.forEach(System.out::println);
            System.out.println();
    
            // Map 컬렉션으로 수정 (Key : 이름, value : Student 객체)
            // 두 번째 파라미터는 자기 자신의 객체를 가리켜 value값을 가져온다
           Map<String, Student> map = students.stream()
                    .collect(Collectors.toMap(s -> s.getName(), s -> s));
    
    //        map.entrySet().forEach(entry -> {System.out.println(entry.getKey() + " " + entry.getValue());});
            map.forEach((key,value) -> System.out.println(key + " " + value));
            System.out.println();
    
            // 참고사항 groupingBy
            Map<String,List<Student>> listMap = students.stream()
                    .collect(Collectors.groupingBy(Student::getGender));
    
            listMap.forEach((s,studentList) -> {
                System.out.println(s);
                studentList.forEach(System.out::println);
                System.out.println();
            } );
        }
    }
profile
나태해 지지 말고 꾸준히

0개의 댓글