티스토리 뷰

반응형

오늘의 주제는 Stream이다. 실무에서 많이 사용하고있는 문법인 Stream은 가독성을 높여주고 지연 연산을 통해 성능을 최적화 할 수 있다. 우리는 그럼 이러한 많은 장점을 보유한 Stream을 다음 순서로 알아볼 것이다. 

 

  1. Stream에 존재하는 다양한 메서드
    1. 생성하기
    2. 가공하기
    3. 결과 만들기
  2. 동작 순서
  3. 성능 향상
  4. 지연 처리
  5. Null-safe Stream 생성
  6. 줄여 쓰기

자 그럼 시작해보자!

 

Stream에 존재하는 다양한 메서드

우린 차근 차근 Stream을 생성하고 가공하고 결과를 만드는 것을 차례대로 해볼 것이다. 그저 문법이기 때문에 그렇게 어렵지 않을 것이다.

 

그렇다면 같이 Stream을 생성하여 결과를 만들어보자 !!

 

1. 생성하기

먼저 배열을 통해 Stream을 생성해보자 !

public class StreamMethod {

    public static void main(String[] args) {
        // 기본 적인 생성
        Stream<String> stream = Stream.of("Spring", "JPA", "Kafka", "WebFlux", "Kotlin");

        // Array 배열을 복사하여 생성 및 부분 배열만 복사하여 생성
        String[] strArr = {"Spring", "JPA", "Kafka", "WebFlux", "Kotlin"};
        Stream<String> streamFull = Arrays.stream(strArr);
        Stream<String> streamPart = Arrays.stream(strArr, 1, 3);

        // List 배열 복사하여 생성 (Collection 도 가능하다.)
        ArrayList<String> list = new ArrayList<>(Arrays.asList("Spring", "JPA", "Kafka", "WebFlux", "Kotlin"));
        Stream<String> streamOfArrayList = list.stream();

        // 비어있는 Stream도 생성할 수 있다.
        Stream<String> emptyStream = Stream.empty();

        // builder를 사용해 직접 원하는 값을 넣는다.
        Stream<String> buildStream = Stream.<String>builder()
                .add("Spring")
                .add("KafKa")
                .add("WebFlux").build();
                
        // Stream을 연결해 새로운 스트림을 생성
        Stream<String> stream1 = Stream.of("A", "B", "C");
        Stream<String> stream2 = Stream.of("D", "E", "F");
        Stream<String> concat = Stream.concat(stream1, stream2);

        // 개발자가 직접 Stream의 크기를 제한할 수 있다.
        Stream<String> generate = Stream.generate(() -> "element").limit(10);

        // 기존 stream에 parallel()을 이용하여 병렬처리를 할 수 있다.
        Arrays.stream(strArr).parallel().forEach(System.out::println);
    }
}

위 코드들은 Stream 생성을 위한 메소드들을 일부 모아놓은 것이다. 

 

  • Stream.of()를 이용해 별도 배열을 선언하지 않고 Stream을 만들 수 있다.
  • Arrays.stream()을 이용하여 선언한 배열을 복사하여 만들 수 있는 메서드이다.
  • list.stream()을 사용하면 해당 list 배열과 동일한 Stream 배열을 만들어준다.
  • Stream.empty()메서드를 사용하여 빈 Stream을 생성할 수 있습니다.
  • Stream.builder() 메서드는 직접적으로 원하는 값을 넣을 수 있다. 빌더를 사용할 때에는 반드시 제네릭에 원하는 타입을 지정해야 한다. 그렇지 않으면 Object형태의 인스턴스를 생성하기 때문에 타입 안정성에 문제가 생길 수 있다.
  • concat()메서드를 이용해 두 Stream을 연결하여 새로운 Stream을 생성할 수 있다.
  • Stream.generate() 메서드는 특정 크기를 명시하여 크기를 제한하여 크기가 정해져 있는 무한한 Stream의 크기를 줄여준다.
  • parallel()메서드를 사용하여 병렬 Stream을 생성할 수 있는데, 기존 스트림을 이 함수를 통해 병렬처리를 할 수 있다. 

외에도 많은 메서드들이 존재한다. 궁금하다면 한 번 찾아보시길 추천한다. 자 이제 우린 Stream을 생성하였으니 가공해야 한다. 

 

2. 가공하기

public class StreamProcess {

