Stream 중간 연산
1) distinct()
: 중복 요소를 제거
Primitive Type 경우
// IntStream.class
IntStream distinct();
// #예시
int[] intStream = new int[] {1, 2, 3, 4, 1, 2, 3, 4};
int[] result = Arrays.stream(intStream).distinct().toArray();
assertThat(result).hasSize(4); // ok
Collection 경우
: Person 클래스에 정의한 equals 통해 비교하여 같은 객체인지 판별한다( 동등성 )
// Collection.class
default Stream<E> stream() {
return StreamSupport.stream(this.spliterator(), false);
}
// #예시
List<Person> people = List.of(
new Person(1, "Ash"),
new Person(3, "Cina"),
new Person(2, "Brian"),
new Person(3, "Cina"),
new Person(1, "Ash")
);
List<Person> person = people.stream().distinct().collect(Collectors.toList());
assertThat(person).hasSize(3);
assertThat(person).extracting("name")
.containsExactlyInAnyOrder("Ash", "Brian", "Cina");
2) filter()
: 조건을 통과하는 요소만 뽑아낸다
Primitive Type 경우
// IntStream.class
IntStream filter(IntPredicate var1);
// #예시
int[] numbers = IntStream.rangeClosed(1, 10).toArray(); // [1, .., 10] 배열 생성
// 필터링
int[] odd = Arrays.stream(numbers).filter(v -> v % 2 == 1).toArray(); // 홀수
int[] even = Arrays.stream(numbers).filter(v -> v % 2 == 0).toArray(); // 짝수
assertThat(odd).hasSize(5);
assertThat(odd).containsExactly(1, 3, 5, 7, 9);
assertThat(even).hasSize(5);
assertThat(even).containsExactly(2, 4, 6, 8, 10);
Collection 경우
임의 클래스 정의
class Student {
String name;
int score;
// construtor, getter 생략
public boolean passed() {
return this.score >= 70;
}
}
마찬가지로 Collection 자료구조의 경우 Collection.class 에 정의된 stream() method 호출
// Collection.class
default Stream<E> stream() {
return StreamSupport.stream(this.spliterator(), false);
}
// #예시
// 데이터 생성 (이름, 성적)
List<Student> students = List.of(
new Student("Atom", 69),
new Student("Cherry", 85),
new Student("Brian", 75),
new Student("Eilish", 45)
);
List<Student> passed = students.stream()
.filter(Student::passed)
.collect(Collectors.toList());
List<Student> notPass = students.stream()
.filter(s -> !s.passed())
.collect(Collectors.toList());
assertThat(passed).hasSize(2);
assertThat(passed).extracting("name")
.containsExactly("Cherry", "Brian");
assertThat(notPass).hasSize(2);
assertThat(notPass).extracting("name")
.containsExactly("Atom", "Eilish");
3) limit()
: maxSize만큼 스트림의 요소를 잘라낸다
Primitive Type 경우
// IntStream.class
IntStream limit(long var1);
// #예시
List<Integer> integers = IntStream.range(1, 100).limit(10).boxed().collect(Collectors.toList());;
assertThat(integers).hasSize(10);
assertThat(integers).contains(1, 2, 3, 4, 5, 6, 7, 8 , 9, 10);
int[] random = new Random().ints().limit(5).toArray();
assertThat(random).hasSize(5);
Stream.class 생략
4) skip()
: 스트림의 요소를 n 만큼 건너뛴다
Primitive Type 경우
// IntStream.class
IntStream skip(long var1);
// #예시
int[] odd = IntStream.rangeClosed(1, 10).filter(i -> i % 2 == 0).skip(2).toArray();
assertThat(odd).hasSize(3);
assertThat(odd).contains(6, 8, 10);
Stream.class 생략
5) peek()
- 스트림의 각 요소에 작업 수행
- 최종 연산 forEach()의 경우 스트림의 요소를 소비하는 반면, peek()는 중간 연산 과정 중에 요소를 확인하는 용도로 사용 (소비 안함)
Stream.class
Stream<T> peek(Consumer<? super T> var1);
// #예시. 문자열 길이가 3 초과할 경우, 대문자로 변환하여 List 반환
List<String> result = Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(i -> System.out.println("filtered value : " + i))
.map(String::toUpperCase)
.peek(i -> System.out.println("mapped value : " + i))
.collect(Collectors.toList());
assertThat(result).hasSize(2);
assertThat(result).contains("THREE", "FOUR");
// # 콘솔 출력
filtered value : three
mapped value : THREE
filtered value : four
mapped value : FOUR
6) sorted()
스트림의 요소를 정렬한다
Stream.class
Stream<T> sorted()
Stream<T> sorted(Comparator<T> comparator)
Compartor.naturalOrder() 와 Comparator.reverseOrder()
- naturalOrder : 오름차순
- reverseOrder : 내림차순
List<Integer> numbers = List.of(9, 4, 2, 1, 6);
List<Integer> asc = numbers.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
List<Integer> desc = numbers.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
assertThat(asc).containsExactly(1, 2, 4, 6, 9); // ok
assertThat(desc).containsExactly(9, 6, 4, 2, 1); // ok
아래와 같이 선언되어 있었다
// Comparators.class
static enum NaturalOrderComparator implements Comparator<Comparable<Object>> {
INSTANCE;
private NaturalOrderComparator() {
}
public int compare(Comparable<Object> c1, Comparable<Object> c2) {
return c1.compareTo(c2); // 오름차순
}
public Comparator<Comparable<Object>> reversed() {
return Comparator.reverseOrder();
}
}
// Collections.class
private static class ReverseComparator implements Comparator<Comparable<Object>>, Serializable {
private static final long serialVersionUID = 7207038068494060240L;
static final ReverseComparator REVERSE_ORDER = new ReverseComparator();
private ReverseComparator() {
}
public int compare(Comparable<Object> c1, Comparable<Object> c2) {
return c2.compareTo(c1); // 내림차순
}
private Object readResolve() {
return Collections.reverseOrder();
}
public Comparator<Comparable<Object>> reversed() {
return Comparator.naturalOrder();
}
}
람다 표현식 사용
정수를 이어 붙여 만들 수 있는 가장 큰 수를 구하는 문제가 있을 때
입력(numbers) | 결과 |
[6, 10, 2] | "6210" |
[3, 30, 34, 5, 9] | "9534330" |
IntStream.of(numbers) // IntStream
.mapToObj(String::valueOf) // Stream<String>
.sorted((a, b) -> (b + a).compareTo(a + b)) // 람다 표현식 사용
.collect(Collectors.joining());
- IntStream.sorted() 는 Comparator 매개변수를 받지 않기 때문에 Stream<T> 로 변환 후 sorted(..) 호출
클래스 메서드 참조
List<String> alphabet = List.of("A", "s", "w", "F", "e");
alphabet.stream().sorted(String::compareTo).forEach(System.out::println); // A F e s w
List<Integer> numbers = List.of(9, 4, 2, 1, 6);
numbers.stream().sorted(Integer::compareTo).forEach(System.out::println); // 1 2 4 6 9
클래스 static 상수 참조
- String.CASE_INSENSITIVE_ORDER : 문자열의 대소문자 구분하지 않고 정렬
- String.CASE_INSENSITIVE_ORDER.reversed() : String.CASE_INSENSITIVE_ORDER 역정렬
List<String> alphabet = List.of("A", "s", "w", "F", "e");
alphabet.stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(System.out::println); // A e F s w
alphabet.stream().sorted(String.CASE_INSENSITIVE_ORDER.reversed()).forEach(System.out::println); // w s F e A
Comparator 클래스 정적 메서드(static method) 활용
// String 길이로 정렬, T : String, U : int
List<String> strings = List.of("one", "two", "three", "five", "six", "seven");
strings.stream().sorted(Comparator.comparing(String::length)).forEach(System.out::println);
strings.stream().sorted(Comparator.comparingInt(String::length)).forEach(System.out::println);
strings.stream().sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println);
- comparing(..) : T 타입 입력 받아 U 타입 리턴하는 Function 전달받아 Wrapper Class 비교
- comparingInt(..) : T 타입 입력받아 int 타입 리턴하는 ToIntFunction 전달받아 Primitive Type 비교
- comparingLong(..), comparingDouble(..) 마찬가지로 T 타입 입력 받아 해당 Primitive Type 비교
Wrapper 클래스 타입 보다 Primitive 타입 비교하는 게 성능에서 이점을 가짐
Comparator.class
static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator)((Serializable)((c1, c2) -> {
return ((Comparable)keyExtractor.apply(c1)).compareTo(keyExtractor.apply(c2));
}));
}
static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator)((Serializable)((c1, c2) -> {
return Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}));
}
static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator)((Serializable)((c1, c2) -> {
return Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}));
}
static <T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator)((Serializable)((c1, c2) -> {
return Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
}));
}
**Comparator.comparing(..).thenComparingInt(..) 의 경우 추후 포스팅
7) map(), flatMap()
map : 스트림의 요소를 변환(mapping)하여 반환
<R> Stream<R> map(Function<? super T, ? extends R> var1);
IntStream mapToInt(ToIntFunction<? super T> var1); // T 타입 받아 int 리턴
LongStream mapToLong(ToLongFunction<? super T> var1); // T 타입받아 long 리턴
DoubleStream mapToDouble(ToDoubleFunction<? super T> var1); // T 타입 받아 double 리턴
예시
- 파일 스트림에서 파일명 내에 파일 확장자만 추출하여 대문자 변환 후 리스트로 반환
Stream<File> fileStream = Stream.of(new File("ex1.java"),
new File("ex1.txt"),
new File("ex1.class"),
new File("ex2.class"),
new File("ex3.csv"),
new File("ex4")
);
List<String> result = fileStream.map(File::getName) // Stream<String>
.fileter(t -> t.indexOf(".") != -1) // Stream<String>
.map(t -> t.substring(t.indexOf(".") + 1) // Stream<String>
.map(String::toUpperCase) // Stream<String>
.collect(Collectors.toList());
assertThat(result).containsExactly("JAVA", "TXT", "CLASS", "CSV");
flatMap : 2차원 스트림을 1차원 스트림으로 평면화
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> var1);
IntStream flatMapToInt(Function<? super T, ? extends IntStream> var1);
LongStream flatMapToLong(Function<? super T, ? extends LongStream> var1);
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> var1);
예시. String[] 문자열을 1차원으로
Stream<String[]> strArrStream = Stream.of(
new String[] {"a", "aa", "aaa"},
new String[] {"b", "bb", "bbb"},
new String[] {"c"}
);
// 만약 map() 사용할 경우 Stream<Stream<String>>을 리턴
List<String> result = strArrStream.flatMap(Arrays::stream) // Stream<String>
.collect(Collectors.toList());
assertThat(result).hasSize(7);
참고
https://mkyong.com/java8/java-8-flatmap-example/
'공부 > Java' 카테고리의 다른 글
[Java] Stream Quiz 개인 풀이 (출처. 망나니 개발자 기술 블로그) (0) | 2023.08.15 |
---|---|
[Java] Stream 최종 연산(Terminal Operation) (0) | 2023.08.05 |
[Java] Stream 생성 (파일 데이터 제외) (0) | 2023.08.03 |
[Java] Stream API (0) | 2023.08.03 |
[Java] 람다식을 더 짧게 - 메서드 참조 Method Reference (0) | 2023.08.02 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!