7.1 원시 스트림
스트림과 함께 원시 타입을 사용하는 데는 두가지 옵션이 존재
- 오토박싱
- 원시 타입의 값을 객체로 변환할 때 오버헤드 발생
- null 값의 존재 가능성
- 스트림의 특화된 변형
- IntStream, LongStream, DoubleStream 등 원시 타입 전용 스트림
public class PrimitiveStreamExample {
public static void main(String[] args) {
IntStream intStream = IntStream.range(1, 10);
intStream.forEach(System.out::print);
}
}
/*
---
123456789
*/
7.2 반복 스트림
세밀한 제어가 필요하다면 스트림 타입 및 원시 타입 변형에서 사용 가능한 static interate 메서드를 지원한다.
<T> Stream<T> interate(T seed, UnaryOperator<T> f)
IntStream iterate(int seed, IntUnaryOperator f)
자바 9에서는 종료 조건을 위한 Predicate 변형을 포함하여 두 가지 추가 메서드를 도입했다.
<T> Stream<T> interate(T seed, Predicate<T> hasNext, UnaryOperator<T> next)
IntStream iterate(int seed, IntPredicate hasNext, IntUnaryOperator next)
스트림에 대한 반복적 접근법은 시드값에 UnaryOperator
를 적용함으로써 정렬되고 잠재적으로 무한한 요소의 시퀀스를 생성한다.
반복 스트림은 ORDERED, IMMUTABLE, 원시 스트림의 경우 NONNULL 등의 특성을 가진다.
public class IterateStreamExample {
public static void main(String[] args) {
Stream.iterate(1, n -> n + 1)
.limit(10)
.forEach(System.out::print);
}
}
/*
---
12345678910
*/
7.3 무한 스트림
<T> Stream<T> generate(Supplier<T> s)
IntStream generate(IntSupplier s)
LongStream generate(LongSupplier s)
DoubleStream generate(DoubleSupplier s)
초기 값이 없기 때문에 스트림은 순서가 없는 상태가 되며, 병렬 처리에서 유리하다.
비순서 스트림의 경우, 병렬 환경에서 limit 연산을 사용하더라도 처음 n개는 보장되지 않는다는 단점이 있다.
public class InfiniteStreamExample {
public static void main(String[] args) {
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::print);
}
}
랜덤 숫자
java.util.Random
java.util.concurrent.ThreadLocalRandom
java.util.SplittableRandom
위 스트림들은 사실상 무한하다.
메모리는 한정되어 있다.
7.4 배로에서 스트림으로, 다시 배열
객체 타입 배열
java.util.Arrays
에는 Stream\<T>\
를 생성하기 위한 두 가지 정적 편의 메서드가 존재한다.
<T> Stream<T> stream(T[] array);
<T> Stream<T> stream(T[] array, int startInclusive, int endExclusive);
반대로 Stream<T>
에서 T[]로 변환하는 것은 다음 최종 연산 중 하나를 사용해야한다.
Object[] toArray();
<A> A[] toArray(IntFunction<A[] generator>);
첫 번째 방식은 JVM에서 배열이 생성되는 방식으로 인해 스트림의 실제 요소의 타입과는 관계없이 Object[] 배열만 반환한다.
스트림의 특정 요소 타입이 필요한 경우 스트림에 적절한 배열을 생성하는 방법을 제공해야할 때 두번째 방식이 활용 된다.
public class ArrayStreamExample {
public static void main(String[] args) {
String[] array = {"시윤", "하늘", "성현"};
Stream<String> stream = Arrays.stream(array);
String[] newArray = stream.toArray(String[]::new);
System.out.println(Arrays.toString(newArray));
}
}
/*
---
[시윤, 하늘, 성현]
*/
7.5 저수준 스트림 생성
Stream<T> stream(Spliterator<T> spliterator, boolean parallel)
- 순차적 스트림이나 병렬 스트림 생성
Stream<T> stream(Supplier<? extends Spliterator<T>> supplier, int characteristics, boolean parallel
- 스트림 파이프라인의 최종 연산이 호출된 후 한 번만 호출
- 가변성 또는 비동시성 스트림에 대해서도 안정적으로 처리할 수 있다.
또 다른 방법은 동적 바인딩 방식의 Spliterator를 사용하는 법.
public class LowLevelStreamExample {
public static void main(String[] args) {
Supplier<Spliterator<String>> spliteratorSupplier =
() -> Stream.of("성현", "시윤", "하늘").spliterator();
Stream<String> stream = StreamSupport.stream(spliteratorSupplier, Spliterator.IMMUTABLE, false);
stream.forEach(System.out::println);
}
}
/*
---
성현
시윤
하늘
*/
7.6 파일 I/O 사용하기
I/O 관련 슽릠은 사용이 끝난 후, 명시적으로 close()를 호출하여 닫아야 한다.
- Stream 타입은
java.lang.AutoCloseable
인터페이스를 준수하므로, try-with-resources블록을 사용한다.
디렉토리 내용 읽기
public class DirectoryContentsExample {
public static void main(String[] args) {
try (Stream<Path> paths = Files.list(Paths.get("."))) {
paths.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
---
./readme.md
./gradle
./gradlew
./.gitignore
./scripts
...
*/
깊이 우선 디렉터리 순회
public class DepthFirstTraversalExample {
public static void main(String[] args) {
try (Stream<Path> paths = Files.walk(Paths.get("."))) {
paths.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
---
./build/classes/java
./build/classes/java/main
./build/classes/java/main/org
./build/classes/java/main/org/pli
./build/classes/java/main/org/pli/readbook
...
*/
파일 시스템 탐색하기
public class FileSystemTraversalExample {
public static void main(String[] args) {
try (Stream<Path> paths = Files.find(Paths.get("."),
Integer.MAX_VALUE,
(path, basicFileAttributes) -> basicFileAttributes.isRegularFile(),
FileVisitOption.FOLLOW_LINKS)) {
paths.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
...
./build/classes/java/main/org/pli/readbook/ReadBookApplication.class
./build/classes/java/main/org/pli/readbook/afunctionalapproachtojava/chapter05/R1/PersonRecord.class
./build/classes/java/main/org/pli/readbook/afunctionalapproachtojava/chapter05/R1/TupleExample.class
...
*/
파일 한 줄씩 읽기
public class ReadFileLineByLineExample {
public static void main(String[] args) {
Path path = Paths.get("src/main/java/org/pli/readbook/afunctionalapproachtojava/chapter07/c6/text.txt");
try (Stream<String> lines = Files.lines(path)) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
---
안녕
하세
요?
*/
사용시 주의 사항
- 스트림의 종료 필수
- 디렉터리 내용은 약한 일관성을 가지고 있음
- 요소의 순서는 보장되지 않음
스트림 닫기
- 파일 I/O 스트림은 명시적으로 닫아야 한다.
- 관리되지 않은 자원은 메모리 누수의 원인이 될 수 있으며, 가비지 컬렉터가 해당 메모리를 회수하지 않을 수 있다.
보장되지 않는 요소의 순서
- 스트림의 느긋한 특성으로 인해 I/O 스트림의 요소 순서에 알파벳순을 보장하지 않는다.
- 순서를 유지하기 위해서는 추가적인 정렬작업이 필요할 수 있다.
7.7 날짜와 시간 처리
시간 타입 질의
<R> R query(TemporalQuery<R> query);
public class TimeQueryExample {
public static void main(String[] args) {
LocalDate date = LocalDate.now();
TemporalQuery<Boolean> query = t -> t.isSupported(ChronoField.DAY_OF_MONTH);
boolean result = date.query(query);
System.out.println("ChronoField.DAY_OF_MONTH 지원 여부: " + result);
}
}
/*
ChronoField.DAY_OF_MONTH 지원 여부: true
*/
LocalDate 범위 스트림
Stream<LocalDate> datesUntil(LocalDate endExclusive, Period step);
public class LocalDateRangeStreamExample {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2024, 6, 14);
LocalDate end = LocalDate.of(2024, 6, 16);
Stream<LocalDate> dates = start.datesUntil(end, Period.ofDays(1));
dates.forEach(System.out::println);
}
}
/*
---
2024-06-14
2024-06-15
*/
7.8 JMH를 활용한 스트림 성능 측정
JMH는 JVM의 워밍업, 반복 및 코드 최적화를 처리하여 결과의 신뢰성을 높여주기 때문에 평가의 기준선이 된다.
7.9 컬렉터 알아보기
컬렉터의 핵심 기능은 다양한 요소를 새로운 자료 구조로 모으는 것.
다운스트림 컬렉터
다운 스트림의 컬렉터의 일반적인 작업은 변환, 축소, 평탄화, 필터링, 복합 컬렉터 연산이 있다.
요소 변환
Collectors.groupingBy
,Collectors.mapping
public class ElementTransformationExample {
public static void main(String[] args) {
Stream<String> names = Stream.of("하늘", "성현", "시윤", "준호");
Map<Integer, List<String>> groupedByLength =
names.collect(Collectors.groupingBy(String::length));
System.out.println(groupedByLength);
}
}
/*
---
{2=[하늘, 성현, 시윤, 준호]}
*/
요소 축소
Collectors.counting
,Collectors.summingInt
,Collectors.reducing
public class ElementReductionExample {
public static void main(String[] args) {
Stream<String> names = Stream.of("하늘", "성현", "시윤", "준호");
long count = names.count();
System.out.println("Count: " + count);
}
}
/*
---
Count: 4
*/
컬렉션 평탄화
Collectors.flatMapping
public class CollectionFlatteningExample {
public static void main(String[] args) {
Stream<List<String>> listOfLists = Stream.of(List.of("시윤", "준호"), List.of("성현", "하늘"));
List<String> flatList = listOfLists.flatMap(List::stream).collect(Collectors.toList());
System.out.println(flatList);
}
}
/*
---
[시윤, 준호, 성현, 하늘]
*/
요소 필터링
Collectors.filtering
public class ElementFilteringExample {
public static void main(String[] args) {
Stream<String> names = Stream.of("준호", "시윤", "하늘", "성현", "성공");
List<String> filteredNames =
names.filter(name -> name.startsWith("성")).collect(Collectors.toList());
System.out.println(filteredNames);
}
}
/*
---
[성현, 성공]
*/
합성 컬렉터
Collectors.teeing
Collectors.teeing 메서드는 세 개의 인수를 받습니다:
- 첫 번째 Collector - 스트림의 요소를 처리하는 첫 번째 컬렉터.
- 두 번째 Collector - 스트림의 요소를 처리하는 두 번째 컬렉터.
- BiFunction - 두 개의 컬렉터 결과를 결합하는 함수.
public class CompositeCollectorExample {
public static void main(String[] args) {
Stream<String> names = Stream.of("Haneul", "Sunghyun", "Siyoon", "Junho");
String result =
names.collect(
Collectors.teeing(
Collectors.counting(),
Collectors.mapping(String::toUpperCase, Collectors.joining(", ")),
(count, uppercased) -> "개수: " + count + ", 대문자로 변환된 이름: " + uppercased));
System.out.println(result);
}
}
/*
---
개수: 4, 대문자로 변환된 이름: HANEUL, SUNGHYUN, SIYOON, JUNHO
*/
7.10 스트림에 대한 고찰
- 모든 루프가 스트림으로 전환된 필요는 없다. 모든 스트림이 루프로 대체되어야 하는 것도 아니다.
'스터디 > 함수형 프로그래밍 with 자바' 카테고리의 다른 글
Chapter 08. 스트림을 활용한 병렬 데이터 처리 (0) | 2024.06.20 |
---|---|
Chapter 06. 스트림 (Stream) (1) | 2024.06.06 |
Chapter 05. 레코드 (Record) (0) | 2024.05.30 |
Chapter 04. 불변성 (0) | 2024.05.21 |
Chapter 03. JDK의 함수형 인터페이스 (1) | 2024.05.16 |