[JAVA] 스트림 API (Stream API)

과거에는 Java에서 Stream은 데이터를 전달받거나 저장하기 위해서 많이 사용하였다. 하지만 함수형 프로그래밍 기반으로 프로그램을 작성하면서 데이터 처리에서도 활발하게 스트림 형태를 사용하기 시작했다.


스트림 API (Stream API)

개요

데이터를 스트림 기반으로 처리하는 경우 다음과 같은 장점이 있음

  1. 다양한 형태의 묶음형 데이터(Container)를 표준화된 방식으로 처리 가능.\
Stream randomStream = Stream.generate(Math::random); 
Stream strStream = Stream.of(new String[]{"w","o","w"});
  1. 묶음형 데이터(Container)들의 요소를 순회하며 처리 가능
// asis : for(String name: names){ System.out.println(name); } 
aList().stream().forEach(System.out:println);

Stream 특징

  1. Functional : 데이터 소스를 변경하지 않는다. (원본 데이터를 훼손하지 않는다.)
  2. Single-use : 스트림을 한번 쓰고나면 재활용할 수 없다. 새로운 스트림을 얻어서 사용해야 한다.
  3. Lazily Evaluated : 중간 연산만 되는 연산들은 지연된 연산이며, 최종 연산 전 까지 수행되지 않는다.

Stream API 활용

Java Stream 객체 타입

스트림으로 처리되는 데이터 타입에 따라서 다음과 같이 Stream 객체 타입이 나뉜다.

// 일반적인 객체를 처리하기 위한 스트림 
Stream<String> stringStraem Arrays.stream(new String[]{"abc","def","ghi"});

// 기본 자료형 int를 처리하기 위한 스트림  
IntStream intStream = Arrays.stream(new int[]{1,2,3,4});

// 기본 자료형 long을 처리하기 위한 스트림  
LongStream longStream = Arrays.stream(new long[]{1L,2L,3L,4L});

// 기본 자료형 Double을 처리하기 위한 스트림  
DoubleStream doubleStream = Arrays.stream(new double[]{1.0,2.0,3.0,4.0});

Stream 생성

일반적으로 Stream은 Collection 객체로부터 생성하거나 Array로 부터 생성하여 활용한다.

// Array로부터 stream 생성 
Arrays.stream(new int[]{1,2,3,4}).forEatch(System.out::println);

// List로 부터 stream 생성 
List<String> stringList = Arrays.asList("abc","def","ghi");
stringList.stream().map(String::toUpperCase).forEach(System.out::println);

// Set으로 부터 stream 생성 
Set<Integer> integerSet = new HashSet<>();
integerSet.addAll(Arrays.asList(1,2,3,4));
integerSet.stream().map(i -> i+2).filter(i -> i%2).forEach(System.out::println);

// int 범위값으로 stream 생성 
IntStream.range(-2, 3).forEach(System.out::println); // -2, -1, 0, 1, 2
IntStream.rangeClosed(-2, 3).forEach(System.out::println); // -2, -1, 0, 1, 2, 3

// long 범위값으로 stream 생성 
LongStream.range(-2, 3).forEach(System.out::println); // -2, -1, 0, 1, 2
LongStream.rangeClosed(-2, 3).forEach(System.out::println); // -2, -1, 0, 1, 2, 3

// Random 으로 int stream 생성 
Random ran = new Random(); 
ran.ints(5).map(Math::abs).map(i -> i%100).forEach(System.out::println); // 42, 30, 85, 4, 57

Stream 처리

스트림 API를 사용하여 데이터를 처리하는 경우 중간 연산과 최종 연산을 구분해서 사용해야 한다.

  1. 중간 연산 - 입력값을 전달받아 리턴을 함으로써 함수 체인닝(chaining) 중간에 사용할 수 있는 것을 의미한다.

    타입 메소드 종류
    변형 map, flatMap
    필터링 filter, district
    정렬 sorted
    자르기 limit, skip
    조회 peek
    // filter(), distinct() . 
    IntStream intStream = intStream.of(1,1,2,3,3,4,5,5,6);
    intStream.distinct().filter(i -> i%2 == 0).forEach(Stream.out::print); //246
    
     // sort()
     Stream<String> strStream = Stream.of("a","b","c");
     strStream.sorted();
     strStream.sorted(Comparator.reverseOrder()); 
    
     // map()
     Stream<String> strStream = Stream.of("a","b","c");
     strStream.map(s -> s.toUpperCase).forEach(System.out::print);  // ABC
    
     // skip 
     IntStream intStream = IntStream.range(1,10); 
     intStream.skip(3).limit(5).forEach(System.out::print); // 45678
    

    `

  2. 최종 연산 - 최종 연산은 Consume I/F와 유사하게 입력값을 받아서 리턴 값 없이 처리한다.

    타입 메소드 종류
    수집 collect
    순회 forEach
    집계연산 count, max, min, average, sum, reduce
    추출 findFirst, findAny
    검사 allMatch, anyMatch, noneMatch
    IntStream intStream = IntStream.range(1,10);
    intStream.reduce(0,(a,b) -> a+1); // count
    intStream.reduce(0,(a,b) -> a+b); // sum
    
    IntStream intStream = IntStream.range(1,10); 
    List<Integer> intList = intSTream.collect(Collectors.toList());