[Java] Java8 Stream API (2)

한호성·2023년 3월 1일
0

Java Stream API

목록 보기
2/3

Introduction

실무 개발 중에 Java8 에서 도입 된 Stream 기능을 유용하게 사용하여, Java Collection frameworks의 자료구조를 편하게 사용하였습니다. Stream API를 더 유용하게 사용하고, 자세한 동작원리를 알기 위해, 공부한 글입니다.
[목차]

  • Java Stream API 나오게 된 배경
  • Java Stream API 개념 및 특징
  • Java Stream API 다양한 사용법
  • Java Stream API 사용시 고려해야 할점

Java Stream API 다양한 사용법

Stream api를 사용하기 위해선 3단계를 거치게 됩니다.

  1. 생성하기 : 스트림 인스턴스 생성

    • 배열 / 컬렉션 / 빈 스트림
    • 기본타입 형 / String / vkdlf tmxmfla
    • 병렬 스트림 / 스트림 연결하기
  2. 가공하기 : 원하는 결과를 만들어가는 중간 작업

    • Filtering
    • Mapping
    • Sorting
    • Iterating
  3. 결과만들기 : 최종적으로 결과를 만들어내는 작업

    • Reduction
    • Collecting
    • Matching
    • Iterating
    • Calculating

생성하기

배열 스트림

스트림을 이용하기 위해서는 먼저 스트림을 생성해야한다. 배열 or Collection을 통해서 생성할 수 있습니다.. 코드를 보겠습니다.

String[] arr = new String[] {"asd","qwe","zxc"};
Stream<String> stream = Arrays.stream(arr);
 //(index 1~2 요소)
Stream<String> streamOfArrayPart = Arrays.stream(arr,1,3) 

컬렉션 스트림

컬렉션 타입의 경우 인터페이스에 추가된 디폴트 메소드 stream을 이용해서 스트림을 만들 수 있습니다.

#cf) java8 버전 이후부터 인터페이스에 디폴트 메소드가 추가 되었습니다. 인터페이스의 default 메소드에 대해서 따로 정리해서 link 달도록 하겠습니다.

List<String> list = Arrays.asList("asd","qwe","zxc");
Stream<String> stream = list.stream();
Stream<String> paralelStream = list.parallelStream(); //병렬 처리 스트림

비어 있는 스트림

비어있는 스트림도 생성 가능하다. 비어있는 스트림이 왜 필요할까요? 요소가 없을 때, null 대신에 사용할 수 있습니다. 예시를 보겠습니다.

public Stream<String> of(List<String> list){
	return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}

Stream.builder()

빌더패턴의 빌더를 사용하면, 스트림에 원하는 값을 넣을 수 있습니다. 마지막에 build 메소드로 스트림을 리턴합니다.

Stream<String> builderStream = Stream.<String>builder()
								.add("asd")
								.add("qwe")
 								.add("zxc")
 								.build();

Stream.iterate()

iterate 메소드를 이용하면, 초기값과 해당 값을 다루는 람다를 이용해서 스트림에 들어갈 요소를 만듭니다. 사이즈 제한이 꼭 필요합니다.

Stream<Integer> iteratedStream = Stream.iterate(30, n -> n+2).limit(5); 

기본 타입형 스트림

primitive type의 스트림을 생성할 수도 있습니다.

아래의 코드는, Random함수를통해 임의의 숫자들을 primitive Type의 스트림을 만든것입니다.


        DoubleStream doubles = new Random().doubles(3);
        IntStream intStream = new Random().ints(3);
        LongStream longStream = new Random().longs(3);

병렬 스트림

스트림 생성 시 사용하는 stream 대신 parallelStream 메소드를 사용해서 병렬 스트림을 쉽게 생성할 수 있습니다.
( 내부적으로 쓰레드를 처리하기 위해 Fork/Join Framework를 사용한다고 합니다.)

Stream<Product> parallelStream = productList.parallelStream();

boolean isMany = parallelStream
							.map(product -> product.getAmout() * 10)
                            .anyMatch(amount -> amount>200);

Stream 연결하기

stream.concat 메소드를 이앵효서 두 개의 스트림을 연결해서 새로운 스트림을 만들어 낼 수 있습니다.


