728x90
대량의 데이터를 가공해서 축소하는 것을 일반적으로 리덕션(Reduction)이라고 하는데. 데이터의 합계, 평균값, 카운팅, 최대값, 최소값 등이 대표적인 리덕션의 결과물이라고 볼 수 있다. 그러나 컬렉션의 요소를 리덕션의 결과물로 바로 집계할 수 없을 경우에는 집계하기 좋도록 필터링, 매핑, 정렬, 그룹핑 등의 중간 처리가 필요하다.
중간 처리 & 최종 처리
스트림은 데이터의 필터링, 매핑, 정렬, 그룹핑 등의 중간 처리와 합계, 평균, 카운팅, 최대값, 최소값 등의 최종 처리를 파이프라인(pipelines)으로 해결한다. 파이프라인은 여러 개의 스트림이 연결되어 있는 구조를 말한다. 파이프라인에서 최종처리를 제외하고는 모두 중간 처리 스트림이다.
Stream 인터페이스에는 필터링, 매핑, 정렬 등의 많은 중간 처리 메소드가 있는데. 이 메소드들은 중간 처리된 스트림을 리턴한다.
그리고 이 스트림에서 다시 중간 처리 메소드를 호출해서 파이프라인을 형성하게 된다.
중간 스트림이 생성될 때 바로 요소들이 중간 처리되는 것이아니라, 최종 처리가 설정(시작)되기 전까지 중간처리는 지연된다. 최종처리가 시작되면 비로소 컬렉션 요소가 하나씩 중간 스트림에서 처리되고 최종 처리까지 오게된다.
이와 같은 파이프라인을 자바 코드로 표현하면 아래와 같다.
Stream<Member> maleFemaleStream = list.stream()
Stream<Member> maleStream = maleFemaleStream.filter(m -> m.getSex()==Member.MALE);
IntStream ageStream = maleStream.mapToInt(Member::getAge);
OptionalDouble optionalDouble = ageStream.average();
double ageAvg = optionalDouble.getAsDouble();
로컬 변수 생략하고 연결하면 다음과 같은 형태의 파이프라인 코드만 남는다.
double ageAvg = list.stream() //오리지날 스트림
.filter(m -> m.getSex()==Member.MALE) // 중간 처리 스트림
.mapToInt(Member :: getAge) // 중간 처리 스트림
.average() //최종 처리
.getAsDouble();
filter(m -> m.getSex()==Member.MALE) // 남자 Member 객체를 요소로 하는 새로운 스트림을 생성한다.
mapToInt(Member :: getAge()) // Member 객체를 age 값으로 매핑해서 age를 요소로 하는 새로운 스트림을 생성한다.
average() // age 요소들의 평균을 OptionalDouble에 저장한다.
getAsDouble() // OptionalDouble에서 저장된 평균값을 읽으려면 getAsDouble()을 호출한다.
중간 처리 메소드 & 최종 처리 메소드
스트림 파이프라인에서 중간 처리를 하는 메소드와 최종 처리를 하는 메소드의 종류
중간 처리 메소드 (중간 연산자)
동작 | 메소드 |
필터링 | dinstict(), filter() |
자르기 | skip(), limit() |
정렬 | sorted() |
매핑 | flatMap(), flatMapToP(), map(), mapToP(), asDoubleStream(), asLongStream() |
조회 | peek() |
- 중간 처리 메소드 이후에 최종 처리 메소드를 붙여서 사용한다.
- 값을 원하는 형태로 처리하기 위한 연산자이다. 중간 연산자들은 lazy하게 실행되고 결과로 stream을 반환한다.
- lazy한 처리는 최종처리메소드가 실행되기전까지 연산이 실행되지 않음,결과가 필요하기 전까지 실행되지 않는다, 연산의 시점을 최대한 늦춘다는 의미이다.
- 그렇기 때문에 중간연산자는 연쇄적으로 연결하여 연산을 처리할 수 있다. (Ex - list.stream().filter(...).map(...))
필터링
필터링은 스트림의 일부 요소를 제거하는 역할을 한다.
Stream<String> stringStream = Stream.of("Java","Is","Fun","Isn't","It","?","Java");
stringStream.forEach(System.out::println); // 기본형 중복이 포함되서 출력됨.
- distinct() : 스트림에 같은 요소가 있을 경우 하나만 남기고 삭제하는 메소드(중복 제거)
stringStream.distinct().forEach(System.out::println);
// stringStream.distinct() 여기까지 중간처리메소드, foreach는 최종처리 메소드
- filter() : Predicate 계열을 입력을 받아, true인 요소만 남긴다.
stringStream = Stream.of("Java","Is","Fun","Isn't","It","?");
stringStream.filter(s- >s.length()>=3).forEach(System.out::println);
자르기
자르기는 스트림의 일부 요소를 한번에 생략한다.
- skip(long n) : 스트림의 처음부터 n개의 요소를 생략하는 메소드, n개 자른다. n+1부터 끝까지 반환
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Stream<Integer> stream = list.stream().skip(5).forEach(n-> System.out.println(s));
- limit(long maxsize) : 스트림의 최대 요소 개수를 maxsize를 제한한다, maxsize의 요소보다 뒤에 있는 값들은 짤린다. 0부터 n개까지 반환
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Stream<Integer> stream = list.stream().limit(5).forEach(n-> System.out.println(s));
정렬
스트림 요소의 compareTo() 또는 입력받은 Comparator를 이용해 정렬한다.
// Comparable 객체를 정렬한 스트림 반환
stringStream = Stream.of("abc","fwf","twtie","dnmov","work");
stringStream.sorted().forEach(System.out::println); // 사전순으로 정렬됨
// Comparator 인터페이스를 람다식으로 구현하여 정렬한다.
// Comparator를 이용하여 정렬된 스트림 반환
stringStream = Stream.of("abc","fwf","twtie","dnmov","work");
stringStream.sorted((o1, o2) -> o1.length() - o2.length())
.forEach(System.out::println);
//compreator를 람다식으로 작성해줄 수 있다, 길이가 짧은 것부터 나온다.
매핑
Function 인터페이스를 이용해 요소의 값을 변환한다.
- map 계열 - 입력 1 : 1 출력
- <R> Stream<R> map(Function<? super T, ? extends R> mapper) : 기존 스트림의 T 타입 요소를 R 타입으로 변환하여 새로운 스트림 반환
// Function 계열의 인터페이스를 사용하여 스트림의 각 요소를 매핑(Operator 계열도 사용) stringStream = Stream.of("abc","fwf","twtie","dnmov","work"); // Function 계열로 string -> integer 로 변환하는 매핑 // Function<String,Intger>와 같은 형식을 람다식에 넣어준 것이다. Stream<Integer> stream2 = stringStream.map(s->s.length()) ; //입력은 string 출력은 원하는대로 할수 있다 stream2.forEach(System.out::println);
- PStream mapToP(ToPFunction<? super T> mapper) : R(return)이 기본형 타입으로 제한된 map()
// PStream (기본형 타입의 스트림)은 Operator 계열로 처리(자료형 반환x) // 입출력 값이 똑같기 때문에 IntStream intStream3 = IntStream.of(5,2,30,8,0,2,-34); IntStream intStream4 = intStream3.map(value -> value * 10); // 형 변환이 되지 않고 입력이 출력이 된다. intStream4.forEach(System.out::println);
- flatMap 계열 - 입력 1 : n 출력(스트림 형태로 출력한다.)(Function 계열만 사용)
- <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) : 스트림의 T 타입 요소가 n개의 R 타입 요소로 매핑된 새로운 스트림을 반환
- PStream flatMapToP(Function<? super T, ? extends PStream> mapper) : R이 기본형 타입으로 제한된 flatMap()
// s.split("") : "java" -> {"j","a","v","a"} List<String> list2 = Arrays.asList("java","backend","best","course"); list2.stream().flatMap(s -> { return Arrays.stream(s.split("")); }).forEach(System.out::println); //foreach를 사용하면 스트림이 끝나게 된다.
조회
스트림 처리 과정에 영향을 미치지 않으며, 중간 결과를 확인할 수 있으며 입력받은 스트림과 동일한 스트림을 반환한다.
- Stream peek(Consumer<? super T> action)
- peek() -> Consumer 계열의 람다식 입력을 받아 입력 요소를 소비
- peek()는 입력받아 스트림과 동일한 스트림을 다시 출력
System.out.println(list2.stream().flatMap(s -> {
return Arrays.stream(s.split(""));
}).peek(s-> System.out.println("flatMap():"+s))
.distinct().peek(s-> System.out.println("distinct():"+s))
.count()); // 함수형 프로그래밍은 선언형이어서 어떻게 해라라고 말해야한다.
// flatmap은 모든 요소를 다 나타내고 distinct는 중복된것을 없애고 하나만 출력됨.
'Languages > Java' 카테고리의 다른 글
[Java] 스트림 (stream) (1) 개념과 종류 (0) | 2022.01.05 |
---|---|
[Java] 람다식 (4) 표준 API의 함수적 인터페이스 (0) | 2021.12.31 |
[Java] 람다식 (3) 클래스 멤버와 로컬 변수 사용 (0) | 2021.12.30 |
[Java] 람다식 (2) 타겟 타입과 함수적 인터페이스 (0) | 2021.12.30 |
[Java] 람다식 (1) 람다식 개념과 기본 문법 (0) | 2021.12.30 |
댓글