JAVA 함수형 프로그래밍 - 스트림1
외부반복
반복문을 이용하여 각 요소를 순회한다.
List<String> names = Arrays.asList("kim", "rang", "dong", "jang");
for (String name : names) {
System.out.println(name);
}
내부반복
무엇을 할지 정의하고 어떻게 수행될지에 대한 세부사항은 라이브러리에 의해 관리된다.
List<String> names = Arrays.asList("kim", "rang", "dong", "jang");
names.stream().forEach(this::print);
private void print(String name) { // 필수 X
System.out.println(name);
}
스트림 요소 흐름
스트림
Map/Filter/Reduce 세 가지 연산으로 축약됨
- 맵(
Map
) : 데이터 변환 - 필터(
Filter
) : 데이터 선택 - 리듀스(
Reduce
) : 결과도출
public record Shape(int corners) implements Comparable<Shape> {
public int cornersCount() {
return corners();
}
public boolean hasCorners() {
return corners() > 0;
}
public List<Shape> twice() {
return List.of(this, this);
}
@Override
public int compareTo(Shape o) {
return Integer.compare(corners(), o.corners());
}
public static Shape circle() {
return new Shape(0);
}
public static Shape triangle() {
return new Shape(3);
}
public static Shape square() {
return new Shape(4);
}
}
스트림 중간 (요소선택)
filter
요소 중 filter
에 참인 결과만 뽑는다.
List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));
List<Shape> filterShapes = shapes.stream()
.filter(Shape::hasCorners)
.collect(Collectors.toList());
System.out.println(filterShapes.toString()); // [Shape(4), Shape(3), Shape(3), Shape(4)]
dropWhile
요소 중 dropWhile
결과에 참일때까지 요소들을 버린다.
List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));
List<Shape> filterShapes = shapes.stream()
.dropWhile(Shape::hasCorners)
.collect(Collectors.toList());
System.out.println(filterShapes.toString()); // [Shape(0), Shape(4), Shape(0)]
takeWhile
요소 중 takeWhile
결과에 거짓일 때까지 요소를 선택한다.
List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));
List<Shape> filterShapes = shapes.stream()
.takeWhile(Shape::hasCorners)
.collect(Collectors.toList());
System.out.println(filterShapes.toString()); // [Shape(4), Shape(3), Shape(3)]
limit
요소 중 limit
를 지정한 갯수까지만 선택한다.
List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));
List<Shape> filterShapes = shapes.stream()
.limit(2)
.collect(Collectors.toList());
System.out.println(filterShapes.toString()); // [Shape(4), Shape(3)]
skip
요소 중 skip
를 지정한 갯수를 건너뛴다.
List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));
List<Shape> filterShapes = shapes.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println(filterShapes.toString()); // [Shape(3), Shape(0), Shape(4), Shape(0)]
distinct
요소 중 중복된 것을 제거해줌
List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));
List<Shape> filterShapes = shapes.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(filterShapes.toString()); // [Shape(4), Shape(3), Shape(0)]
sorted
Comparable
을 상속(=implements
)받은 경우 정렬 가능
List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));
List<Shape> filterShapes = shapes.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(filterShapes.toString()); // [Shape(0), Shape(0), Shape(3), Shape(3), Shape(4), Shape(4)]
요소 매핑
map
요소를 새로운 요소로 반환하는 함수
List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));
List<Shape> filterShapes = shapes.stream()
.map(Shape::cornersCount)
.collect(Collectors.toList());
System.out.println(filterShapes.toString()); // [4, 3, 3, 0, 4, 0]
flatMap
Optional
과 같은 컨테이너 형태의 요소를 펼쳐서 새로운 다중 요소를 포함하는 새로운 스트림으로 만듬
List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(0));
List<Shape> filterShapes = shapes.stream()
.map(Shape::twice)
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(filterShapes.toString()); // [new Shape(4), new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(0)]
Peek
중간결과를 디버깅하기 위해 사용
List<Shape> result = Stream.of(Shape.square(), Shape.triangle(), Shape.circle())
.map(Shape::twice)
.flatMap(List::stream)
.peek(shape -> System.out.println("모양: " + shape))
.filter(shape -> shape.corners() < 4)
.collect(Collectors.toList());
// 모양: Shape[corners=4]
// 모양: Shape[corners=4]
// 모양: Shape[corners=3]
// 모양: Shape[corners=3]
// 모양: Shape[corners=0]
// 모양: Shape[corners=0]
스트림 종료
네 가지 그룹으로 나뉨
- 축소
- 집계
- 찾기 및 매칭시키기
- 소비
요소 축소 (=fold 연산)
누적 연산자를 반복적으로 적용하여 스트림의 요소들을 하나의 결과값으로 만듬
reduce
identify
: 시드값
T reduce(T identify, BinaryOperator<T> accumulator)
U reduce(U identify, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
Optional<T> reduce(BinaryOperator<T> accumulator)
// 0부터 누적
int total = Stream.of(1, 2, 3, 4, 5, 6)
.reduce(0, Integer::sum); // 21
int mapReduce = Stream.of("apple", "orange")
.mapToInt(String::length)
.reduce(0, (acc, length) -> acc + length); // 17
int reduceOnly = Stream.of("apple", "orange", "banana")
.reduce(0, (acc, str) -> acc + str.length(), Integer::sum); // 17
요소 집계
컬렉터를 활용한 요소 집계
java.util.Collection
toCollection(Supplier<C> collectionFactory)
toList()
,toSet()
toUnmodifiabelList()
,toUnmodifiableSet()
: 자바 10+ 직렬화 가능성, 스레드 안정성, 가변성 등 고려
java.util.Map
toMap()
,toCurrentMap()
toUnmodifiableMap()
: 자바 10+ 직렬화 가능성, 스레드 안정성, 가변성 등 고려
찾기 및 매칭시키기
함수 | 설명 |
---|---|
Optional<T> findFirst() |
첫 번째로 만나는 스트림의 요소를 반환함, 스트림이 비어있다면 빈 Optional<T> 를 반환 |
Optional<T> findAny() |
스트림 내의 임의의 요소를 반환, 스트림이 비어있다면 빈 Optional<T> 를 반환 |
boolean anyMatch(Predicate<? super T> predicate) |
predicate 와 일치하는 요소가 스트림에 하나라도 존재한다면 true 반환 |
boolean allMatch(Predicate<? super T> predicate) |
스트림의 모든 요소가 predicate와 일치하면 true 반환 |
boolean noneMatch(Predicate<? super T> predicate) |
predicate 와 일치하는 요소가 스트림에 없으면 true 반환 |
요소 소비
함수 | 설명 |
---|---|
void forEach(Consumer<? super T> action) |
각 요소마다 주어진 동작을 수행함 |
void forEachOrdered(Consumer<? super T>) |
스트림이 ORDERED 상태라면 만나게 되는 순서에 따라 각 요소에 대한 action이 실행됨 |
연산비용
파이프라인을 통과하는 요소가 적을수록 성능은 향상된다.
filter()
같은 연산을 먼저 하자
map()
연산 : 5번실행sorted()
연산 : 8번실행filter()
연산 : 5번실행forEach()
연산 : 2번실행
- 결과 : 총 20번의 연산 실행
Stream.of("ananas", "oranges", "apple", "pear", "banana")
.map(String::toUpperCase)
.sorted()
.filter(str -> str.startsWith("A"))
.forEach(System.out::println);
filter()
연산 : 5번실행map()
연산 : 2번실행sorted()
연산 : 1번 실행forEach()
연산 : 2번 실행
- 결과 : 총 10번의 연산 실행
Stream.of("ananas", "oranges", "apple", "pear", "banana")
.filter(str -> str.startsWith("a"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
쇼트 서킷 (short-circuit)
스트림을 조기에 중단하는 것
스트림이 모든 요소를 순회하지 않아도 목적을 달성할 수 있는 연산
논쇼트 서킷 (non-short-circuit)
int result = Stream.of("apple", "orange", "banana", "melon")
.peek(str -> Sytem.out.println("peek 1: " + str))
.map(str -> {
System.out.println("map: " + str);
return str.toUpperCase();
})
.peek(str -> System.out.println("peek 2: " + str))
.count();
// 출력결과 없음
int result = Stream.of("apple", "orange", "banana", "melon")
.filter(str -> str.contains("e"))
.peek(str -> Sytem.out.println("peek 1: " + str))
.map(str -> {
System.out.println("map: " + str);
return str.toUpperCase();
})
.peek(str -> System.out.println("peek 2: " + str))
.count();
/**
* 출력
* peek 1: apple
* map: apple
* peek 2: APPLE
* peek 1: orange
* map: orange
* peek 2: ORANGE
* peek 1: melon
* map: melon
* peek 2: MELON
*/
댓글남기기