[Java] 스트림

최우형·2023년 3월 12일
1

Java

목록 보기
16/24

📌스트림

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

  • 명령형 프로그래밍 방식
import java.util.List;

public class ImperativeProgramming {
    public static void main(String[] args){
        // List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
        List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
        int sum = 0;

        for(int number : numbers){
            if(number > 4 && (number % 2 == 0)){
                sum += number;
            }
        }

        System.out.println("명령형 프로그래밍을 사용한 합계 : " + sum);
    }
}

//출력값
명령형 프로그래밍을 사용한 합계 : 14

위 코드는 "어떻게"에 초점을 둔 명령형 프로그래밍이다.

  • 선언형 프로그래밍 방식
import java.util.List;

public class DeclarativePrograming {
    public static void main(String[] args){
        // List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
        List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);

        int sum =
                numbers.stream()
                        .filter(number -> number > 4 && (number % 2 == 0))
                        .mapToInt(number -> number)
                        .sum();

        System.out.println("선언형 프로그래밍을 사용한 합계 : " + sum);
    }
}

//출력값
선언형 프로그래밍을 사용한 합계 : 14

직관적이고 간결한 코드

스트림의 특징

  1. 스트림 처리 과정은 생성, 중간 연산, 최종 연산 세 단계의 파이프라인으로 구성될 수 있다.
  2. 스트림은 원본 데이터 소스를 변경하지 않는다(read-only).
  3. 스트림은 일회용이다(onetime-only).
  4. 스트림은 내부 반복자이다.

스트림의 생성

배열을 데이터 소스로 하는 스트림 생성은 Arrays클래스의 stream() 메서드 또는 Stream클래스의 of()메서드를 사용할 수 있다.

  • Arrays.stream()
public class StreamCreator {
   
       public static void main(String[] args) {
           // 문자열 배열 선언 및 할당
           String[] arr = new String[]{"김코딩", "이자바", "박해커"};
   
           // 문자열 스트림 생성
           Stream<String> stream = Arrays.stream(arr);
   
           // 출력
           stream.forEach(System.out::println);
   
       }
   }
   
   // 출력값
   김코딩
   이자바
   박해커
  • Stream.of()
import java.util.stream.Stream;

public class StreamCreator {

    public static void main(String[] args) {
        // 문자열 배열 선언 및 할당
        String[] arr = new String[]{"김코딩", "이자바", "박해커"};

        // 문자열 스트림 생성
        Stream<String> stream = Stream.of(arr);

        // 출력
        stream.forEach(System.out::println);

    }
}

// 출력값
김코딩
이자바
박해커
  • Arrays 클래스 종류

  • IntStream 의 유용한 기능들

import java.util.Arrays;
import java.util.stream.IntStream;

public class StreamCreator {

    public static void main(String[] args) {

        // int형 배열로 스트림 생성
        int[] intArr = {1,2,3,4,5,6,7};
        IntStream intStream = Arrays.stream(intArr);

        // 숫자와 관련된 경우 intStream을 사용하는 것을 권장
        System.out.println("sum=" + intStream.sum());
//        System.out.println("average=" + intStream.average());

    }
}

//출력값
sum=28

📌컬렉션 스트림 생성

Collection으로부터 확장된 하위클래스 ListSet을 구현한 컬렉션 클래스들은 모두 stream() 메서드를 사용하여 스트림을 생성할 수 있다.

  • 컬렉션 스트림 생성
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamCreator {

    public static void main(String[] args) {
				// 요소들을 리스트
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
        Stream<Integer> stream = list.stream();

        stream.forEach(System.out::print);
    }
}

//출력값
1234567

임의의 수 스트림 생성

import java.util.Random;
import java.util.stream.IntStream;

public class StreamCreator {

    public static void main(String[] args) {
        //         난수 생성
        IntStream ints = new Random().ints();
        ints.forEach(System.out::println);
    }
}

이렇게 하면 출력값이 무한대로 생성된다.

스트림의 크기가 정해지지 않은 것을 무한 스트림(infinite stream)이라 부른다.