    public static void main(String[] args) {
        // 특정 요소와 동일한 요소를 걸러낸다.
        List<String> list = Arrays.asList("A", "B", "C", "D");
        Stream<String> stream = list.stream().filter(s -> s.equals("A")); // [A]

        // 최대 size 까지 Stream을 리턴한다. generate()와 같이 사용
        Stream<String> limit = Stream.generate(() -> "ele").limit(2); // [ele, ele]

        // n개의 요소를 제외한 Stream 리턴
        List<Integer> integers = Arrays.asList(5, 7, 3, 1, 2, 6, 1, 9, 0);
        Stream<Integer> streamSort = integers.stream()
                .sorted(Comparator.reverseOrder())
                .skip(4); // [3, 2, 1, 1, 0]

        // 중복요소를 제거한 Stream을 리턴한다.
        List<String> duplicates = Arrays.asList("A", "B", "B", "B", "C", "C", "C", "A");
        Stream<String> StreamDistinct = duplicates.stream().distinct(); // [A, B, C]

        // 문자열을 소문자로 바꿔준다.
        Stream<String> StreamMap = list.stream().map(String::toLowerCase); // [a, b, c, d]

        // int형으로 바꿔준다.
        List<String> nums = Arrays.asList("1", "2", "3", "4");
        IntStream toIntStream = list.stream().mapToInt(Integer::parseInt); // [1, 2, 3, 4]

        // 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어준다.
        List<List<String>> doubleList = Arrays.asList(List.of("a"), List.of("b"));
        List<String> collect = doubleList.stream().flatMap(Collection::stream).collect(Collectors.toList());

        // 정렬을 해주는 메서드
        Stream<Integer> sorted = integers.stream().sorted(); // 오름차순
        Stream<Integer> reverseSorted = integers.stream().sorted(Comparator.reverseOrder()); // 내림차순

        // 수행결과에 영향을 미치진 않는다. 중간에 결과를 확인할 때 사용할 수 있다.
        Stream<Integer> peekStream = integers.stream()
                .peek(System.out::println)
                .sorted(Comparator.reverseOrder());
    }
}

 

  • filter는 특정 요소를 걸러내는 역할을 한다. 인자로
    Predicate<? super T> predicate​
     를 받는데 함수형 인터페이스로 boolean을 리턴하며 조건식을 표현한다.

  • limit은 최대 size까지 Stream을 리턴한다. long타입의 maxSize라는 변수를 인자로 받는다.
  • skip()메서드는 long 타입의 n을 인자로 받아 n개의 요소를 생략한 Stream을 리턴한다.
  • distinct는 중복을 제거하는 메서드이다.
  • map은 Stream내 요소들을 특정 값으로 변환한다. 이때 람다를 인자로 받는다.
  • mapTo자료형() 메서드는 해당 요소를 자료형에 해당하는 타입으로 바꿔준다.
  • flatMap은 인자를 mapper를 받아 Stream을 리턴한다. 새로운 Stream을 생성해서 리턴하는 람다를 넘겨야 하는데, flatMap은 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어준다.

  • sorted()는 특정 배열을 정렬한다. default로는 오름차순이다.
  • peek은 Stream내 특정 작업을 수행할 뿐 결과에 영향을 미치지 않는다. 중간에 결과를 확인할 때 사용할 수 있다.

자 이제 우리는 내부 데이터를 입맛에 맞게 가공을 해보았으니 결과를 반환해야 한다.

 

public class StreamResult {

    public static void main(String[] args) {
        // 결과를 만들어 내는 메서드 accumulator, identity, combiner를 인자로 받을 수 있다
        OptionalInt reduce1 = IntStream.range(1, 10)
                .reduce((a, b) -> a + b); // 45

        int reduce2 = IntStream.range(1, 10)
                .reduce(10, Integer::sum); // 55

        int reduce3 = Stream.of(1, 2, 3)
                .parallel().reduce(10, Integer::sum, (a, b) -> a + b); // 36

        // 필요한 요소를 수집하여 새로운 Collection으로 반환한다.
        List<String> list = Stream.of("a", "b", "c").filter(ele -> ele.contains("b"))
                .collect(Collectors.toList());

        // sum(), count() 같은 메서드를 사용하여 연산결과를 반환 시킨다.
        List<Integer> nums = Arrays.asList(1, 3, 5, 7, 9);
        int sum = nums.stream().mapToInt(i -> i).sum();

        nums = Arrays.asList(1, 3, 3, 3, 4, 5, 6);
        long count = nums.stream().filter(i -> i == 3).count();

        // Stream에서 찾고자 하는 객체가 존재하는 지 탐색하고 boolean 타입을 리턴한다.
        List<String> strList = Arrays.asList("A", "B", "C", "D");

        // 하나라도 조건에 부합해야함
        boolean anyMatch = strList.stream()
                .anyMatch(s -> s.contains("A")); // true

        // 모두가 조건에 일치 해야함
        boolean allMatch = strList.stream()
                .allMatch(s -> s.contains("A")); // false

        // 조건에 맞는 객체 없음
        boolean noneMatch = strList.stream()
                .noneMatch(s -> s.contains("A")); // false

        // 요소를 순회하며 실행(최종 작업이 된다.)
        nums.forEach(System.out::println);
        
        IntStream.range(1, 10).parallel().forEach(System.out::println);

        // 병렬 처리 시 순서를 보장할 때 사용
        IntStream.range(1, 10).parallel().forEachOrdered(System.out::println);
    }
}

 

  • reduce() 메서드는 3가지 파라미터를 갖고 결과를 만든다. 
    • accumulator : 각 요소를 처리하는 계산 로직
    • identity : 계산을 위한 초깃값으로 Stream이 비어서 계산할 내용이 없더라도 리턴한다.
    • combiner : 병렬 Stream을 처리할 때 나눠서 계산한 결과를 하나로 합치는 로직
  • collect()는 필요한 요소를 수집하여 Collection으로 반환한다. 
  • Stream에는 sum(), count(), average(), min(), max()등이 존재하는데 특정 연산을 진행 한 후 결과를 리턴한다.
  • match() 함수는 조건에 맞게 탐색을 하여 존재 여부를 boolean을 리턴한다.
  • forEach()는 요소를 순회하며 실행하는 최종작업이다, peek()은 중간 작업에 대해 처리하였지만, forEach는 최종 작업이라는 차이점이 존재한다.

자 Stream에 메서드를 공부해보며 우린 하나의 Collection 또는 특정 값을 만들어 냈다. 다음에는 이 Stream을 이용하는 이유와 Stream의 장점에 대해 알아보겠다.

 

Reference.

https://has3ong.github.io/programming/java-streamintro2/

 

Java 8 Stream API 살펴보기 -2- Stream 가공하기 / 결과 만들기

Java 8 Stream API 살펴보기 -2- Stream 가공하기 / 결과 만들기

has3ong.github.io

https://has3ong.github.io/programming/java-streamintro1/

 

Java 8 Stream API 살펴보기 -1- Stream 생성하기

Java 8 Stream API 살펴보기 -1- Stream 생성하기

has3ong.github.io

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함