Stream의 중간처리 메소드의 종류들과 사용 방법
중간 처리 메소드의 종류
종류 | 리턴타입 | 메소드(매개변수) | 소속된 인터페이스 | |
중간 처리 | 필터링 | Stream IntStream LongStream DoubleStream |
distinct() | 공통 |
filter(...) | 공통 | |||
매핑 | flatMap(...) | 공통 | ||
flatMapToDouble(...) | Stream | |||
flatMapToInt | Stream | |||
flatMapToLong(...) | Stream | |||
map(...) | 공통 | |||
mapToDouble(...) | Stream, IntStream, LongStream | |||
mapToInt(...) | Stream, LongStream, DoubleStream | |||
mapToLong(...) | Stream, IntStream, DoubleStream | |||
mapToObj(...) | IntStream, LongStream, DoubleStream | |||
asDoubleStream() | IntStream, LongStream | |||
asLongStream() | IntStream | |||
boxed() | IntStream, LongStream, DoubleStream | |||
정렬 | sorted(...) | 공통 | ||
루핑 | peek(...) | 공통 |
위와 같이 리턴 타입이 스트림이면 중간 처리 메소드이다.
또한, 소속된 인터페이스가 공통이라는 의미는 Stream, IntStream, LongStream, DoubleStream에서 모두 제공된다는 뜻이다.
종류로는 필터링, 매핑, 정렬, 루핑이 있는데 하나씩 살펴보자.
필터링
1. distinct()
distinct() 메소드는 중복을 제거하는데, Stream의 경우 Object.equals(Object)가 true이면 동일한 객체로 판단하여 중복을 제거하고, 나머지 IntStream, longStream, DoubleStream은 동일값일 경우 중복을 제거한다.
dictinct()를 사용한 예시 코드는 아래와 같다.
public static void main(String[] args) {
Solution solution = new Solution();
List<String> list = Arrays.asList("가", "가", "가", "나", "나", "다");
list.stream().distinct().forEach(Main::print);
}
public static void print(String s) {
System.out.println(s + " 출력됨 ");
}
중복을 제거하므로 출력 결과는 가,나,다 이 세가지만 나올 것이다. 참고로, foreach() 메소드는 최종처리 메소드이다.
2. filter()
filter() 메소드는 원하는 기준으로 요소를 걸러내는 역할을 한다. 여기서 원하는 기준은 Predicate 형태를 매개값으로 주면 된다. 그러면 Predicate가 true를 리턴하는 요소만 필터링하는 방식으로 작동한다.
public static void main(String[] args) {
Solution solution = new Solution();
List<String> list = Arrays.asList("가", "가", "가", "나", "나", "다");
list.stream().filter(s->s.equals("나")).forEach(Main::print);//"나"만 남겨두고 다 버림
}
public static void print(String s) {
System.out.println(s + " 출력됨 ");
}
매핑
매핑은 스트림의 요소를 다른 요소로 대체하는 작업을 말한다. 스트림에서 제공하는 매핑 메소드는 flatMapXXX(), mapXXX(), asDoubleStream(), asLongStream(), boxed()가 있다.
1. flatMapXXX()
flatMapXXX() 메소드는 Array나 Object로 감싸져 있는 모든 원소를 단일 원소 스트림으로 반환하는 역할을 하며, 매개변수로는 Function을 사용한다. 그리고 내부적으로 T를 Stream<R> 형식인 스트림 형태로 매핑한다.
아래는 flatMap을 사용한 예제 코드이다.
public static void main(String[] args) {
Solution solution = new Solution();
List<String> list = Arrays.asList("Hello", "World!");
list.stream().flatMap(s -> Arrays.stream(s.split(""))).forEach(Main::print);
}
2. mapXXX()
mapXXX 메소드는 단일 스트림의 원소를 매핑시킨 후 매핑시킨 값을 다시 스트림으로 변환하는 역할을 하며, 매개변수로는 Function이나 Operator를 사용한다. 그리고 내부적으로 T를 R형식으로 매핑하는데 flatMapXXX()와는 다르게 스트림 외에 다른 형식으로 매핑이 가능하다.
flatMapXXX()와의 차이점을 예시로 구현해보자.
public static void main(String[] args) {
List<String> list = Arrays.asList("Hello", "World");
list.stream().flatMap(s -> Arrays.stream(s.split(""))).forEach(System.out::print);
System.out.println("\n--------------------------------");
list.stream().map(s->Arrays.stream(s.split(""))).forEach(System.out::print);
}
두 출력이 결과값이 다르다. 왜 이렇게 나타나는 것일까?
그림과 코드가 다르긴 하지만 크게 다른 것이 없다. map은 매핑된 형태가 스트림이어도 되고 아니어도 무방하다.
map은 hello가 하나하나 쪼개져서 한개의 스트림으로 들어갔고, world가 하나하나 쪼개져서 한개의 스트림으로 들어가 총 두개의 스트림으로 들어갔다.
그에 비해 flatmap은 hello가 하나하나 쪼개지고 world도 하나하나 쪼개져서 그 전부가 한개의 스트림으로 들어갔다.
위 출력문 중 첫번째 줄은 h,e,l,l,o,w,o,r,l,d가 하나의 스트림으로 출력이 된 것이고
두번째 줄은 hello가 하나의 스트림으로, world가 하나의 스트림으로 스트림 참조값이 두개 출력이 된 것이다.
mapXXX() 사용 예시2
public static void main(String[] args) {
List<Member> memberList = Arrays.asList(
new Member("이산", Sex.MALE, 22),
new Member("진영", Sex.MALE, 23),
new Member("솔빈", Sex.FEMALE, 20)
);
memberList.stream()
.mapToInt(Member::getAge)
.forEach(System.out::println);
}
3. asDoubleStream(), asLongStream(), boxed()
asDoubleStream 메소드는 IntStream의 int요소 또는 LongStream의 long요소를 double 요소로 타입변환해서 DoubleStream을 생성한다. 마찬가지로 다른 메소드들도 또다른 요소의 타입으로 변환 가능하고, boxed 메소드는 int,long,double 요소를 Int, Long, Double 요소로 박싱해서 Stream을 생성한다.
여기서 주의할 점은 IntStream과 Stream<Integer>는 엄연히 다른 스트림이라는 것이다.
public static void main(String[] args) {
int[] intArray = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray);
intStream
.asDoubleStream() // DoubleStream 생성
.forEach(System.out::println);
System.out.println();
intStream = Arrays.stream(intArray);
intStream
.boxed() // Stream<Integer> 생성
.forEach(obj -> System.out.println(obj.intValue()));
}
정렬
스트림은 중간 처리 단계에서 요소를 일정한 기준에 맞게 정렬을 할 수 있다. 정렬을 할 수 있는 스트림의 타입은 Stream<T>와 IntStream, DoubleStream, LongStream이 있는데, 있는데 이를 각각 설명해 보겠다.
1. Stream<T>
sorted()를 사용하였을 경우 객체를 Comparable 구현 방법에 따라 정렬한다. Student 클래스가 있다고 하면, Comparable을 상속받아서 compareTo() 메소드를 오버라이드함으로써 정렬하는 기준을 정해줄 수 있다.
반면, sorted(Comparable<T>)를 사용하였을 경우 객체를 주어진 Comparator에 따라 정렬한다. Comparator을 따로 정의하고 이를 객체로 생산형 sorted의 매개변수로 넘겨주어도 되고, Comparator의 내장된 메소드를 사용해도 되고, 람다식을 사용해도 무방하다.
람다식을 사용한 예제는 아래와 같다.
public static void main(String[] args) {
List<Member> memberList = Arrays.asList(
new Member("이산", Sex.MALE, 22),
new Member("진영", Sex.MALE, 23),
new Member("지수", Sex.FEMALE, 20)
);
memberList.stream()
.sorted((m1, m2) -> m1.getAge() - m2.getAge())
// .sorted()만 사용해도 된다. 어차피 같음
.forEach(m -> System.out.println(m.getName()));
}
2. IntStream, DoubleStream, LongStream
이들은 sorted() 메소드를 사용하여 요소를 오름차순 정렬할 수 있다. 만약 매개변수를 통하여 정렬하는 기준을 설정해주고 싶다면, 위에서 설명한대로 Comparator를 정의하여 넣어주면 된다.
루핑
루핑은 특이하게도 중간 처리 메소드에서도 사용되고, 최종 처리 메소드에서도 사용된다. 이 둘의 차이를 알아보자.
기능 자체는 루핑한다는 것에서 동일하지만, peek()는 최종 처리 메소드가 없으면 동작하지 않는다. forEach()메소드는 그 자체로 최종 처리 메소드이기 때문에 루핑이 정상적으로 작동한다.
public static void main(String[] args) {
int[] intArr = {1, 2, 3, 4, 5};
System.out.println("[peek()를 마지막에 호출한 경우]");
Arrays.stream(intArr)
.filter(a -> a % 2 == 0)
.peek(System.out::println); // 동작하지 않음.
System.out.println("\n\n[최종 처리 메소드를 마지막에 호출한 경우]");
int total = Arrays.stream(intArr)
.filter(a -> a % 2 == 0)
.peek(System.out::println) // 동작함.
.sum(); // 요소의 합을 구하는 최종 처리 메소드
System.out.println("총합 : " + total);
System.out.println("\n\n[forEach()를 마지막에 호출한 경우");
Arrays.stream(intArr)
.filter(a -> a % 2 == 0)
.forEach(System.out::println);
}
최종 처리 메소드 없이 peek()만 호출하면 그 안에 있는 짝수 요소가 출력되지 않으며, 최종 처리 메소드가 존재할 때 peek()를 호출하면 짝수 요소가 출력된다는 사실을 알 수 있다.
또한, foreach는 그 자체로 최종 처리 메소드이므로 마지막에 사용함으로써 짝수 요소를 출력하게 만들 수 있다.
'DEV > JAVA' 카테고리의 다른 글
자바의 차트 라이브러리 - JFreeChart (1) | 2023.10.12 |
---|---|
Java8부터 도입된 Stream(1) - Stream의 개념 (0) | 2023.08.11 |
인터페이스 정리 코드(자바) (0) | 2022.11.18 |
추상 클래스와 인터페이스의 차이 (0) | 2022.11.18 |