import java.util.Random;
import java.util.stream.IntStream;

public class StreamCreator {

    public static void main(String[] args) {
			
			// 스트림 생성의 범위를 5개로 제한
        IntStream ints = new Random().ints(5);
				// IntStream ints = new Random().ints().limit(5); 
        ints.forEach(System.out::println);
    }
}

ints(5) 처럼 ()에 5를 넣으면 특정 범위를 반환하는 것이 가능하다.
limit(5) 도 가능

range(1, 10) : start 부터 end까지의 범위를 지정할 수 있다. 1~9까지만 나옴
rangeClosed(1, 10) : start 부터 end까지의 범위를 지정할 수 있다. 1~10까지 나옴


📌스트림의 중간 연산

가장 빈번하게 사용하는 것 위주로 설명

필터링( filter(), distinct() )

  • distinct() : Stream의 중복 데이터가 존재하는 경우, 중복을 제거하기 위해 사용한다.

  • filter() : Stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어낸다.
    () 안에 조건이 참이 되는 요소만 필터링한다. 여기서 조건은 람다식을 사용하여 정의할 수 있다.

//예제
import java.util.Arrays;
import java.util.List;

public class FilteringExample {
    public static void main(String[] args) throws Exception {

        List<String> names = Arrays.asList("김코딩", "이자바", "박해커", "김코딩", "박해커");

        names.stream()
                .distinct() //중복 제거
                .forEach(element -> System.out.println(element));
        System.out.println();

        names.stream()
                .filter(element -> element.startsWith("김")) // 김씨 성을 가진 요소만 필터링 
                .forEach(element -> System.out.println(element));
        System.out.println();

        names.stream()
                .distinct() //중복제거
                .filter(element -> element.startsWith("김")) // 김씨 성을 가진 요소만 필터링 
                .forEach(element -> System.out.println(element));
    }
}

// 출력값
김코딩
이자바
박해커

김코딩
김코딩

김코딩

매핑( map() )

원하는 필드만 추출하거나 특정 형태로 변환할 때 사용

  • map()
import java.util.Arrays;
import java.util.List;

public class IntermediateOperationExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("kimcoding", "javalee", "hackerna", "luckyguy");
        names.stream()
                .map(element -> element.toUpperCase()) // 요소들을 하나씩 대문자로 변환
                .forEach(element->System.out.println(element));
    }
}

// 출력값
KIMCODING
JAVALEE
HACKERNA
LUCKYGUY

(((((((2)))))))
import java.util.Arrays;
import java.util.List;

public class IntermediateOperationExample {
    public static void main(String[] args)
    {
        List<Integer> list = Arrays.asList(1, 3, 6, 9);

        // 각 요소에 3을 곱한 값을 반환
        list.stream().map(number -> number * 3).forEach(System.out::println);
    }

}

// 출력값
3
9
18
27
  • flatMap()
// 주어진 이중 배열
String[][] namesArray = new String[][]{{"박해커", "이자바"}, {"김코딩", "나박사"}};

// 기대하는 출력값
박해커
이자바
김코딩
나박사

// flatMap()
Arrays.stream(namesArray).flatMap(Arrays::stream).forEach(System.out::println);

// 출력값
박해커
이자바
김코딩
나박사

flatMap()을 사용하여 중첩 구조를 제거하고 단일 컬렉션(Stream<String>)으로 만들어주는 역할을 한다. 이를 요소들을 평평하게 한다는 의미에서 플래트닝(flattening)이라고 한다.


정렬( sorted() )

정렬할 때 사용한다.

  • 기본 정렬
import java.util.Arrays;
import java.util.List;

public class IntermediateOperationExample {
    public static void main(String[] args) {
				// 동물들의 이름을 모아둔 리스트 
        List<String> animals = Arrays.asList("Tiger", "Lion", "Monkey", "Duck", "Horse", "Cow");
				
				// 인자값 없는 sort() 호출
        animals.stream().sorted().forEach(System.out::println);
    }
}

