티스토리 뷰

Java

Java8 Stream API

J-Mandu 2022. 5. 4. 09:00

Stream이란?

다량의 데이터를 읽어온 다음, 사용자가 원하는 데이터로 가공하여 보여주는 객체입니다.

원본 데이터는 수정하지 않으며, Terminal Operation이 끝날 경우 소실됩니다.

 

 

Stream 구조

0 또는 다수의 Intermediate Operation(중개 연산)과 1개의 Terminal Operation(최종 연산)으로 구성되어 있습니다.

Intermediate Operation은 새로운 Stream을 반환하고 항상 Lazy(게을러)하여

Terminal Operation이 실행될 때까지 시작되지 않습니다.

출처 :http://www.tcpschool.com/java/java_stream_concept

Stream의 특징

  • 원본 데이터를 수정하지 않습니다.
  • 손쉽게 병렬 처리를 할 수 있습니다.
  • 함수형 인터페이스를 지원합니다.
  • Stream은 일회성입니다.
  • Stream 작업은 내부 반복문으로 인하여 코드가 간결합니다.

Stream 생성

 

1. Collection 인터페이스를 구현한 모든 객체에  Stream() 메소드

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();

 

2. 배열에 관한 Stream을 생성하기 위한 Arrays클래스의 stream() 메소드

String[] arr = new String[]{"가","나","다","라"};
Stream<String> stream = Arrays.stream(arr);		// 가, 나, 다, 라
Stream<String> stream2 = Arrays.stream(arr, 0, 2);	// 가, 나

 

3. 매개변수를 받아 Stream을 생성하기 위한 Stream클래스의 of() 메소드

Stream<Integer> stream = Stream.of(4, 2, 3, 1);

 

4. 지정된 범위의 연속된 정수로 Stream을 생성하기 위한 IntStream클래스의 range() 및 rangeClosed() 메소드

IntStream stream1 = IntStream.range(1, 4);		// 1, 2, 3
IntStream stream2 = IntStream.rangeClosed(1, 4);	// 1, 2, 3, 4

 

5. 난수로 이루어진 Stream을 생성하기 위한 Random클래스의 ints(), longs(), doubles() 메소드

IntStream stream = new Random().ints(4);		// Int형의 랜덤한 숫자 4개
LongStream longs = new Random().longs(4);		// Long형의 랜덤한 숫자 4개
DoubleStream doubles = new Random().doubles(4);		// Double형의 랜덤한 숫자 4개

 

6. 람다 표현식에 의한 반환되는 값을 요소로 무한 Stream을 생성하기 위한 Stream클래스의 iterate() 및 generate() 메소드

Stream<Integer> stream = Stream.iterate(1, n -> n + 1);	// 1, 2, 3, 4 ...
Stream<Integer> stream2 = Stream.generate(() -> 1);	// 1, 1, 1, 1 ...

 

7. 파일의 한 행을 요소로 하는 Stream을 생성하기 위한 lines() 메소드

String<String> stream = Files.lines(Path path);

 

8. 아무 요소도 가지지 않는 빈 Stream을 생성하기 위한 Stream클래스의 empty() 메소드

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

Stream의 Intermediate Operation(중개 연산)

 

1. Stream 필터  : filter(), distinct()

    Stream<T> filter(Predicate<? super T> predicate) 

    해당 Stream에서 주어진 조건에 맞는 데이터만으로 구성된 새로운 Stream을 반환합니다.

 

    Stream<T> distinct()

    해당 Stream에서 중복된 데이터를 제거 후 새로운 Stream을 반환합니다.

int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7, 1, 2, 3};
IntStream filter = Arrays.stream(arr).filter(number -> number > 5);	// 6, 7
IntStream distinct = Arrays.stream(arr).distinct();			// 1, 2, 3, 4, 5, 6, 7

 

2. Stream 변경 : map(), flatMap()

    <R> Stream<R> map(Functoin<? super T, ? extends R> mapper)

    해당 Stream의 데이터들을 Function 매개변수로 전달하여, 그 반환 값으로 이루어진 새로운 Stream을 반환합니다.

 

   <R> Stream<R> flatMap(Functoin<? super T, ? extends Stream<? extends R>> mapper)

   해당 Stream의 데이터들을 Function 매개변수로 전달하여, Stream 반환 값으로 이루어진 새로운 Stream을 반환합니다.

