일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 자바 중급1편 - 날짜와 시간
- 람다
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch12
- 데이터 접근 기술
- 스프링 입문(무료)
- 자바의 정석 기초편 ch7
- @Aspect
- 자바의 정석 기초편 ch13
- 자바 중급2편 - 컬렉션 프레임워크
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch6
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch9
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch1
- 자바로 계산기 만들기
- 스프링 mvc1 - 스프링 mvc
- 스프링 트랜잭션
- 자바의 정석 기초편 ch2
- 스프링 mvc2 - 로그인 처리
- 자바로 키오스크 만들기
- 자바 고급2편 - io
- 자바의 정석 기초편 ch5
- 자바 기초
- 자바의 정석 기초편 ch11
- 2024 정보처리기사 시나공 필기
- 자바 고급2편 - 네트워크 프로그램
- 스프링 고급 - 스프링 aop
- Today
- Total
개발공부기록
Chapter 17 - 스트림 요소 처리1(Stream), 스트림이란?, 내부 반복자, 중간 처리와 최종 처리, 스트림 얻기, 요소 걸러내기(필터링), 요소 변환(매핑) 본문
Chapter 17 - 스트림 요소 처리1(Stream), 스트림이란?, 내부 반복자, 중간 처리와 최종 처리, 스트림 얻기, 요소 걸러내기(필터링), 요소 변환(매핑)
소소한나구리 2025. 3. 13. 14:12스트림이란?
기본적으로 컬렉션 및 배열에 저장된 요소를 반복 처리하기 위해서는 for문을 사용하거나 Iterator(반복자)를 이용하여 처리할 수 있었음
Java 8부터는 또 다른 방법이 추가 되었는데 그것이 바로 스트림임
스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있음
List 컬렉션에서 요소를 반복 처리하기 위해 스트림을 사용하면 아래와 같이 사용할 수 있음
Stream<String> stream = list.stream();
stream.forEach(item -> /* item 처리 */ );
List 컬렉션의 stream() 메소드로 Stream 객체를 얻고 forEach() 메소드로 요소를 어떻게 처리할지를 람다식으로 제공함
StreamExample
package ch17.sec01.exam01;
public class StreamExample {
public static void main(String[] args) {
// Set 컬렉션 생성
HashSet<String> set = new HashSet<>();
set.add("홍길동");
set.add("신용권");
set.add("감자바");
// Stream을 이용한 요소 반복 처리
Stream<String> stream = set.stream(); // 스트림 얻기
stream.forEach(name -> System.out.println(name));
}
}
/* 실행 결과
홍길동
신용권
감자바
*/
- set.stream()으로 스트림을 얻음
- stream.forEach()를 사용하여 요소를 반복하고 인자로 람다식을 활용함
Stream은 Iterator와 비슷한 반복자이지만 아래와 같은 차이점을 가지고 있음
- 내부 반복자이므로 처리 속도가 빠르고 병렬 처리에 효율적임
- 람다식으로 다양한 요소 처리를 정의할 수 있음
- 중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있음
내부 반복자
for문과 Iterator는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리하는데 이것을 외부 반복자라고 함
반면 스트림은 요소 처리 방법을 컬렉션 내부로 주입시켜서 요소를 반복 처리하는데 이것을 내부 반복자라고 함
외부 반복자일 경우는 컬렉션의 요소를 외부로 가져오는 코드와 처리하는 코드를 모두 개발자 코드가 가지고 있어야 함
반면 내부 반복자일 경우에는 개발자 코드에서 제공한 데이터 처리 코드(람다식)를 가지고 컬렉션 내부에서 요소를 반복 처리함
내부 반복자는 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업 할 수 있어 하나씩 처리하는 순차적 외부 반복자보다 효율적으로 요소를 반복시킬 수 있음
ParallelStreamExample
List 컬렉션의 내부 반복자를 이용하여 병렬 처리하는 예제
package ch17.sec01.exam01;
public class ParallelStreamExample {
public static void main(String[] args) {
// List 생성
List<String> list = new ArrayList<>();
list.add("홍길동");
list.add("박병렬");
list.add("감자바");
list.add("람다식");
list.add("스트림");
// 병렬 처리
Stream<String> parallelStream = list.parallelStream();
parallelStream.forEach(name -> {
System.out.println(name + ": " + Thread.currentThread().getName());
});
}
}
/* 실행결과
람다식: ForkJoinPool.commonPool-worker-4
스트림: ForkJoinPool.commonPool-worker-3
감자바: main
홍길동: ForkJoinPool.commonPool-worker-2
박병렬: ForkJoinPool.commonPool-worker-1
*/
- parallelStream() 메서드로 병렬 처리 스트림을 얻고 forEach() 메서드를 호출할 때 람다식을 제공하면 병렬로 처리되는 스트림을 얻을 수 있음
- 병렬로 처리되기 때문에 실행할 때마다 결과가 다르게 출력됨
중간처리와 최종 처리
스트림은 하나 이상 연결될 수 있는데 아래의 그림을 보면 컬렉션의 오리지널 스트림 뒤에 필터링 중간 스트림이 연결될 수 있고 그 뒤에 매핑 중간 스트림이 연결될 수 있음
이렇게 스트림이 연결되어 있는 것을 파이프라인(pipelines)이라고 함
오리지널 스트림과 집계 처리 사이의 중간 스트림들은 최종 처리를 위해 요소를 걸러내거나(필터링) 요소를 변환시키거나(매핑), 정렬하는 작업을 수행함
최종 처리는 중간 처리에서 정제된 요소들을 반복하거나 집계(카운팅, 총합, 평균) 작업을 수행함
아래의 그림과 코드는 Student 객체를 요소로 가지는 컬렉션에서 Student 스트림을 얻고 중간 처리를 통해 score 스트림으로 변환한 후 최종 집계 처리로 score 평균을 구하는 과정을 그림과 코드로 나타낸 것이다
// Student 스트림
Stream<Student> studentStream = list.stream();
// score 스트림, student 객체를 getScore() 메소드의 리턴값으로 매핑
IntStream scoreStream = studentStream.mapToInt(student -> student.getScore());
// 평균 계산
double avg = scoreStream.average().getAsDouble();
mapToInt()메서드는 객체를 int 값으로 매핑해서 IntStream으로 변환시키는데 어떤 객체를 어떤 int 값으로 매핑할 것인지는 람다식으로 제공해야 함
student -> student.getScore()는 Student 객체를 getScore()의 리턴값으로 매핑하며 IntStream은 최종 처리를 위해 다양한 메서드를 제공하는데 average() 메서드는 요소들의 평균 값을 계산함
double avg = list.stream()
.mapToint(student -> student.getScore())
.average()
.getAsDouble();
메서드 체이닝 패턴을 이용하면 훨씬 더 간결하게 코드를 작성할 수 있으며 스트림 파이프라인으로 구성할 때 주의점은 파이프라인의 맨 끝에는 반드시 최종 처리 부분이 있어야 한다는 것임
최종 처리가 없다면 오리지널 및 중간 처리 스트림은 동작하지 않게 됨
즉, 위 코드에서 average() 이후의 코드를 생략하면 stream(), mapToInt()는 동작하지 않음
Student
package ch17.sec01.exam03;
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
}
StreamPipeLineExample
package ch17.sec01.exam03;
public class StreamPipeLineExample {
public static void main(String[] args) {
List<Student> list1 = Arrays.asList(
new Student("홍길동", 10),
new Student("신용권", 20),
new Student("스트림", 30)
);
// 방법 1
Stream<Student> stream = list1.stream();
IntStream scoreStream = stream.mapToInt(student -> student.getScore());
double avg = scoreStream.average().getAsDouble();
System.out.println("list1 평균 점수 = " + avg);
List<Student> list2 = Arrays.asList(
new Student("람다식", 50),
new Student("스트림", 70),
new Student("강제비", 30)
);
double avg2 = list2.stream()
.mapToInt(Student::getScore)
.average()
.getAsDouble();
System.out.println("list2 평균 점수 = " + avg2);
}
}
/* 실행 결과
list1 평균 점수 = 20.0
list2 평균 점수 = 50.0
*/
- 메서드 체이닝을 활용하면 스트림의 코드가 매우 간결해져 편리하게 각 요소의 값을 처리할 수 있음
리소스로부터 스트림 얻기
java.util.stream 패키지에는 스트림 인터페이스 들이 있는데 BaseStream 인터페이스가 부모인 자식 인터페이스들은 아래 처럼 상속 관계를 이루고 있음
BaseStream에는 모든 스트림에서 사용할 수 있는 공통 메서드들이 정의 되어 있음
Stream은 객체 요소를 처리하는 스트림이고 IntStream, LongStream, DoubleStream은 각각 기본 타입인 int, long, double 요소를 처리하는 스트림임
이 스트림 인터페이스들의 구현 객체는 다양한 리소스로부터 얻을 수 있는데 주로 컬렉션과 배열에서 얻지만 아래와 같은 리소스로부터 스트림 규현 객체를 얻을 수도 있음
리턴 타입 | 메소드(매개변수) | 리소스 |
Stream<T> | java.util.Collection.stream(), java.util.Collection.parallelStream() | List, Set |
Stream<T> IntStream LongStream DoubleStream |
Arrays.stream(T[]), Arrays.stream(int[]), Arrays.stream(long[]), Arrays.stream(double[]) Stream.of(T[]), IntStream.of(int[]), LongStream.of(long[]), 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 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있음
Product
package ch17.sec04;
public class Product {
private int pno;
private String name;
private String company;
private int price;
// 생성자, 게터, 세터
@Override
public String toString() {
return new StringBuilder()
.append("{").append("pno:").append(pno).append(", ")
.append("name:").append(name).append(", ")
.append("company:").append(company).append(", ")
.append("price:").append(price)
.append("}")
.toString();
}
}
StreamExample
public class StreamExample {
public static void main(String[] args) {
// List 컬렉션 생성
List<Product> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Product product = new Product(i, "상품" + i, "회사" + i, (int) (10000 * Math.random()));
list.add(product);
}
// 객체 스트림 얻기
Stream<Product> stream = list.stream();
stream.forEach(p -> System.out.println(p));
}
}
/* 실행 결과
{pno:0, name:상품0, company:회사0, price:1844}
{pno:1, name:상품1, company:회사1, price:1372}
{pno:2, name:상품2, company:회사2, price:9327}
{pno:3, name:상품3, company:회사3, price:5195}
{pno:4, name:상품4, company:회사4, price:7480}
*/
배열로 부터 스트림 얻기
- 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();
}
}
/* 실행 결과
홍길동,홍기롱,홍길옹,
1,2,3,4,5,
*/
숫자 범위로부터 스트림 얻기
- IntStream 또는 LongStream의 정적 메서드인 range()와 rangeClose() 메서드를 이용하면 특정 범위의 정수 스트림을 얻을 수 있음
- 첫 번째 매개값은 시작 수 이고 두 번째 매개값은 끝 수인데 끝 수를 포함하지 않으면 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);
}
}
/* 실행 결과
총합: 5050
*/
파일로부터 스트림 얻기
- java.nio.file.Files의 lines() 메서드를 이용하면 텍스트 파일의 행 단위 스트림을 얻을 수 있음
- 이 메서드는 텍스트 파일에서 한 행씩 읽고 처리할 때 유용하게 사용할 수 있음
data.txt
{pno:0, name:상품0, company:회사0, price:379}
{pno:1, name:상품1, company:회사1, price:9631}
{pno:2, name:상품2, company:회사2, price:599}
{pno:3, name:상품3, company:회사3, price:1937}
{pno:4, name:상품4, company:회사4, price:2000}
StreamExample
public class StreamExample {
public static void main(String[] args) throws URISyntaxException, IOException {
Path path = Paths.get(StreamExample.class.getResource("data.txt").toURI());
Stream<String> stream = Files.lines(path, Charset.defaultCharset());
stream.forEach(line -> System.out.println(line));
stream.close();
}
}
/* 실행 결과
{pno:0, name:상품0, company:회사0, price:379}
{pno:1, name:상품1, company:회사1, price:9631}
{pno:2, name:상품2, company:회사2, price:599}
{pno:3, name:상품3, company:회사3, price:1937}
{pno:4, name:상품4, company:회사4, price:2000}
*/
요소 걸러내기(필터링)
필터링은 요소를 걸러내는 중간 처리 기능이며 필터링 메서드는 distinct(), filter()가 있음
리턴 타입 | 메소드(매개변수) | 설명 |
Stream IntStream LongStream DoubleStream |
distinct() | 중복 제거 |
filter(Predicate<T>) filter(IntPredicate) filter(LongPredicate) filter(DoublePredicate) |
- 조건 필터링 - 매개 타입은 요소 타입에 따른 함수영 인터페이스이므로 람다식으로 작성 가능 |
distinct() 메서드는 요소의 중복을 제거함
객체 스트림(Stream)일 경우 equals() 메서드의 리턴값이 true이면 동일한 요소로 판단하며 IntStream, LongStream, DoubleStream은 같은 값일 경우 중복을 제거함
filter() 메서드는 매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링함
Predicate는 함수형 인터페이스로 아래와 같은 종류가 있으며 모든 Predicate는 매개값을 조사한 후 boolean을 리턴하는 test() 메서드를 가지고 있음
인터페이스 | 추상 메서드 | 설명 |
Predicate<T> | boolean test(T t) | 객체 T를 조사 |
IntPredicate | boolean test(int value) | int 값을 조사 |
LongPredicate | boolean test(long value) | long 값을 조사 |
DoublePredicate | boolean test(double value) | double 값을 조사 |
FilteringExample
- 이름 List에서 중복된 이름을 제거하고 출력한 후 이어서 성이 '신'인 이름만 필터링해서 출력하는 예제
public class FilteringExample {
public static void main(String[] args) {
// List 컬렉션 생성
List<String> list = new ArrayList<>();
list.add("홍길동");
list.add("신제비");
list.add("감자칩");
list.add("신제비");
list.add("신라면");
list.add("신제비");
// 중복 요소 제거
list.stream()
.distinct()
.forEach(n -> System.out.println(n));
System.out.println();
// 신으로 시작하는 요소만 필터링
list.stream()
.filter(n -> n.startsWith("신"))
.forEach(n -> System.out.println(n));
System.out.println();
// 중복 요소를 먼저 제거하고, 신으로 시작하는 요소만 필터링
list.stream()
.distinct()
.filter(n -> n.startsWith("신"))
.forEach(n -> System.out.println(n));
}
}
/* 실행 결과
홍길동
신제비
감자칩
신라면
신제비
신제비
신라면
신제비
신제비
신라면
*/
요소 변환(매핑)
매핑(mapping)은 스트림의 요소를 다른 요소로 변환하는 중간 처리 기능임
매핑 메서드는 mapXxx(), asDoubleStream(), asLongStream(), boxed(), flatMapXxx() 등이 있음
요소를 다른 요소로 변환
mapXxx() 메서드는 요소를 다른 요소로 변환한 새로운 스트림을 리턴함
다음 그림처럼 원래 스트림의 A 요소는 C 요소로, B 요소는 D 요소로 변환하여 C, D 요소를 가지는 새로운 스트림이 생성됨
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 IntStream LongStream |
mapToDouble(IntToDoubleFunction) | int -> double |
mapToDouble(LongToDoubleFunction) | long -> double | |
mapToInt(DoubleToIntFunction) | double -> int | |
mapToLong(DoubleToLongFunction) | double -> long |
매개 타입인 Function은 함수형 인터페이스로 아래의 종류가 있음
인터페이스 | 추상 메서드 | 매개값 -> 리턴값 |
Function<T, R> | R apply(T t) | T -> R |
IntFunction<R> | R apply(int value) | int -> R |
LongFunction<R> | R aply(long value) | long -> R |
DoubleFuntion<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 |
Student
public class Student {
private String name;
private int score;
// 생성자, 게터
}
MapExample
- Student 스트림을 score 스트림으로 변환하고 점수를 콘솔에 출력
public class MapExample {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("홍삼두", 69));
studentList.add(new Student("김석칠", 94));
studentList.add(new Student("이두박", 72));
// Student를 score 스트림으로 변환
studentList.stream()
.mapToInt(s -> s.getScore())
.forEach(score -> System.out.println(score));
}
}
/* 실행 결과
69
94
72
*/
기본 타입간의 변환이거나 기본 타입 요소를 래퍼 객체 요소로 변환하려면 다음과 같은 간편화 메서드를 사용할 수 있음
리턴 타입 | 메서드(매개변수) | 요소 -> 변환 요소 |
LongStream | asLongStream() | int -> long |
DoubleStream | asDoubleStream() | int -> double long -> double |
Stream<Integer> Stream<Long> Stream<Double> |
boxed() | int -> Integer long -> Long double -> Double |
MapExample
- 정수 스트림을 실수 스트림으로 변환하고 기본 타입 스트림을 래퍼 스트림으로 변환
package ch17.sec06.exam02;
public class MapExample {
public static void main(String[] args) {
int[] intArray = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray);
intStream
.asDoubleStream()
.forEach(d -> System.out.println(d));
System.out.println();
intStream = Arrays.stream(intArray);
intStream
.boxed()
.forEach(obj -> System.out.println(obj.intValue()));
}
}
/* 실행 결과
1.0
2.0
3.0
4.0
5.0
1
2
3
4
5
*/
요소를 복수 개의 요소로 변환
flatMapXxx() 메서드는 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림을 리턴함
아래의 그림처럼 원래 스트림의 A 요소를 A1, A2 요소로 변환하고 B 요소를 B1, B2로 변환하면 A1, A2, B1, B2 요소를 가지는 새로운 스트림이 생성됨
filatMap() 메서드 종류
리턴 타입 | 메서드(매개변수) | 요소 -> 변환 요소 |
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 |
FlatMappingExample
public class FlatMappingExample {
public static void main(String[] args) {
// 문장 스트림을 단어 스트림으로 변환
List<String> list1 = new ArrayList<>();
list1.add("이것이 자바냐?");
list1.add("나는 개발돌이야");
list1.stream()
// String 배열을 Arrays.stream으로 Stream<String>으로 만듦
.flatMap(data -> Arrays.stream(data.split(" ")))
.forEach(word -> System.out.println(word));
// 문자열 숫자 목록 스트림을 숫자 스트림으로 변환
List<String> list2 = Arrays.asList("10, 20, 30", "40, 50");
list2.stream()
.flatMapToInt(data -> {
// String[] 배열을 int[] 배열로 만듦
String[] split = data.split(",");
int[] intArr = new int[split.length];
for (int i = 0; i < split.length; i++) {
intArr[i] = Integer.parseInt(split[i].trim());
}
// Arrays.Stream()으로 int[] 배열을 intStream으로 만듦
return Arrays.stream(intArr);
})
.forEach(number -> System.out.println(number));
}
}
/* 실행 결과
이것이
자바냐?
나는
개발돌이야
10
20
30
40
50
*/
'기타 개발 강의 및 책 정리 > 이것이자바다 개정판' 카테고리의 다른 글
Chapter 17 - 스트림 요소 처리2(Stream), 요소 정렬, 요소를 하나씩 처리(루핑), 요소 조건 만족 여부(매칭), 요소 기본 집계, 요소 커스텀 집계, 요소 수집, 요소 병렬 처리 (0) | 2025.03.14 |
---|---|
Chapter 16 - 람다식 (0) | 2025.03.10 |
Chapter19 - 네트워크 입출력, IP 주소 얻기, TCP 네트워킹, UDP 네트워킹 (0) | 2025.02.11 |
chapter4 - 조건문과 반복문 확인문제, 추가내용 / 이것이 자바다(개정판) (0) | 2024.02.13 |
chapter3 - 연산자 확인문제, / 이것이 자바다(개정판) (0) | 2024.02.12 |