Skills/Java

Java - Stream 사용법 및 예제

aoaa 2022. 11. 25. 17:12

 1. Stream

 Stream은 Java 8부터 추가된 Collections의 저장 요소를 참조하여 처리할 수 있는 반복자를 말합니다. API 설명을 직역하면 Colletion의 map-reduce(?) 변환과 같이 함수 스타일(람다식을 의미하겠죠?) 작업을 지원하는 클래스라고 적혀있습니다. 

 한 줄로 설명하면 Collection에 저장되어 있는 element들을 순회하면서 처리할 수 있는 코드패턴이라고 보면 되겠네요. 코드로 한번 살펴보겠습니다.

 


2. 사용법

2.1 Stream 객체 생성

List<String> list = Arrays.asList("a","b","c");
Stream <String> stream = list.stream();

 Stream을 사용하려면 Stream 객체를 생성해야합니다. Collection 객체들은 stream() 메서드를 지원하며, 위의 예시는 List를 호출하여 Stream 객체로 만든다는 의미가 됩니다.


2.2 Stream - Array

String[] array = new String[]{"a", "b", "c"};
Stream<String> stream1 = Arrays.stream(array); // a, b, c
Stream<String> stream2 = Arrays.stream(array, 1, 3); // b, c

 Arrays.stream()의 패러미터에 배열인 array를 넣게되면 배열을 순회하는 stream 객체를 만들 수 있습니다. 

패러미터로 배열, 시작, 종료 index를 준다면 배열 일부를 순회하는 stream 객체를 만들 수도 있습니다.


2.3 Stream - Builder

String<Strig> buidler = Stream<String>builder()
		    	    .add("A")
                            .add("B")
                            .add("C")
                            .build();

 builder를 통해 만든 stream 객체에 "A","B","C" 순서로 문자열의 데이터를 처리가 가능합니다.


2.4 Stream - Generator

Stream<String> gen = Stream.generate(() -> "this generator");
Stream<String> gen = Stream.generate(() -> "this generator").limit(3);

 첫 줄의 코드는 generate() 메서드를 통해 this generator를 무한하게 생성하는 stream을 만들어냅니다.

두 번째 줄의 코드에서 limit 메서드를 통해 반복 횟수를 정하여 3번만 반복하도록 제한을 할 수도 있습니다.


2.4 Stream - 기본 타입 생성

  JDK 1.5 이상에서는 Primitive(기본) Type을 객체 타입의 데이터에 할당하면 *오토박싱이 발생합니다.

*Boxing Primitive type byte, short, int, long, float 등.. 에서 Wrapper type Byte, Short, Integer, Long 로 바뀌는 것을 의미합니다. (Unboxing은 Wrapper type -> Primitive type) 

 

이러한 다른 타입간의 형변환은 오버헤드를 일으켜 애플리케이션의 성능에 영향을 줄 수 밖에 없는데, Stream에서는 이 오토박싱이 일어나지 않도록 성능을 개선한 API를 제공합니다.

IntStream intstream = IntStream.range(1,10);
LongStream longstream = LngStream.range(1,1000);

 

 Int, Long, DoubleStream의 range메서드는 Primitive type을 취급하도록 강제하여 오토박싱이 일어나지 않게됩니다. 

Stream<Integer> stream = IntStream.range(1,10).boxed();

LongStream stream = new Random().Long(5); // Long형 난수 5가지 생성

 기본형 타입이 아닌 제네릭을 이용한 클래스를 사용하기를 원한다면 .boxed를 사용할 수 있고, Random() 클래스를 사용해 Stream으로 뽑아낼 수도 있습니다.


2.5 Stream - 문자열

IntStream stream = "This is Stream".char(); 

Stream<String> st = Pattern.complie(",").splitAsStream("Apple","Banana","Cherry"); 
// [Apple, Banana, Cherry]

 .char는 문자열을 구성하고 있는 문자들의 ASCII 코드 값을 Stream 형태로 뽑아줍니다.

또한 splitAsStream으로 문자열의 split처럼 구분할 수 있습니다. 

Stream<String> st1 = Stream.of("Korea", "GANA");
Stream<String> st2 = Stream.of("Porutugal","Uruguay");

Stream<String> st3 = Stream.concat(st1, st2); // "Korea", "GANA", "Porutugal", "Uruguay"

concat() 메서드는 인자로 받은 Stream을 연결하여 새로운 Stream을 만들어냅니다.


3. 람다식을 활용한 Stream 데이터 처리

3.1 Filter

 Filter는 stream 객체에서 나오는 데이터 중 특정한 데이터를 선별하는 역할을 합니다.

boolean 값을 return하는 람다식을 넘겨 데이터에 대해 람다식을 적용하여 true를 반환하는 데이터를 선별하게 됩니다. 

Stream<Integer> stream = IntStream.range(1, 10).boxed();
stream.filter(v -> ((v % 2) == 0))
            .forEach(System.out::println);
// 2, 4, 6, 8

 첫 줄의 stream은 1부터 9까지의 Integer를 취급합니다. .filter() 메서드에 2로 나누어 떨어지는 조건을 삽입한 뒤, forEach를 이용해 stream에서 필터링한 값을 출력을 해본다면 짝수만을 뽑은 stream 객체를 return할 수 있습니다. 


3.2 Map

 Map은 stream의 데이터를 변경을 가해주는 메서드로, stream으로 생성된 데이터에 map메서드의 인자로 받은 람다식을 적용하여 데이터를 변경합니다.

 

Stream<Integer> stream = IntStream.range(1, 10).boxed();
stream.filter(v -> ((v % 2) == 0))
            .map(v -> v * 10)
            .forEach(System.out::println);
// 20, 40, 60, 80

위의 예제에서 한 가지 추가하였는데, 짝수를 선별한 뒤 그 짝수에 10을 곱하여 출력한 경우입니다. 


3.3 Sorted

Sorted는 stream 데이터를 정렬할 때 사용하며, 패러미터가 없다면 기본으로 오름차순으로 정렬합니다. 이 때 두 값을 비교하는 경우에는 comparator를 sorted()의 인자로 사용할 수 있습니다. 

 저번 프리코스 미션 중 lotto 미션에서 사용한 sorted입니다. 1부터 45까지의 6자리 난수를 정렬하기위해 사용했습니다. 


3.4 Peek

 peek은 stream 내의 element를 대상으로 map 연산을 수행합니다. 이 때, 새로운 stream을 생성하지 않고 인자로 받은 람다식을 적용하게 됩니다.

 

 어찌보면 foreach()와 동일한 것 같지만, peek() 같은 경우 중간 처리 메서드로 중간 연산의 수행 결과를 디버깅하는 수단이고, forEach()는 최종 처리 메서드라는 것입니다. 무엇을 의미하느냐 peek()는 최종 처리 메서드가 없으면 동작하지 않는 stream이라는 것입니다.

 

public static void main(String[] args){
        int[] intArr = {1, 2, 3, 4, 5};

        // 최종처리 메소드가 없으면 동작 X
        Arrays.stream(intArr)
            .filter(a -> a%2 == 0)
            .peek(System.out::println); // peek은 중간 처리 메소드(intermediate)

        // 최종처리 메소드(Terminal) sum()이 존재, 정상 작동
        Arrays.stream(intArr)
            .filter(a -> a%2 == 0)
            .peek(System.out::println)
            .sum();

        // forEach는 최종처리 메소드(Terminal)로 정상 동작
        Arrays.stream(intArr)
            .filter(a -> a%2 == 0)
            .forEach(System.out::println);
}

 

 

 

 

참조