스트림
- 스트림(Stream)이란 데이터의 흐름을 가리킨다.
- 컬렉션 인스턴스나 배열에 저장된 데이터들을 꺼내서하는 일련의 작업들을 조금 더 쉽게 하기 위해 스트림을 사용한다.
- 스트림은 원본 데이터를 변경하지 않는다.
동작 흐름
스트림은 크게 세단계에 걸쳐서 동작한다.
- 스트림의 생성
- 스트림의 중간 연산
- 스트림의 최종 연산
스트림을 생성하고 이를 대상으로 중간 연산을 진행하면, 원하는 기준으로 데이터를 필터링하고 필터링 된 데이터의 결과를 얻을 수 있다.
스트림의 생성은 저장소에 저장된 데이터를 뽑아 파이프에 흘려보내기 좋은 구조로 나열한 인스턴스의 생성을 의미한다.
데이터를 흘려보내는 파이프는 연산을 의미하며, 메소드(파이프)의 종류는 두가지로 나뉜다.
- 중간 연산 : 마지막이 아닌 위치에서 진행이 되어야하는 연산이다.
중간 연산끼리는 순서를 바꿀 수 있다. - 최종 연산 : 마지막에 진행이 되어야 하는 연산이다.
이때 중간 연산은 생략 가능하지만, 최종 연산이 생략되면 아무런 결과를 얻지 못한다.
class Stream_Exam1 {
public static void main(String[] args) {
int[] ar = { 1, 2, 3, 4, 5 };
IntStream stm1 = Arrays.stream(ar); // 배열 ar로부터 스트림 생성
IntStream stm2 = stm1.filter(n -> n % 2 == 1); // 중간연산 진행
int sum = stm2.sum(); // 최종연산 진행
System.out.println(sum); // 9
}
}
다음과 같이 간단하게 작성해 줄 수도 있다.
public static void main(String[] args) {
int[] ar = { 1, 2, 3, 4, 5 };
int sum = Arrays.stream(ar) // 스트림 생성
.filter(n -> n % 2 == 1) // filter 통과
.sum(); // sum 통과 결과 반환
System.out.println(sum); // 9
}
스트림 생성
배열 기반 - stream
// 배열 전체를 대상으로 스트림 생성
static <T> Stream<T> stream(T[] array)
// 배열의 일부(시작 인덱스부터 종료 인덱스 이전까지)를 대상으로 스트림 생성
static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)
// 기본타입 저장을 위한 스트림 메소드 별도 정의
static IntStream stream(int[] array)
static IntStream stream(int[] array, int startInclusive, int endExclusive)
static DoubleStream stream(double[] array)
static DoubleStream stream(double[] array, int startInclusive, int endExclusive)
static LongStream stream(double[] array)
static LongStream stream(double[] array, int startInclusive, int endExclusive)
더보기
class Stream_Arrays {
public static void main(String[] args) {
String[] names = {"앤", "욱", "준", "졍"};
// 배열 전체을 이용한 스트림 생성
Stream<String> stm1 = Arrays.stream(names);
stm1.forEach(s -> System.out.print(s + " ")); // 최종 연산, 스트림 요소의 순차적 접근
System.out.println(); // 앤 욱 준 졍
// 배열의 특정 부분만을 이용한 스트림 생성
Stream<String> stm2 = Arrays.stream(names, 1, 3);
stm2.forEach(s -> System.out.print(s + " ")); // 최종 연산, 스트림 요소의 순차적 접근
System.out.println(); // 욱 준
}
}
다음과 같이 간단하게 작성해줄 수도 있다.
String[] names = {"앤", "욱", "준", "졍"};
Arrays.stream(names)
.forEach(s -> System.out.print(s + " ")); // 앤 욱 준 졍
컬렉션 인스턴스 기반 - stream
- java.util.Collection<E> 인터페이스의 디폴트 메소드로 stream 메소드가 정의되어 있다.
- 따라서 대부분의 컬렉션 인스턴스를 대상으로 stream 메소드를 호출해 스트림을 생성할 수 있다.
default Stream<E> stream()
더보기
class Stream_List {
public static void main(String[] args) {
List<String> list = Arrays.asList("Red", "Black", "Blue");
list.stream() // 컬렉션 인스턴스 기반 스트림 생성
.forEach(s -> System.out.print(s + " ")); // 스트림 요소의 순차적 접근
System.out.println(); // Red Black Blue
}
}
가변매개변수 기반 - of
- Stream<T> 인터페이스의 of 메소드를 사용하면 가변 매개변수(variable prameter)를 전달받아 스트림을 생성할 수 있다.
- Stream<T>의 T에는 기본 자료형이 올 수 없기 때문에 기본 자료형을 대상으로 한 of 메소드가 별도로 정의되어 있다.
- 이러한 메소드를 통해 기본 자료형을 대상으로 스트림을 생성하면 불필요한 오토박싱과 오토 언박싱을 피할 수 있다.
static <T> Stream<T> of(T t)
static <T> Strean<T> of(T... values)
static IntStream of(int... values) // IntStream의 메소드
static LongStream of(long... values) // LongStream의 메소드
static DoubleStream of(double... values) // DoubleStream의 메소드
더보기
class Stream_Of {
public static void main(String[] args) {
// ex1
Stream.of(11, 22, 33, 44, 55)
.forEach(s -> System.out.print(s + " "));
System.out.println(); // 11, 22, 33, 44, 55
// ex2
Stream.of("This is String")
.forEach(System.out::println); // This is String
// ex3 - 하나의 컬렉션 인스턴스로 이뤄진 스트림 생성
List<String> sl = Arrays.asList("Red", "Black", "Blue");
Stream.of(sl)
.forEach(System.out::println); // [Red, Black, Blue]
}
}
of 메소드 매개변수로 컬렉션 인스턴스를 전달하면 해당 인스턴스 하나로 이뤄진 스트림이 생성된다.
지정된 범위의 연속된 정수 - range
- IntStream, LongStream 인터페이스의 range, reangeClosed 메소드를 사용해 지정된 범위의 연속된 정수를 스트림으로 생성할 수 있다.
static IntStream range(int startInclusive, int endExclusive) // 시작값 이상 종료값 미만
static LongStream range(long startInclusive, long endExclusive) // 시작값 이상 종료값 미만
static IntStream rangeClosed(int startInclusive, int endInclusive) // 시작값 이상 종료값 이하
static LongStream rangeClosed(long startInclusive, long InExclusive) // 시작값 이상 종료값 이하
더보기
class Stream_Range {
public static void main(String[] args) {
IntStream.range(1, 5) // 1이상 5미만 스트림 생성
.forEach(s -> System.out.print(s + " ")); // 1 2 3 4
System.out.println();
IntStream.rangeClosed(1, 5) // 1이상 5이하 스트림 생성
.forEach(s -> System.out.print(s + " ")); // 1 2 3 4 5
}
}
중간 연산 메소드
- 초기 스트림은 중간 연산을 통해 또 다른 스트림으로 변환된다.
- 중간 연산은 스트림을 전달받아 스트림을 반환하므로, 연속으로 연결해서 사용할 수 있다.
필터링 - filter
- filter 메소드는 기존 스트림에서 주어진 조건에 맞는 요소만으로 구성된 새로운 스트림을 반환한다.
- filter 메소드 호출시 Predicate의 추상 메소드인 test를 구현하는 람다식(필터링의 조건이 되는 식)을 인자로 전달해야 한다.
- 내부적으로 스트림의 데이터를 하나씩 인자로 전달하면서 test를 호출한다.
- 그 결과 true가 반환되면 스트림에 남기고, false가 반환되면 해당 데이터는 거른다.
Stream<T> filter(Predicate<? super T> predicate) // Predicate<T> boolean test(T t)
int[] ex1 = {1, 2, 3, 4, 5};
Arrays.stream(ex1) // 배열 기반 스트림 생성
.filter(s -> n%2 == 1) // 홀수만 통과
.forEach(s -> System.out.print(s + " "));
System.out.println(); // 1 3 5
List<String> ex2 = Arrays.asList("Red", "Black", "Blue");
ex2.stream() // 컬렉션 인스턴스 기반 스트림 생성
.filter(s -> s.length() == 3) // 길이가 3이면 통과
.forEach(s -> System.out.print(s + " ")); // Red
매핑 - map
- map 메소드는 스트림의 요소들을 주어진 함수에 인자로 전달하여, 그 반환 값들로 이루어진 새로운 스트림을 반환한다.
- 이때 기본 자료형을 위한 map 메소드가 별도로 정의되어 있으며, 불필요한 오토박싱과 오토 언박싱 과정을 생략할 수 있다.
- map() 메소드 호출시 Function의 추상메소드인 apply를 구현하는 람다식(맵핑의 기준이 되는 식)을 인자로 전달해야 한다.
- 맵핑을 진행하면 스트림의 데이터 형(type)이 달라지는 특징이 있다.
<R> Stream<R> map(Function<? super T, ? extends R> mapper) // Function<T, R> R apply(T t)
IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
List<String> ex = Arrays.asList("Red", "Black", "Blue");
ex.stream() // 컬렉션 인스턴스 기반 스트림 생성
.map(s -> s.length()) // 문자열의 길이를 기준으로 맵핑
.forEach(s -> System.out.print(s + " ")); // 3 5 4
매핑 - flatMap
- flatMap 메소드를 사용하면 하나의 데이터가 여러 개의 데이터로 맵핑된다.
- flatMap 메소드 호출시 전달할 람다식에서는 스트림을 생성하고 스트림을 반환해야 한다.
<R> Stream<R> flatMap(Funtion<T, Stream<R>> mapper) // Function<T, Stream<R>> Stream<R> apply(T t)
IntStream flatMapToInt(Function<T, IntStream> mapper)
LongStream flatMapToLong(Function<T, LongStream> mapper)
DoubleStream flatMapToDouble(Function<T, DoubleStream> mapper)
더보기
class Stream_FlatMap1 {
public static void main(String[] args) {
Stream<String> ss1 = Stream.of("My_NAME", "My_AGE");
// 람다식에서 스트림 생성
Stream<String> ss2 = ss1.flatMap(s -> Arrays.stream(s.split("_")));
ss2.forEach(System.out::println);
}
}
My
NAME
My
AGE
연결 - concat
- concat 메소드는 두 개의 스트림을 연결하여 하나의 스트림으로 반환해준다.
static <T> Stream<T> concat(Stream<? extends T)a, Stream<? extends T> b)
static IntStream concat(IntStream a, IntStream b) // IntStream의 메소드
static LongStream concat(LongStream a, LongStream b) // LongStream의 메소드
static DoubleStream concat(DoubleStream a, DoubleStream b) // DoubleStream의 메소드
더보기
class Stream_Concat {
public static void main(String[] args) {
Stream<String> ss1 = Stream.of("Cake", "Milk");
Stream<String> ss2 = Stream.of("Lemon", "Jelly");
Stream.concat(ss1, ss2) // 스트림 연결
.forEach(s -> System.out.print(s + " ")); // Cake Milk Lemon Jelly
}
}
정렬 - sorted
- sorted 메소드는 정렬 기능을 제공하는 중간 연산 메소드이다.
- 기본적으로 Comparable 인터페이스의 compareTo 메소드 구현 내용(오름차순, 사전 편찬순)을 기반으로 정렬이 이뤄진다.
- sorted 메소드 호출시 인자로 Comparator 인터페이스의 compare 메소드를 구현하는 람다식을 전달하면, 해당 람다식을 기준으로 정렬이 이뤄진다.
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
IntStream sorted()
LongStream sorted()
DoubleStream sorted()
더보기
class Stream_Sorted {
public static void main(String[] args) {
Stream.of("Red", "Blue", "Yellow")
.sorted() // 기본적으로 사전 편찬 순 오름차순 정렬
.forEach(s -> System.out.print(s + " ")); // Blue Red Yellow
System.out.println();
Stream.of("Red", "Blue", "Yellow")
.sorted((s1, s2) -> s1.length() - s2.length()) // 글자수를 기준으로 오름차순 정렬
.forEach(s -> System.out.print(s + " ")); // Red Blue Yellow
}
}
루핑 - peek
- 루핑이란 스트림을 이루는 모든 데이터 각각을 대상으로 특정 연산을 진행하는 행위를 말한다. 대표적인 루핑 연산으로 forEach()가 있지만 이는 최종 연산에 해당한다. 따라서 중간 연산의 루핑을 위한 peek 메소드가 존재한다.
- peek 메소드는 forEach와 기능와 동일하며, 최종 연산이 아닌 중간 연산이라는 차이점만 있다.
- 주로 연산과 연산 사이에 결과를 확인하고 싶을 때 유용하게 사용할 수 있다.
Stream<T> peek(Consumer<? super T> action)
IntStream peek(IntConsumer action)
LongStream peek(LongConsumer action)
DoubleStream peek(DoubleConsumer action)
더보기
class Stream_Peek {
public static void main(String[] args) {
// 최종 연산이 생략된 스트림의 파이프라인
IntStream.of(1, 3, 5)
.peek(d -> System.out.print(d + " ")); // 중간 연산은 아무런 의미x
System.out.println();
// 최종 연산이 존재하는 스트림의 파이프 라인
IntStream.of(5, 3, 1)
.peek(d -> System.out.print(d + " ")) // 중간 연산
.sum(); // 최종 연산
}
}
5 3 1
참고 예제
• 필터링(filter) + 맵핑(mapToInt)
더보기
class ToyPriceInfo { // 장난감 모델 별 가격 정보
private String model; // 모델명
private int price; // 가격
public ToyPriceInfo(String m, int p) {
model = m;
price = p;
}
public int getPrice() {
return price;
}
}
class Stream_FilteringMap {
public static void main(String[] args) {
List<ToyPriceInfo> ls = new ArrayList<>();
ls.add(new ToyPriceInfo("건담_45", 200));
ls.add(new ToyPriceInfo("콩순이_2", 350));
ls.add(new ToyPriceInfo("타요_779", 550));
int sum = ls.stream()
.filter(p -> p.getPrice() < 500) // 가격이 500 미만인 것만 통과
.mapToInt(t -> t.getPrice()) // 가격을 기준으로 맵핑
.sum(); // 최종 연산
System.out.println("sum = " + sum);
}
}
sum = 550
• 맵핑 (flatMap)
더보기
class ReportCard { // 성적표
private int kor; // 국어 점수
private int eng; // 영어 점수
private int math; // 수학 점수
public ReportCard(int k, int e, int m) {
kor = k;
eng = e;
math = m;
}
public int getKor() { return kor; }
public int getEng() { return eng; }
public int getMath() { return math; }
}
class Stream_FlatMap_GradeAvg {
public static void main(String[] args) {
ReportCard[] cards = {
new ReportCard(70, 80, 90),
new ReportCard(90, 80, 70),
new ReportCard(80, 80, 80)
};
Arrays.stream(cards) // 배열 기반 스트림 생성
.flatMapToInt(r -> IntStream.of(r.getKor(), r.getEng(), r.getMath()))
.average()
.ifPresent(avg -> System.out.println("평균 : " + avg));
}
}
평균 : 80.0
이외의 중간 연산 메소드
메소드 | 설명 |
Stream<T> distinct() | 해당 스트림에서 중복된 요소가 제거된 새로운 스트림을 반환 내부적으로 Object 클래스의 equals() 메소드를 사용 |
Stream<T> limit(long maxSize) | 해당 스트림에서 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림을 반환 |
Stream<T> skip(long n) | 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소를 제외한 나머지 요소만으로 이루어진 새로운 스트림을 반환 |
최종 연산 메소드
reduce
- reduce 메소드는 스트림의 모든 요소를 소모하여 연산을 수행하고, 그 결과를 반환한다.
- 첫번째와 두번째 요소를 대상으로 연산을 수행한 뒤, 그 결과와 세번째 요소를 대상으로 또 다시 연산을 수행하는 식으로 진행
- reduce() 메소드는 전달하는 람다식에 의해 연산의 내용이 결정되어 활용도가 높다.
- 메소드 호출시 apply 추상 메소드를 구현하는 람다식을 두번째 인자로 전달해야 한다.
- 첫번째 인자로 전달되는 값은 스트림을 구성하는 데이터가 하나도 없을 때 반환된다.
- 이때 스트림이 비어있지 않은 경우에는 첫번째 전달인자를 스트림의 첫번째 데이터로 간주하고 리덕션을 진행한다는 점을 주의해야한다.
T reduce(T identity, BinaryOperator<T> accumulator)
// BinaryOperator<T> T apply(T t1, T t2)JAVA
더보기
class Stream_Reduce {
public static void main(String[] args) {
List<String> ls = Arrays.asList("Red", "Blue", "Black", "Orange");
BinaryOperator<String> lc = (s1, s2) -> {
if (s1.length() > s2.length()) // 긴 문자열을 반환
return s1;
else
return s2;
};
String str = ls.stream()
.reduce("", lc); // 스트림이 빈 경우 빈 문자열 반환
System.out.println(str); // Orange
}
}
sum, average, count, min, max
- IntStream, LongStream, DoubleStream형 참조변수가 참조하는 스트림을 대상으로만 다음과 같은 연산이 가능하다.
int sum()
long count()
OptionalDouble average()
OptionalInt min()
OptionalInt max()
// LongStream, DoubleStream에도 동일한 메소드 존재
더보기
class Stream_RelatedToNum {
public static void main(String[] args) {
// 합
int sum = IntStream.of(1, 3, 5, 7, 9)
.sum();
System.out.println("합 : " + sum); // 합 : 25
// 개수
long cnt = IntStream.of(1, 3, 5, 7, 9)
.count();
System.out.println("개수 : " + cnt); // 개수 : 5
// 평균
IntStream.of(1, 3, 5, 7, 9)
.average()
.ifPresent(avg -> System.out.println("평균 : " + avg)); // 평균 : 5.0
// 최소
IntStream.of(1, 3, 5, 7, 9)
.min()
.ifPresent(mn -> System.out.println("최소 : " + mn)); // 최소 : 1
// 최대
IntStream.of(1, 3, 5, 7, 9)
.max()
.ifPresent(mx -> System.out.println("최대 : " + mx)); // 최대 : 9
}
}
forEach
- 스트림의 각 요소를 소모하여 전달된 람다식을 수행한다.
- 반환 타입이 void로 주로 스트림의 모든 요소를 출력하는 용도로 사용한다.
void forEach(Consumer<? super T> action)
void forEach(IntConsumer action)
void forEach(LongConsumer action)
void forEach(DoubleConsumer action)
allMatch, anyMatch, noneMatch
- 스트림의 요소 중에서 특정 조건을 만족하는 요소가 있는지, 아니면 모두 만족하거나 모두 만족하지 않는지 확인할 수 있는 메소드이다.
boolean allMatch(Predicate<? super T> predicate) 스트림의 모든 요소가 조건을 만족하면 true
boolean anyMatch(Predicate<? super T> predicate) 스트림의 요소중 하나라도 조건을 만족하면 true
boolean noneMatch(Predicate<? super T> predicate) 스트림의 모든 요소가 조건을 만족하지 않으면 true
boolean allMatch(IntPredicate predicate)
boolean anyMatch(IntPredicate predicate)
boolean noneMatch(IntPredicate predicate)
// LongStream, DoubleStream에도 동일한 메소드 존재
더보기
class Stream_Match {
public static void main(String[] args) {
boolean b = IntStream.of(1, 2, 3, 4, 5)
.allMatch(n -> n % 2 == 0);
System.out.println("모두 짝수인가요? " + b);
b = IntStream.of(1, 2, 3, 4, 5)
.anyMatch(n -> n % 2 == 0);
System.out.println("짝수가 하나는 있나요? " + b);
b = IntStream.of(1, 2, 3, 4, 5)
.noneMatch(n -> n % 2 == 0);
System.out.println("짝수가 하나는 없나요? " + b);
}
}
모두 짝수인가요? false
짝수가 하나는 있나요? true
짝수가 하나는 없나요? false
collect
- collect 메소드는 중간 연산을 통과한 스트림의 요소를 인자로 전달된 Collectores 객체에 구현된 방법대로 수집한다.
- 첫번째 인자는 스트림의 요소를 저장해줄 저장소를 만드는 람다식을 전달한다.
두번째 인자는 만들어준 저장소에 스트림의 요소를 저장하는 람다식을 전달한다.
세번째 인자는 병렬스트림의 결과를 어떻게 취합할지에 대한 람다식을 전달한다.
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner)
// IntStream, LongStream, DoubleStream에도 동일한 메소드 존재
더보기
class Stream_Collect {
public static void main(String[] args) {
String[] names = {"anne", "wuga", "jeong", "jun"};
Stream<String> ss = Arrays.stream(names); // 스트림 생성
List<String> ls = ss.parallel() // 병렬 스트림으로 변환
.filter(s -> s.length() < 5) // 문자열 길이 5 미만인 것만 통과
.collect(() -> new ArrayList<>(), // 저장소 생성
(c, s) -> c.add(s), // 저장소에 통과된 스트림 요소 저장
(ls1, ls2) -> ls1.addAll(ls2)); // 병렬 처리 결과를 취합하는 방법
System.out.println(ls);
}
}
[anne, wuga, jun]
병렬 스트림
하나의 작업을 둘 이상의 작업으로 나누어서 동시에 진행하는 것을 병렬처리라 한다. 자바는 언어차원에서 이러한 '병렬 처리'를 위한 메소드를 지원한다.
병렬 스트림 생성 - parallelStream
- parallelStream 메소드를 통해 병렬 스트림을 생성한다.
- 병렬 스트림을 생성하면 이어지는 연산들은 CPU의 코어 수를 고려해 적절하게 병렬로 처리된다.
- 병렬 스트림을 생성하면 둘 이상의 연산을 동시에 진행하기 때문에 연산의 단계를 줄일 수 있어 속도 측면에서 장점을 갖는다.
- 하지만, 모든 상황에서 병렬처리가 더 빠른 속도를 갖지는 않기 때문에 상황에 따라 선택적으로 사용한다.
List<String> ls = Arrays.asList("Red", "Blue", "Black", "orange");
BinaryOperator<String> lc = (s1, s2) -> {
if (s1.length() > s2.length()) // 긴 문자열을 반환
return s1;
else
return s2;
};
String str = ls.parallelStream() // 병렬 처리를 위한 스트림 생성
.reduce("", lc);
System.out.println(str); // Orange
병렬 스트림으로 변경 - parallel
- 이미 스트림을 생성한 상태에서 이를 기반으로 parallel 메소드를 호출하면 병렬스트림으로 생성해준다.
Stream<T> parallel() // Stream<T>의 메소드
DoubleStream parallel() // DoubleStream의 메소드
IntStream parallel() // IntStream의 메소드
LongStream parallel() // LongStream의 메소드
더보기
class Stream_ToParallel {
public static void main(String[] args) {
List<String> ls = Arrays.asList("Red", "Black", "Blue");
Stream<String> ss = ls.stream(); // 스트림 생성
BinaryOperator<String> lc = (s1, s2) -> {
if (s1.length() > s2.length()) // 문자열 길이가 긴 요소 반환
return s1;
else
return s2;
};
String str = ss.parallel() // 병렬 스트림 생성
.reduce("", lc);
System.out.println(str); // Black
}
}
Reference
http://www.tcpschool.com/java/java_stream_concept
'☕ Java > 이론' 카테고리의 다른 글
정적 팩토리 메서드(static factory method) (0) | 2023.06.12 |
---|---|
[Java] 체크 예외와 언체크 예외 (0) | 2023.03.07 |
[Junit5] @ParameterizedTest 사용하기 (0) | 2022.10.11 |
[Java] 추상 클래스 (0) | 2022.08.30 |
[Java] Optional 클래스 (0) | 2022.02.07 |
[Java] 메소드 참조 (Method References) (0) | 2022.02.04 |
[Java] 함수형 인터페이스 (0) | 2022.02.03 |
[Java] 람다 (0) | 2022.01.28 |