들어가는 글

스트림(Stream)은 기본 사용법을 이용해 활용하는 방법은 무궁무진한게 스트림(Stream)이라고 생각이든다.
그리고 스트림(Stream)은 단순 반복문을 위한 기능이 아닌 데이터 가공에 의미가 있다고 생각이 든다.





스트림 사용법

스트림 생성

1.스트림 컬렉션

자바의 스트림을 사용하려면 우선 스트림 객체를 생성해야한다.

// 1. ex)
List<String> list = Arrays.asList("A","B","C");
Stream<String> stream = list.stream();

// 2. ex)
void test(List<Board> boardList) {
    Stream<String> stream = boardList.stream();
}



2.스트림 배열

배열의 경우 정적 메소드를 이용하면 된다.

String[] arr = {"a","b","c"};

Stream<String> stream1 = Arrays.stream(arr);
Stream<String> stream2 = Arrays.stream(arr, 1, 3); //인덱스 1포함, 3제외 substring(0,10) 과 비슷한 개념 



3.스트림 빌더

배열이나 컬렉션을 통해서 생성하는게 아닌 직접 값을 입력해서 스트림 객체를 생성하는 방법도 있다.

Stream<Object> stream = Stream.builder()
        .add("Apple1")
        .add("Apple2")
        .add("Apple3")
        .build();
        
// Map을 이용하면 : 
Stream<String> stream = Stream.builder()
        .add("Apple1")
        .add("Apple2")
        .add("Apple3")
        .build()
        .map(String::valueOf);



4.스트림 Generator

데이터를 생성하는 람다식을 이용해서 스트림을 생성할 수도 있습니다.

    // Supplier에 해당하는 람다식이 데이터를 생성하는 람다식이라고 합니다.
    public static<T> Stream<T> generate(Supplier<T> s) {....}

generate() 메소드의 인자로 "Hello"를 찍어주는 람다식을 주었습니다. 이렇게 되면 "Hello"를 무한으로 찍어내고 .limit(5) 메소드로 5개만 찍어내라는 제한을 걸어 무한생성을 막았습니다.

Stream<String> stream = Stream.generate(() -> "Hello").limit(5);



5.스트림 lterator

iterate() 메소드를 이용해서 수열 형태의 데이터를 생성할 수도 있습니다. 첫번째 매개변수 '100' 은 100을 초기 데이터로 잡고 n은 100 이며 n을 +10 을 .limit(5) 5번하겠다는 메소드입니다.

Stream<Integer> stream5 = Stream.iterate(100, n -> n + 10).limit(5);



6.스트림 - Empty스트림

빈 스트림을 만들고 싶다면 .empty(); 를 사용하면됩니다.

Stream<String> stream = Stream.empty();

//하지만 이것은 빈 스트림이지 null 아닙니다.
System.out.println(stream == null);
//false



7. 스트림 - 기본타입

자바에서 기본타입에 대해서 오토박싱과 언박싱이 발생한다. int 변수를 다룰 때,
Integer 클래스로 오토박싱해서 처리하는 경우가 있는데, 이경우 오버헤드가 발생해서 성능
저하가 있을 수 있다고 한다. 스트림 객체의 생성에서도 마찬가지인데 오토박싱을 하지 않으려면 다음과 같이 스트림을 사용하면된다.

        IntStream intStream = IntStream.range(1,10); // 1~ 9
        LongStream longStream = LongStream.range(1,10000000); // 1~ 99999999

제네릭을 이용한 클래스로 사용하려면 박싱을 해서 사용해야한다.

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



8. 스트림 - 문자열 스트림

특정 구분자를 이용해서 문자열을 스플릿 한 다음 각각을 스트림으로 뽑아낼 수도 있다.

Stream<String> stream = Pattern.compile(",").splitAsStream("Apple,Banana,Melon");



9. 스트림 - 스트림연결

두 개의 스트림을 연겨래서 하나의 새로운 스트림으로 만들어 낼 수도 있습니다. Stream.concat(); 메소드를 이용합니다.

        Stream<String> stream1 = Stream.of("1","2","3");
        Stream<String> stream2 = Stream.of("4","5","6");
        
        Stream<String> stream3 = Stream.concat(stream1,stream2);



스트림 데이터 가공

 

filter

filter() 메소드에는 boolean값을 리턴하는 람다식을 넘겨주게 됩니다. 그러면 뽑아져 나오는 데이터에 대해
람다식을 적용해서 true가 리턴되는 데이터만 선별합니다.

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

stream.filter(num -> num == 1).forEach(System.out::println);
// 1



Map