String[] arr = {"A-01", "A-010", "B-02", "B-020"};
// 4, 5, 4, 5
Stream<Integer> stream = Arrays.stream(arr).map(string -> string.length());
// A, 01, A , 010, B, 02, B , 020
Stream<String> stream2 = Arrays.stream(arr).flatMap(string -> Stream.of(string.split("-")));

 

3. Stream 제한 : limit(), skip()

    Stream<T> limit(long maxSize)

    해당 Stream에서 매개변수로 전달된 개수만큼의 데이터만으로 이루어진 새로운 Stream을 반환합니다. 

 

    Stream<T> skip(long n)

    해당 Stream에서 매개변수로 전달된 개수만큼을 제외한 나머지 데이터로 이루어진 새로운 Stream을 반환합니다.

Stream<Integer> stream = Stream.iterate(1, number -> number + 1);	// 1, 2, 3, 4, ...
Stream<Integer> stream2 = stream.skip(10);				// 11, 12, 13, 14, ...
Stream<Integer> stream3 = stream2.limit(3);				// 11, 12, 13

 

4. Stream 정렬 : sorted()

    Stream<T> sorted()

    해당 Stream의 데이터들을 오름차순으로 정렬합니다.

 

    Stream<T> sorted(Comparator<? super T> comparator)

    해당 Stream의 데이터들을 Comparator를 이용하여 정렬합니다.

Integer[] arr = {1, 5, 2, 35, 6, 9, 20, 12};
// 1, 2, 5, 6, 9, 12, 20, 35
Stream<Integer> sorted = Arrays.stream(arr).sorted();
// 1, 2, 5, 6, 9, 12, 20, 35
Stream<Integer> sorted1 = Arrays.stream(arr).sorted(Comparator.naturalOrder());

 

5. Stream 연산 결과 확인 : peek()

    Stream<T> peek(Consumer<? super T> action)

    해당 Stream으로부터 각 데이터들을 소모하여 명시된 동작을 수행하여 새로운 Stream을 반환합니다.

Integer[] arr = {1, 5, 2, 35, 6, 9, 20, 12};
Arrays.stream(arr).peek(number -> System.out.println("원본데이터 : " + number))
         .sorted()
         .peek(number -> System.out.println("sorted(): " + number))
         .limit(5)
         .peek(number -> System.out.println("limit(5) : " + number))
         .forEach(System.out::println);

Stream의 Terminal Operation(최종 연산)

 

1. Stream 출력 : forEach()

    void forEach(Consumer<? super T> action)

    해당 Stream의 각 데이터들을 소모하여 명시된 동작을 수행합니다.

Integer[] arr = {1, 5, 2, 35, 6, 9, 20, 12};
Arrays.stream(arr).sorted().forEach(System.out::println); // 1, 2, 5, 6, 9, 12, 20, 35

 

2. Stream 소모 : reduce()

    Optional<T> reduce(BinaryOperator<T> accumulator)

    처음 두 데이터를 가지고 연산을 수행한 뒤, 그 결과와 다음 요소를 가지고 또다시 연산을 수행합니다.

    이런 식으로 해당 Stream의 모든 데이터들을 소모하여 연산을 수행하고, 그 결과를 반환합니다.

 

    T reduce(T identity, BinaryOperator<T> accumulator)

    위와 같으며, 첫 번째 매개변수가 초기값을 가지고 시작하므로 Optional이 아닌 T타입을 반환합니다.

Integer[] arr = {1, 5, 2, 35, 6, 9, 20, 12};
Optional<Integer> reduce = Arrays.stream(arr).reduce((n, n2) -> n + n2); // 90
Integer reduce2 = Arrays.stream(arr).reduce(10, (n, n2) -> n + n2);	// 100

 

3. Stream 검색 : findFirst(), findAny()

    Optional<T> findFirst()

    해당 Stream에서 첫 번째 데이터를 참조하는 Optional을 반환합니다.


    Optional<T> findAny()

    해당 Stream에서 병렬 Stream일 경우 가장 먼저 찾은 데이터를 참조하는 Optional을 반환합니다.

    병렬 Stream이 아닐 경우는 findFirst()와 같습니다.

