일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 오픈챌린지
- 백준
- 부트캠프
- 백엔드
- 티스토리챌린지
- 내일배움캠프
- 객체지향
- UXUI기초정복
- baekjoon
- Java
- mysql
- OPENPATH
- 디자인교육
- UXUI챌린지
- UXUIPrimary
- 국비지원
- 패스트캠퍼스
- Be
- 백엔드 부트캠프
- 백엔드개발자
- 오블완
- 국비지원취업
- 디자인챌린지
- 국비지원교육
- 내일배움카드
- KDT
- 환급챌린지
- Spring
- 오픈패스
- 디자인강의
- Today
- Total
군만두의 IT 공부 일지
[스터디4] 08. 스트림 요소 처리 본문
목차
17장. 스트림 요소 처리
17.1 스트림이란?
컬렉션 및 배열에 저장된 요소를 반복 처리하기 위해서는 for 문을 이용하거나 Iterator(반복자)를 이용했음.
List<String> list = ...;
for(int i=0; i<list.size(); i++) {
String item = list.get(i);
// item 처리
}
Set<String> set = ...;
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()) {
String item = iterator.next();
// 요소 처리
}
Java 8부터는 컬렉션 및 배열의 요소를 반복 처리하기 위해 스트림(Stream)을 사용할 수 있다.
Stream<String> stream = list.stream();
Stream.forEach( item -> //item 처리
);
List 컬렉션의 stream() 메소드로 Stream 객체를 얻고, forEach() 메소드로 요소를 어떻게 처리할지를 람다식으로 제공한다.
Stream과 Iterator의 차이점
- 내부 반복자이므로 처리 속도가 빠르고 병렬 처리에 효율적이다.
- 람다식으로 다양한 요소 처리를 정의할 수 있다.
- 중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.
17.2 내부 반복자
- 외부 반복자: for 문과 Iterator는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리한다.
- 컬렉션의 요소를 외부로 가져오는 코드와 처리하는 코드를 모두 개발자 코드가 가지고 있어야 한다.
- 내부 반복자: 스트림은 요소 처리 방법을 컬렉션 내부로 주입시켜서 요소를 반복 처리한다.
- 개발자 코드에서 제공한 데이터 처리 코드(람다식)를 가지고 컬렉션 내부에서 요소를 반복 처리한다.
- 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있어 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있다.
17.3 중간 처리와 최종 처리
- 스트림 파이프라인(pipelines)
- 스트림은 하나 이상 연결될 수 있다.
- 컬렉션의 오리지널 스트림 뒤에 필터링 중간 스트림이 연결될 수 있고, 그 뒤에 매핑 중간 스트림이 연결될 수 있다.
오리지널 스트림과 집계 처리 사이의 중간 스트림들은 최종 처리를 위해 요소를 걸러내거나(필터링), 요소를 변환시키거나(매핑), 정렬하는 작업을 수행한다. 최종 처리는 중간 처리에서 정제된 요소들을 반복하거나, 집계(카운팅, 총합, 평균) 작업을 수행한다.
// Student 스트림
Stream<Student> studentStream = list.stream();
// score 스트림
IntStream scoreStream = studentStream.mapToInt(student -> student.getScore()); // Student 객체를 getScore() 메소드의 리턴값으로 매핑
// 평균 계산
double avg = scoreStream.average().getAsDouble();
// 메소드 체이닝 패턴 이용하여 위 코드 간결하게 작성
double avg = list.stream()
.mapToInt(student -> student.getScore())
.average()
.getAsDouble();
- 스트림 파이프라인으로 구성할 때 주의할 점
- 파이프라인의 맨 끝에는 반드시 최종 처리 부분이 있어야 한다. 최종 처리가 없으면 오리지널 및 중간 처리 스트림은 동작하지 않는다.
17.4 리소스로부터 스트림 얻기
java.util.stream 패키지에는 다양한 스트림 인터페이스가 있다.
- Stream: 객체 요소를 처리하는 스트림
- IntStream, LongStream, DoubleStream: 각각 기본 타입인 int, long, double 요소를 처리하는 스트림
▲ BaseStream 인터페이스를 부모로 한 자식 인터페이스의 상속 관계
리턴 타입 | 메소드(매개변수) | 리소스 |
Stream<T> | java.util.Collection.stream() java.util.Collection.parallelStream() |
List 컬렉션 Set 컬렉션 |
Stream<T> IntStream LongStream DoubleStream |
Arrays.stream(T[]), Stream.of(T[]) Arrays.stream(int[]), IntStream.of(int[]) Arrays.stream(long[]), LongStream.of(long[]) Arrays.stream(double[]), DoubleStream.of(double[]) |
배열 |
IntStream | IntStream.range(int, int) IntStream.rangeClosed(int, int) |
int 범위 |
LongStream | LongStream.range(long, long) LongStream.rangeClosed(long, long) |
long 범위 |
Stream<Path> | Files.list(Path) | 디렉토리 |
Stream<String> | Files.lines(Path, Charset) | 텍스트 파일 |
DoubleStream IntStream LongStream |
Random.doubles(...) Random.ints() Random.longs() |
랜덤 수 |
컬렉션으로부터 스트림 얻기
java.util.Collection 인터페이스는 스트림과 parallelStream() 메소드를 가지고 있기 때문에 자식 인터페이스인 List와 Set 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.
public class StreamExample {
public static void main(String[] args) {
// List 컬렉션 생성
List<Product> list = new ArrayList<>();
for(int i = 1; i <= 5; i++) {
Product product = new Product("상품" + i, "멋진 회사", (int)(10000 * Math.random()));
list.add(product);
}
// 객체 스트림 얻기
Stream<Product> stream = list.stream();
stream.forEach(p -> System.out.println(p));
}
}
배열로부터 스트림 얻기
java.util.Arrays 클래스를 이용하면 다양한 종류의 배열로부터 스트림을 얻을 수 있다.
public class StreamExample {
public static void main(String[] args) {
String[] strArray = { "홍길동", "신용권", "김미나" };
Stream<String> strStream = Arrays.stream(strArray);
strStream.forEach(item -> System.out.print(item + ","));
System.out.println();
int[] intArray = { 1, 2, 3, 4, 5 };
IntStream intStream = Arrays.stream(intArray);
intStream.forEach(item -> System.out.print(item + ","));
System.out.println();
}
}
숫자 범위로부터 스트림 얻기
IntStream 또는 LongStream의 정적 메소드인 range()와 rangeClosed() 메소드를 이용하면 특정 범위의 정수 스트림을 얻을 수 있다.
- 첫 번째 매개값: 시작 수
- 두 번째 매개값: 끝 수
- 끝 수를 포함하지 않으면 range(), 포함하면 rangeClosed() 사용
public class StreamExample {
public static int sum;
public static void main(String[] args) {
IntStream stream = IntStream.rangeClosed(1, 100);
stream.forEach(a -> sum += a);
System.out.println("총합: " + sum);
}
}
파일로부터 스트림 얻기
java.nio.file.Files의 lines() 메소드를 이용하면 텍스트 파일의 행 단위 스트림을 얻을 수 있다. data.txt 파일을 한 행씩 읽고 상품 정보를 출력하기 위해 Files의 lines() 메소드를 이용한다.
public class StreamExample {
public static void main(String[] args) throws Exception {
Path path = Paths.get(StreamExample.class.getResource("data.txt").toURI()); // data.txt 파일의 경로(Path) 객체 얻기
Stream<String> stream = Files.lines(path, Charset.defaultCharset()); // Path로부터 파일을 열고 한 행씩 읽으면서 문자열 스트림 생성, 기본 UTF-8 문자셋으로 읽음
stream.forEach(line -> System.out.println(line));
stream.close();
}
}
17.5 요소 걸러내기(필터링)
- 필터링: 요소를 걸러내는 중간 처리 기능
- distinct() 메소드: 요소의 중복 제거한다.
- equals() 메소드: 객체 스트림(Stream)일 경우 리턴값이 true이면 동일한 요소로 판단한다. IntStream, LongStream, DoubleStream은 같은 값일 경우 중복을 제거한다.
- filter() 메소드: 매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링한다.
리턴 타입 | 메소드(매개변수) | 설명 |
Stream IntStream LongStream DoubleStream |
distinct() | - 중복 제거 |
filter(Predicate<T>) filter(IntPredicate) filter(LongPredicate) filter(DoublePredicate) |
- 조건 필터링 - 매개 타입은 요소 타입에 따른 함수형 인터페이스이므로 람다식으로 작성 가능 |
▲ 필터링 메소드의 종류
인터페이스 | 추상 메소드 | 설명 |
Predicate<T> | boolean test(T t) | 객체 T를 조사 |
IntPredicate | boolean test(int value) | int 값을 조사 |
LongPredicate | boolean test(long value) | long 값을 조사 |
DoublePredicate | boolean test(double value) |
▲ Predicate의 종류
Predicate<T>를 람다식으로 표현
T -> { ... return true }
또는
T -> true; // return 문만 있을 경우 중괄호와 return 키워드 생략 가능
17.6 요소 변환(매핑)
- 매핑(mapping): 스트림의 요소를 다른 요소로 변환하는 중간 처리 기능
- 매핑 메소드: mapXxx(), asDoubleStream(), asLongStream(), boxed(), flatMapXxx() 등
요소를 다른 요소로 변환
- mapXxx() 메소드: 요소를 다른 요소로 변환한 새로운 스트림을 리턴한다.
리턴 타입 | 메소드(매개변수) | 요소 → 변환 요소 |
Stream<R> | map(Function<T, R>) | T → R |
IntStream LongStream DoubleStream |
mapToInt(ToIntFunction<T>) | T → int |
mapToLong(ToLongFunction<T>) | T → long | |
mapToDouble(ToDoubleFunction<T>) | T → double | |
Stream<U> | mapToObj(IntFunction<U>) | int → U |
mapToObj(LongFunction<U>) | long → U | |
mapToObj(DoubleFunction<U>) | double → U | |
DoubleStream DoubleStream IntStream LongStream |
mapToDouble(IntToDoubleFunction) | int → double |
mapToDouble(LongToDoubleFunction) | long → double | |
mapToInt(DoubleToIntFunction) | double → int | |
mapToLong(DoubleToLongFunction) | double → long |
▲ mapXxx() 메소드의 종류
인터페이스 | 추상 메소드 | 매개값 → 리턴값 |
Function<T, R> | R apply(T t) | T → R |
IntFunction<R> | R apply(int value) | int → R |
LongFunction<R> | R apply(long value) | long → R |
DoubleFunction<R> | R apply(double value) | double → R |
ToIntFunction<T> | int applyAsInt(T value) | T → int |
ToLongFunction<T> | long applyAsLong(T value) | T → long |
ToDoubleFunction<T> | double applyAsDouble(T value) | T → double |
IntToLongFunction | long applyAsLong(int value) | int → long |
IntToDoubleFunction | double applyAsDouble(int value) | int → double |
LongToIntFunction | int applyAsInt(long value) | long → int |
LongToDoubleFunction | double applyAsDouble(long value) | long → double |
DoubleToIntFunction | int applyAsInt(double value) | double → int |
DoubleToLongFunction | long applyAsLong(double value) | double → long |
▲ Function의 종류
모든 Function은 매개값을 리턴값으로 매핑(변환)하는 applyXxx() 메소드를 가지고 있다. Function<T,R>을 람다식으로 표현하면 다음과 같다.
T -> { ... return R; }
또는
T -> r; // return 문만 있을 경우 중괄호와 return 키워드 생략 가능
기본 타입 간의 변환이거나 기본 타입 요소를 래퍼(Wrapper) 객체 요소로 변환하려면 다음과 같은 간편화 메소드를 사용할 수도 있다.
리턴 타입 | 메소드(매개변수) | 설명 |
LongStream | asLongStream() | int → long |
DoubleStream | asDoubleStream() | int → double long → double |
Stream<Integer> Stream<Long> Stream<Double> |
boxed() | int → Integer long → Long double → Double |
요소를 복수 개의 요소로 변환
- flatMapXxx() 메소드: 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림을 리턴한다.
리턴 타입 | 메소드(매개변수) | 요소 → 변환 요소 |
Stream<R> | flatMap(Function<T, stream<R>>) | T → Stream<R> |
DoubleStream | flatMap(DoubleFunction<DoubleStream>) | double → DoubleStream |
IntStream | flatMap(IntFunction<IntStream>) | int → IntStream |
LongStream | flatMap(LongFunction<LongStream>) | long → LongStream |
DoubleStream | flatMapToDouble(Function<T, Doublestream>) | T → DoubleStream |
IntStream | flatMapToInt(Function<T, Intstream>) | T → IntStream |
LongStream | flatMapToLong(Function<T, LongStream>) | T → LongStream |
▲ flatMap() 메소드의 종류
17.7 요소 정렬
- 정렬: 요소를 오름차순 또는 내림차순으로 정렬하는 중간 처리 기능
리턴 타입 | 메소드(매개변수) | 설명 |
Stream<T> | sorted() | Comparable 요소를 정렬한 새로운 스트림 생성 |
Stream | sorted(Comparator<T>) | 요소를 Comparator에 따라 정렬한 새 스트림 생성 |
DoubleStream | sorted() | double 요소를 오름차순으로 정렬 |
IntStream | sorted() | int 요소를 오름차순으로 정렬 |
LongStream | sorted() | long 요소를 오름차순으로 정렬 |
▲ 요소를 정렬하는 메소드
Comparable 구현 객체의 정렬
스트림의 요소가 객체일 경우 객체가 Comparable을 구현하고 있어야 sorted() 메소드를 사용할 수 있다. 그렇지 않다면 ClassCastException이 발생한다.
public class Xxx implements Comparable {
...
}
List<Xxx> list = new ArrayList<>();
Stream<Xxx> stream = list.stream();
Stream<Xxx> orderedStream = stream.sorted();
Comparator를 이용한 정렬
요소 객체가 Comparable을 구현하고 있지 않으면, 비교자를 제공하여 요소를 정렬시킬 수 있다.
- 비교자: Comparator 인터페이스를 구현한 객체
sorted((o1, o2) -> { ... })
이 글은 『이것이 자바다』 책을 학습한 내용을 정리한 것입니다.
'프로그래밍 > Java' 카테고리의 다른 글
[스터디4] 09. 자바 21에서 강화된 언어 및 라이브러리 (0) | 2025.04.02 |
---|---|
[스터디4] 07. 멀티 스레드 (0) | 2025.03.11 |
[스터디4] 06. 제네릭 (0) | 2025.03.02 |
[스터디4] 05. 중첩 선언과 익명 객체 (0) | 2025.02.25 |
[스터디4] 04. 인터페이스 (1) | 2025.02.16 |