// 출력값
Cow
Duck
Horse
Lion
Monkey
Tiger
  • 역순으로 정렬
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class IntermediateOperationExample {
    public static void main(String[] args) {

        List<String> animals = Arrays.asList("Tiger", "Lion", "Monkey", "Duck", "Horse", "Cow");

				// 인자값에 Comparator 인터페이스에 규정된 메서드 사용
        animals.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);

    }
}

// 출력값
Tiger
Monkey
Lion
Horse
Duck
Cow

기타

  • skip() - 일부 요소를 건너 뛴다.
    import java.util.stream.IntStream;
    
    public class IntermediateOperationExample {
        public static void main(String[] args) {
    
            // 1~10 범위의 정수로 구성된 스트림 생성
            IntStream intStream = IntStream.rangeClosed(1, 10);
    
            // 앞의 5개의 숫자를 건너뛰고 숫자 6부터 출력
            intStream.skip(5).forEach(System.out::println);
        }
    }
    
    // 출력값
    6
    7
    8
    9
    10
  • limit() - 스트림의 일부를 자른다.
import java.util.stream.IntStream;
    
    public class IntermediateOperationExample {
        public static void main(String[] args) {
    
            // 1~10 범위의 정수로 구성된 스트림 생성
            IntStream intStream = IntStream.rangeClosed(1, 10);
    
            // 앞에서부터 5개의 숫자만 출력
            intStream.limit(5).forEach(System.out::println);
        }
    }
    
    // 출력값
    1
    2
    3
    4
    5
  • peek() - forEach() 와 마찬가지로, 요소들을 순회하며 특정 작업을 수행한다.
import java.util.stream.IntStream;

public class IntermediateOperationExample {
    public static void main(String[] args) {

        // 요소들을 사용하여 IntStream 생성
        IntStream intStream3 = IntStream.of(1, 2, 2, 3, 3, 4, 5, 5, 7, 7, 7, 8);

        // 짝수만 필터링하여 합계 구하기
        int sum = intStream3.filter(element -> element % 2 == 0)
                .peek(System.out::println)
                .sum();

        System.out.println("합계 = " + sum);
    }
}

// 출력값
2
2
4
8
합계 = 16

📌최종 연산

기본 집계 ( sum(), count(), average(), max(), min() )

  • 기본 집계
import java.util.Arrays;

public class TerminalOperationExample {
    public static void main(String[] args) {
        // int형 배열 생성
        int[] intArray = {1,2,3,4,5};

        // 카운팅
        long count = Arrays.stream(intArray).count();
        System.out.println("intArr의 전체 요소 개수 " + count);

        // 합계
        long sum = Arrays.stream(intArray).sum();
        System.out.println("intArr의 전체 요소 합 " + sum);

        // 평균
        double average = Arrays.stream(intArray).average().getAsDouble();
        System.out.println("전체 요소의 평균값 " + average);

        // 최대값
        int max = Arrays.stream(intArray).max().getAsInt();
        System.out.println("최대값 " + max);

        // 최소값
        int min = Arrays.stream(intArray).min().getAsInt();
        System.out.println("최소값 " + min);

        // 배열의 첫 번째 요소 
        int first = Arrays.stream(intArray).findFirst().getAsInt();
        System.out.println("배열의 첫번째 요소 " + first);
    }
}

// 출력값
intArr의 전체 요소 개수 5
intArr의 전체 요소 합 15
전체 요소의 평균값 3.0
최대값 5
최소값 1
배열의 첫번째 요소 1

뒤에 getAsInt()getAsDouble()을 붙이는 이유는 Optional로 결과가 나오기 때문에 붙여줘야한다.

//예시
import java.util.Arrays;
import java.util.OptionalDouble;

public class TerminalOperationExample {
    public static void main(String[] args) {
        // int형 배열 생성
        int[] intArr = {1,2,3,4,5};

        // 평균값을 구해 Optional 객체로 반환
        OptionalDouble average = Arrays.stream(intArr).average();
        System.out.println(average);

        // 기본형으로 변환
        double result = average.getAsDouble();
        System.out.println("전체 요소의 평균값 " + result);

    }
}