String[] arr = {"a", "a1", "b", "b1", "c", "c1", "b2", "c2"};
// b
Optional<String> findFirst = Arrays.stream(arr).filter(string -> string.startsWith("b")).findFirst();
// b
Optional<String> parallelFindFirst = Arrays.stream(arr).parallel().filter(string -> string.startsWith("b")).findFirst();
// b
Optional<String> findAny = Arrays.stream(arr).findAny();
// b, b1, b2 셋 중 하나
Optional<String> parallelFindAny = Arrays.stream(arr).parallel().filter(string -> string.startsWith("b")).findAny();

 

4. Stream 검사 : anyMatch(), allMatch(), noneMatch()

     boolean anyMatch(Predicate<? super T> predicate)

     해당 Stream의 일부 데이터가 특정 조건을 만족할 경우에 true를 반환합니다.

 

     boolean allMatch(Predicate<? super T> predicate)

     해당 Stream의 모든 데이터가 특정 조건을 만족할 경우에 true를 반환합니다.

 

     boolean noneMatch(Predicate<? super T> predicate)

     해당 Stream의 모든 데이터가 특정 조건을 만족하지 않을 경우에 true를 반환합니다.

String[] arr = {"a1", "b1", "c1"};
boolean anyMatch = Arrays.stream(arr).anyMatch(string -> string.startsWith("a"));   //true
boolean allMatch = Arrays.stream(arr).allMatch(string -> string.contains("1"));	    //true
boolean noneMatch = Arrays.stream(arr).noneMatch(string -> string.startsWith("d")); //true

 

5. Stream 통계 : count(), min(), max()

     long count()

     해당 Stream의 데이터의 개수를 반환합니다.

 

     Optional<T> min(Comparator<? super T> comparator)

     해당 Stream의 데이터 중에서 가장 큰 값을 가지고 있는 데이터를 참조하는 Optional을 반환합니다.      

 

     Optional<T> max(Comparator<? super T> comparator)

     해당 Stream의 데이터 중에서 가장 작은 값을 가지고 있는 데이터를 참조하는 Optional을 반환합니다.

Integer[] arr = {100, 50, 200, 300, 420};
// 5
long count = Arrays.stream(arr).count();
// 420
Optional<Integer> max = Arrays.stream(arr).max(Integer::compareTo);
// 50
Optional<Integer> min = Arrays.stream(arr).min(Integer::compareTo);

 

6. Stream 연산 : sum(), average()

    T sum()

    해당 Stream의 모든 데이터 합을 구하여 반환합니다.

 

    Optional<T> average()

    해당 Stream의 모든 데이터 평균값을 구하여 반환합니다.

// sum() 및 average() 메소드는 IntStream or DoubleStream과 같은 기본 타입 스트림에서 사용할 수 있습니다.
// 1070
int sum = IntStream.of(100, 50, 200, 300, 420).sum();
// 214.0
OptionalDouble average = IntStream.of(100, 50, 200, 300, 420).average();

 

7. Stream 수집 : collect()

    <R,A> R collect(Collector<? super T,A,R> collector)

    매개변수로 전달된 Collectors 객체에 구현된 방법대로 Stream 데이터를 수집하여 반환합니다.

Integer[] arr = {100, 50, 200, 300, 420, 300};
// [100, 50, 200, 300, 420, 300]
List<Integer> collect = Arrays.stream(arr).collect(Collectors.toList());
// [50, 100, 420, 200, 300]
Set<Integer> collect2 = Arrays.stream(arr).collect(Collectors.toSet());
// {Key100=100, Key420=420, Key200=200, Key300=300, Key50=50}
Map<String, Integer> collect3 = Arrays.stream(arr).collect(toMap((integer) -> "Key" + integer, (integer) -> integer, (oldVal, newVal) -> oldVal));
// 10050200300420300
String collect4 = Arrays.stream(arr).map(integer -> String.valueOf(integer)).collect(Collectors.joining());
// {50=[50], 420=[420], 100=[100], 200=[200], 300=[300, 300]}
Map<Integer, List<Integer>> collect5 = Stream.of(arr).collect(Collectors.groupingBy(Integer::intValue, toList()));
// [100, 50, 200, 300, 420, 300]
ArrayList<Integer> collect6 = Stream.of(arr).collect(Collectors.toCollection(ArrayList::new));

 

 

이로써 공부한 내용을 간략히 정리해보았습니다. 

감사합니다.


출처

https://www.inflearn.com/course/the-java-java8/dashboard

http://www.tcpschool.com/