Stream이란?

자바에서는 collection 객체들에 대해서 stream으로 사용하는 방법을 제공해 준다.

(이 stream은 자바 8 이상부터 사용 가능)

 

물론 System.in 과 System.out도 스트림이고, 우리가 계속 사용해왔던 거지만

자바 8에서 말하는 stream과는 다르다. 자바 8에서의 stream의 데이터의 스트림을 의미한다. 즉, Collections.stream()을 제공해 준다.

이 stream을 이용해서 collection의 데이터를 연속된 흐름으로써 취급할 수 있게 된다.

stream에서는 filter, map, forEach 같은 고차함수(함수를 인자로 받는 함수)가 제공 됨

 

public class Main {
    public static void main(String[] args) {
        Arrays.asList("A", "AB", "ABC", "ABCD", "ABCDE")
                .stream()
                .map(String::length)
                .filter(i->i%2 == 1)
                .forEach(System.out::println);
    }
}

👉result

1
3
5

이게 아까 우리가 만든 MyCollection과 동일한 결과를 반환해 준다.

MyCollection과 다른점은, MyCollection은 map을 돌때, map을 다 수행하고난 데이터 덩어리가 나와서 데이터 덩어리를 MyCollection으로 전달해서 한꺼번에 결과가 만들어 졌다. 그 결과로 또 filter를 적용해서 또 새로운 컬렉션 덩어리를 만들었고 그 컬렉션 덩어리를 가지고 forEach를 수행했다.

 

근데 stream은 iterator처럼 데이터를 한건한건 떨어뜨려 처리함. 그래서 데이터 한건에 대해서 map, filter, forEach를 처리하고 필요 없으면 처리하지않고, 필요하면 필요한 시점까지 뒤로 미뤘다가 처리하는 lazy validation 같은 처리가 구현되어 있어 실제로는 굉장히 효율적으로 동작한다.

 

또 어떤 경우에는 동작 환경에 따라 각 stream의 동작들이 멀티스레드로동작하거나, parallel하게 동작하면서 굉장히 좋은 performance를 보여주기도 함.

 

실제로 stream에는 map, filter, forEach를 만들었는데, 실제로 더 많은 기능들이 담겨 있음.

 

- Stream을 선언하는 여러가지 방법

public class Main {
    public static void main(String[] args) {
        // stream의 제네릭으로 만들어져 있음.
        // 이 stream은 list에 있는 기능이다. (List에 stream 메소드가 있음)
        Stream<Integer> s = Arrays.asList(1,2,3).stream(); 
         
        // 어레이에 대해서도 stream 처리가 가능하다. 
        // 그러나 어레이는 객체가 아니므로 stream이라는 메소드가 있지는 않다.
        // 하지만 util.Arrays 클래스에 stream 메소드가 있어서 
        // 거기다 어레이를 넣어서 스트림을 만들어  낼 수 있다.
        
        // stream이 제네릭으로 되어있으므로 기본형인 int는 스트림을 제네릭으로 만들 수 없다.
        // 따라서 기본형을 위한 stream이 아래와 같이 따로 만들어져 있음
        IntStream s2 = Arrays.stream(new int[]{1, 2, 3} );
        
        // 이렇게 boxed를 이용해서 int를 Integer로 만들 수 있다. (래퍼 클래스로 바꿀 수 있음)
        Stream<Integer> s3 = Arrays.stream(new int[]{1, 2, 3}).boxed(); 
        
        // 이걸 다시 toArray()나 toList()로 만들어 낼 수 있는데,
        // toList() 의 경우 collect라는 기능을 사용하여 List 타입을 쓸 수도 있다.
        List<Integer> list = Arrays.stream(new int[] {1,2,3}).boxed().collect(Collectors.toList());


        // toArray() 할때는 내가 원하는 결과 타입으로 생성할 수 있는 방법을 제공해 주면 됨.
        // 얘는 Object[] 임.
        Object[] obj = Arrays.stream(new int[]{1,2,3}).boxed().toArray(); 
        
        // 이렇게 해야 Integer[]가 됨.
        Integer[] integer = Arrays.stream(new int[]{1,2,3}).boxed().toArray(Integer[]::new); 
        
        // 추가적으로 IntStream을 Stream 제네릭으로 만드는 또다른 방법은 아래와 같다.
        Stream<Integer> s4 = Arrays.stream(new int[]{1, 2, 3}).mapToObj(i->Integer.valueOf(i));
        // 위 라인에서 Arrays.stream(new int[]{1, 2, 3}) 까지는 IntStream이므로 
        // 이를 Stream 제네릭을 바꾸기 위해 object로 바꿔주는 mapToObej 메소드를 사용하면 된다.
    }
}

- Stream을 만드는 방법

스트림은 new로 만들수 없다. 만드는 방법으로 builder를 이용하는데, 2가지 방법이 있다. (generate() 방법, iterate() 방법)

 

1. generate 방법

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

        Stream.generate(()->1) // generate인자로 supplier 값이 들어간다.
            .forEach(System.out::println);
    }
}

👉result

1
1
1
1
1
... (무한 루프)