Map()은 스트림에서 뽑아져 나오는 데이터에 변경을 가해준다.
0 ~ 9를 생성하여 filter에서 0을 걸러내고 map에서 * 10 해주는 로직입니다.

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

        stream
                .filter(num -> num !=0)
                .map(filteringNum -> filteringNum * 10)
                .forEach(System.out::println);

//10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90



flatMap

flatMap() 메소드는 중첩된 스트림 구조를 단일 컬렉션에 대한 스트림으로 만들어주는 역활을 한다.
프로그래밍에서는 이런 작업을 플랫트닝(Flattening)이라고 한다.

        Stream<List<String>> stream = Stream.of(
                Arrays.asList("A", "B", "C"),
                Arrays.asList("D", "E", "F")
        );
        // [{"A", "B", "C"}, {"D", "E", "F"}]

        List<String> flatMap = stream
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
        // {"A", "B", "C", "D", "E", "F"}



sorted

메소드명 그대로 오름차순으로 순서를 정렬합니다.

        Stream<Integer> stream = Arrays.asList(10,1,5,4,20).stream();

        stream.sorted().forEach(System.out::println);
        // 1, 4, 5, 10, 20



Peek

peek() 메소드는 스트림 내 엘리먼트들을 대상으로 Map()매소드 처럼 연산을 수행한다.
하지만 새로운 스트림으 생성하지 않고 그냥 인자로 받은 람다를 적용하기만 한다.

 

peek()메소드는 그냥 한번 해본다는 의밀 ㅗ생성되는 데이터들에 변형을 가하지 않고 그냥 인자로 받은 람다식만 수행해준다.

Stream<Integer> stream = Arrays.asList(10,1,5,4,20).stream();

 Stream<Integer> stream2 = stream.sorted().peek(System.out::println);

peek() 메소드는 리턴값이 있지만 forEach()는 void 메소드이다. 

 



스트림 결과 생성

지금까지 본 데이터 수정 연산들은 또 데이터에 수정을 가한 결과 데이터들을 만들어내는 또 다른 스트림 객체를 리턴했다.

 중간작업들이며 이들만으로는 의미 있는 스트림을 만들 수 없다. 데이터를 가공하고, 필터링한 다음 그 값을 출력하거나

또 다른 컬렉션으로 모아두는 등의 마무리 작업이 필요하다.



통계 값

정수 값을 받는 스트림의 마무리는 총합을 구하거나 최대값, 최소값, 숫자의 개수, 평균값을의 계산이다.

  int sum = IntStream.range(0, 10).sum();
  Long count = IntStream.range(0, 10).count();
  OptionalInt max = IntStream.range(0, 10).max();
  OptionalInt min = IntStream.range(0, 10).min();
  OptionalDouble average = IntStream.range(0, 10).average();



Reduce

Stream의 데이터를 변환하지 않고, 더하거나 빼는 등의 연산을 수행하여 하나의 값으로 만든다면 reduce를 사용할 수 있습니다.

예를 들면, 아래 코드는 1 + 2 + ... + 10의 합을 출력해줍니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));

// sum: 55

초기 값이 있는 reduce

Stream<Integer> numbers3 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum3 = numbers3.reduce(10, (total, n) -> total + n);
System.out.println("sum3: " + sum3);

// sum3: 65



Collect

자바 스트림을 이용하는 가장 많은 패턴 중 하나는 컬렉션 엘리먼트 중 일부를 필터링하고,

값을 변형해서 또 다른 컬렉션으로 만드는 것이다.

.collect(Collectors.toList()) 를 이용해 Stream 반환값을 list로 변형한 예제입니다.

        Stream<Integer> stream = Arrays.asList(10,1,5,4,20).stream();
        
        List<Integer> list = Arrays.asList(10,1,5,4,20).stream().collect(Collectors.toList());



foreach

스트림에서 뽑아져 나오는 값에 대해서 어떤 작업을 하고 싶을 때 foreach()메소드를 사용합니다. 리턴값이 존재하지 않으며 중간연산보다는 최종연산이 목적의 취지에 맞습니다.

        Stream<Integer> stream = Arrays.asList(10,1,5,4,20).stream();
        
        stream.forEach(System.out::println);



마치는 글

스트림의 예제들을 하나씩 살펴보았지만 당연히 모든 코드들이 그럴듯이
스트림 또한 이것만으로 충분하지 않을 수 있다
최대한 구글링을 하여 찾아보고 연습해봐야 익숙해지고 활용이 가능해진다.
앞으로는 스트림과 for문을 언제사용하면 좋을지에 대해서 글을 쓸 예정이다.

복사했습니다!