//출력값
OptionalDouble[3.0]
전체 요소의 평균값 3.0

매칭 ( allMatch(), anyMatch(), noneMatch() )

  • allMatch() - 모든 요소들이 조건을 만족하는 지 여부를 판단한다.
  • noneMatch() - 모든 요소들이 조건을 만족하지 않는 지 여부를 판단한다.
  • anyMatch() - 하나라도 조건을 만족하는 요소가 있는 지 여부를 판단한다.
//예제
import java.util.Arrays;

public class TerminalOperationExample {
    public static void main(String[] args) throws Exception {
        // int형 배열 생성
        int[] intArray = {2,4,6};

        // allMatch()
        boolean result = Arrays.stream(intArray).allMatch(element-> element % 2 == 0);
        System.out.println("요소 모두 2의 배수인가요? " + result);

        // anyMatch()
        result = Arrays.stream(intArray).anyMatch(element-> element % 3 == 0);
        System.out.println("요소 중 하나라도 3의 배수가 있나요? " + result);

        // noneMatch()
        result = Arrays.stream(intArray).noneMatch(element -> element % 3 == 0);
        System.out.println("요소 중 3의 배수가 하나도 없나요? " + result);
    }

}

// 출력값
요소 모두 2의 배수인가요? true
요소 중 하나라도 3의 배수가 있나요? true
요소 중 3의 배수가 하나도 없나요? false

요소 소모( reduce() )

스트림의 요소를 줄여나가면서 연산을 수행하고 최종적인 결과를 반환한다.

  • reduce()
import java.util.Arrays;

public class TerminalOperationExample {
    public static void main(String[] args) throws Exception {
        int[] intArray = {1,2,3,4,5};

        // sum()
        long sum = Arrays.stream(intArray).sum();
        System.out.println("intArray 전체 요소 합: " + sum);

        // 초기값이 없는 reduce()
        int sum1 = Arrays.stream(intArray)
                .map(element -> element * 2)
	                .reduce((a , b) -> a + b)
                .getAsInt();
        System.out.println("초기값이 없는 reduce(): " + sum1);

        // 초기값이 있는 reduce()
        int sum2= Arrays.stream(intArray)
                .map(element -> element * 2)
                .reduce(5, (a ,b) -> a + b);
        System.out.println("초기값이 있는 reduce(): " + sum2);
    }
}

// 출력값
intArray 전체 요소 합: 15
초기값이 없는 reduce(): 30
초기값이 있는 reduce(): 35

요소 수집( collect() )

요소들을 수집하는 최종 처리 메서드이다.


collect는 요소 그룹핑 및 분할 등 다른 기능들을 제공한다.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TerminalOperationExample {

    public static void main(String[] args) {
        // Student 객체로 구성된 배열 리스트 생성 
        List<Student> totalList = Arrays.asList(
                new Student("김코딩", 100, Student.Gender.Male),
                new Student("박해커", 80, Student.Gender.Male),
                new Student("이자바", 90, Student.Gender.Female),
                new Student("나미녀", 60, Student.Gender.Female)
        );
        
        // 스트림 연산 결과를 Map으로 반환
        Map<String, Integer> maleMap = totalList.stream()
                .filter(s -> s.getGender() == Student.Gender.Male)
                .collect(Collectors.toMap(
                        student -> student.getName(), // Key
                        student -> student.getScore() // Value
                ));

        // 출력
        System.out.println(maleMap);
    }
}

class Student {
    public enum Gender {Male, Female};
    private String name;
    private int score;
    private Gender gender;

    public Student(String name, int score, Gender gender) {
        this.name = name;
        this.score = score;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }

    public Gender getGender() {
        return gender;
    }
}

// 출력값
{김코딩=100, 박해커=80}
profile
프로젝트, 오류, CS 공부, 코테 등을 꾸준히 기록하는 저만의 기술 블로그입니다!

0개의 댓글