1이 계속 만들어지고 있다. stream은 데이터의 연속이기 때문에 데이터가 하나씩 만들어지고 끝이 아니라 연속된 데이터가 없어질때까지 계속해서 소진이 되는데 지금 우리는 stream을 generate로 만들어 냈기 때문에 generate하면서 1이라는 값을 계속 발생시킴. 그래서 1이 계속 출력하고 있는거다.

 

아래와 같이 랜덤 값을 계속 출력하게 만들 수도 있다.

public class Main2 {
    public static void main(String[] args) {
        Random r = new Random();
        Stream.generate(r::nextInt) // 랜덤 값을 계속 출력
            .forEach(System.out::println);
    }
}

 

갯수를 제한하고 싶다면 .limit(제한 갯수) 를 쓰면 된다.

public class Main2 {
    public static void main(String[] args) {
        Random r = new Random();
        Stream.generate(r::nextInt) // 랜덤 값을 계속 출력
            .limit(10)
            .forEach(System.out::println);
    }
}

👉result

-2075837988
110343836
778336295
1755524086
-1570023637
-1287197610
-712273281
2135143745
-610736697
2052954176

2. iterate 방법

public class Main2 {
    public static void main(String[] args) {
        // 초기값(seed 값)이 들어와서 다음번에 어떻게 결과를 전달할지 
        // (어떤 값을 만들어 낼지)를 만드는 function이 들어간다.
        // 즉 시드값부터 데이터 스트림이 시작되서 함수에 맞게 스트림이 쌓이게 된다.
        Stream.iterate(0, (i) -> i+1) 
            .forEach(System.out::println);
    }
}

👉result

0
1
2
3
4
... (무한 루프)

 

이 또한 limit으로 제한할 수 있다.

public class Main2 {
    public static void main(String[] args) {
        Stream.iterate(0, (i) -> i+2)
                .limit(10)
                .forEach(System.out::println);
    }
}

👉result

0
2
4
6
8
10
12
14
16
18

- Stream 활용 예제 1

public class Main3 {
    public static void main(String[] args) {
        // 주사위를 100번 던져서 6이 나올 확률을 구하시오.
        Random r = new Random();
        // nextInt에 bound로 6을 넣으면 0~5 이므로 1을 더해서 1~6으로 범위를 만든다.
        var count = Stream.generate(()->r.nextInt(6)+1) 
                .limit(100)
                .filter(n -> n==6)
                .count(); // 근데 var 키워드는 뭐지..?

        System.out.println(count);
    }
}

👉result

20 (랜덤 값임)

즉 위를 한번 실행했을때 100번 던지면 6이 나오는 경우가 20번 이라는 의미다.

 

참고: 자바 10 이후에 자바에서도 자료형이 필요없는 var 형이 도입되었는데, 컴파일러가 프로그램 작성자의 의도를 추측해야 하므로 타입 추론이라고 함

- Stream 활용 예제 2

public class Main3 {
    public static void main(String[] args) {
        // 1~9 사이 값 중에서 겹치지 않게 3개를 출력하라.
        Random r = new Random();
        int[] arr = Stream.generate(()->r.nextInt(9)+1) // 1~9
                .distinct() // 중복 제거
                .limit(3) // 3개 제한
                // 그냥 map은 object에서 object로 바뀌고 mapToInt를 하면 primitive 타입으로 바뀌게 된다.
                // (mapToInt의 결과값으로 IntStream이 반환됨)
                .mapToInt(i->i) 
                .toArray(); // primitive 타입은 그냥 toArray()만 해줘도 반환이 int[] 가 된다.

        System.out.println(Arrays.toString(arr));
    }
}

👉result

[2, 5, 8]

임의의 중복제거된 3가지 원소 배열이 반환된다.


- Stream 활용 예제 3

1. 0~200 사이 값 중에서 랜덤 값 5개를 뽑아 작은 순서대로 표시하시오.

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

        Random r = new Random();
        int[] arr = Stream.generate(()->r.nextInt(201)) 
                .limit(5) 
                .sorted() // 순서대로 정렬
                .mapToInt(i->i)
                .toArray(); 

        System.out.println(Arrays.toString(arr));
    }
}

👉result

[6, 46, 93, 175, 190]

랜덤하게 5가지가 작은 순서대로 나온다.

 

2. 0~200 사이 값 중에서 랜덤 값 5개를 뽑아  순서대로 표시하시오.

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

        Random r = new Random();
        int[] arr = Stream.generate(()->r.nextInt(201))
                .limit(5) 
                .sorted(Comparator.reverseOrder()) // 큰 순서대로 정렬
                .mapToInt(i->i)
                .toArray(); 

        System.out.println(Arrays.toString(arr));
    }
}

👉result

[127, 67, 61, 59, 20]

역순으로 잘 나온다.

 

이렇게 스트림을 사용하면 연속된 데이터에 대해서 강력한 고차함수들을 사용하여 강력한 기능들을 간략하게 표현할 수 있다.

'Java > 프레임워크를 위한 Java' 카테고리의 다른 글

Dependency 설정  (0) 2021.08.08
Optional이란?  (0) 2021.08.06
Collection이란?  (0) 2021.08.06
함수형 인터페이스, 익명클래스, 람다 표현식이란?  (0) 2021.08.06
디폴트 메서드란?  (0) 2021.08.06

댓글

Designed by JB FACTORY