실무 개발 중에 Java8 에서 도입 된 Stream 기능을 유용하게 사용하여, Java Collection frameworks의 자료구조를 편하게 사용하였습니다. Stream API를 더 유용하게 사용하고, 자세한 동작원리를 알기 위해, 공부한 글입니다.
[목차]
- Java Stream API 나오게 된 배경
- Java Stream API 개념 및 특징
- Java Stream API 다양한 사용법
- Java Stream API 사용시 고려해야 할점
Stream api를 사용하기 위해선 3단계를 거치게 됩니다.
생성하기 : 스트림 인스턴스 생성
가공하기 : 원하는 결과를 만들어가는 중간 작업
결과만들기 : 최종적으로 결과를 만들어내는 작업
스트림을 이용하기 위해서는 먼저 스트림을 생성해야한다. 배열 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();
}
빌더패턴의 빌더를 사용하면, 스트림에 원하는 값을 넣을 수 있습니다. 마지막에 build 메소드로 스트림을 리턴합니다.
Stream<String> builderStream = Stream.<String>builder()
.add("asd")
.add("qwe")
.add("zxc")
.build();
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.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");
필터는 말 그대로, 스트림 내 요소들을 하나씩 검사하면서, 필터링하는 과정입니다.
filter 함수는 함수형 인터페이스를 받고 boolean을 retrun 하게된다.
Stream<T> filter (Predicate<? super ?> predicate)
//예시
Stream<String> stream = names.stream().filter(name->name.contains("a"));
맵(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 달아두도록 하겠습니다.
정렬의 방법은 다른 정렬과 마찬가지로, 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]
가공한 스트림을 가지고 내가 사용한 결과값을 만들어내는 단계입니다.
스트림 API는 다양한 종료 작업 메소드를 제공합니다. 최소,최대.합 평균 ㄷㅇ 기본형 타입으로 결과를 만들어 낼 수 있습니다.
long count = IntStream.of(1, 3, 5, 7, 9).count();
long sum = LongStream.of(1, 3, 5, 7, 9).sum();
스트림은 reduce 라는 메소드를 이용해서 결과를 만들어 낼 수 있습니다.
reduce 는 3가지 파라미터를 받을 수 있습니다.
다른 종료 작업중 하나로, Collector 타입의 인자를 받아서 처리한다. 자주 사용하는 작업은 Collectors 객체에서 제공하고 있습니다.
다양한 메소드들이 있는데 상황에 맞춰서 찾아보고 사용하면 될 거 같습니다.
매칭은 조건식 람다 Predicate 를 받아서 해당 조건을 만족하는 요소가 있는지 체크한 결과를 리턴합니다. 다음과 같은 세 가지 메소드가 있습니다.
상황에 맞춰 함수들을 찾아가며 사용하는 것이 좋겠다. 모든 메소드들을 확인하는것은 비효율적이기 때문..
다음에 이어서,Java8 Stream API (3) 주의해야할 점에 대해서 작성하도록 하겠습니다.
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