Stream<String> stream1 = Stream.of("Java", "Scala", "Groovy");
Stream<String> stream2 = Stream.of("Python", "Go", "Swift");
Stream<String> concat = Stream.concat(stream1, stream2);
// [Java, Scala, Groovy, Python, Go, Swift]

가공하기

전체 요소 중에서, 내가 원하는 것만 뽑아내는 단계입니다. 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어붙이는 것도 가능합니다.

List<String> names = Arrays.asList("Eric", "Elena", "Java");

filtering

필터는 말 그대로, 스트림 내 요소들을 하나씩 검사하면서, 필터링하는 과정입니다.
filter 함수는 함수형 인터페이스를 받고 boolean을 retrun 하게된다.

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

//예시
Stream<String> stream = names.stream().filter(name->name.contains("a"));

Mapping

맵(map)은 스트림 내 요소들을 하나씩 특정 값으로 변환해 줍니다. 이 때, 값을 변환하기 위한 람다를 임자로 받습니다.

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

//예시

Stream<String> stream = 
  names.stream()
  .map(String::toUpperCase);
// [ERIC, ELENA, JAVA]

#cf) Java에서 제공하는 함수형 인터페이스 4가지에 대해 정리해서 link 달아두도록 하겠습니다.

Sorting

정렬의 방법은 다른 정렬과 마찬가지로, Comparator를 이용합니다.

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

IntStream.of(14, 11, 20, 39, 23)
  .sorted()
  .boxed()
  .collect(Collectors.toList());
// [11, 14, 20, 23, 39]

결과 만들기

가공한 스트림을 가지고 내가 사용한 결과값을 만들어내는 단계입니다.

Calculating

스트림 API는 다양한 종료 작업 메소드를 제공합니다. 최소,최대.합 평균 ㄷㅇ 기본형 타입으로 결과를 만들어 낼 수 있습니다.


long count = IntStream.of(1, 3, 5, 7, 9).count();
long sum = LongStream.of(1, 3, 5, 7, 9).sum();

Reduction

스트림은 reduce 라는 메소드를 이용해서 결과를 만들어 낼 수 있습니다.
reduce 는 3가지 파라미터를 받을 수 있습니다.

  • accumulator : 각 요소를 처리하는 계산 로직, 각 요소가 올 때마다, 중간 결과를 생성하는로직
  • identity : 계산을 위한 초기 값으로 스트림이 비어서 계산할 내용이 없더라도 이 값을 리턴
  • combiner : 병렬 스트림에서 나눠 계산한 결과를 하나로 합치는 동작하는 로직

Collecting

다른 종료 작업중 하나로, Collector 타입의 인자를 받아서 처리한다. 자주 사용하는 작업은 Collectors 객체에서 제공하고 있습니다.

다양한 메소드들이 있는데 상황에 맞춰서 찾아보고 사용하면 될 거 같습니다.

  • Collectors.toList()
  • Collectors.joining()
  • Collectors.averageingInt()
  • Collectors.summingInt()
  • Collectors.summarizingInt()
  • Collectors.groupingBy()
  • Collectors.partitioningBy()
  • Collectors.collectingAndThen()

Matching

매칭은 조건식 람다 Predicate 를 받아서 해당 조건을 만족하는 요소가 있는지 체크한 결과를 리턴합니다. 다음과 같은 세 가지 메소드가 있습니다.

  • anyMatch : 하나라도 조건을 만족하는 요소가 있는지 확인
  • allMatch : 모두 조건을 만족하는지 확인
  • noneMatch : 모두 조건을 만족하지 않는지

상황에 맞춰 함수들을 찾아가며 사용하는 것이 좋겠다. 모든 메소드들을 확인하는것은 비효율적이기 때문..

다음에 이어서,Java8 Stream API (3) 주의해야할 점에 대해서 작성하도록 하겠습니다.

Reference

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#package.description
https://futurecreator.github.io/2018/08/26/java-8-streams/
https://futurecreator.github.io/2018/08/26/java-8-streams-advanced
https://steady-coding.tistory.com/309
https://mangkyu.tistory.com/112

profile
개발자 지망생입니다.

0개의 댓글

관련 채용 정보