[작성일: 2023. 02. 15]
스트림(Stream)
- 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것 - JDK 1.8
- 컬렉션(List, Set, Map), 배열 등으로부터 Stream을 만들 수 있게 됨.
- Stream을 만들고나서부터는 똑같은 작업방식으로 작업이 진행됨.
- Stream ➡️ 중간연산(0~n번) ➡️ 최종연산(1번), 결과
- 중간연산: 연산결과가 스트림인 연산. 반복적으로 적용 가능
- 최종연산: 연산결과가 스트림이 아닌 연산. 단 한 번만 적용 가능(스트림의 요소를 소모)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> intStream = list.stream(); // 컬렉션
Stream<String> strStream = Stream.of(new String[] { "a", "b", "c")); // 배열
Stream<Integer> evenStream = Stream.iterate(0, n-> n+2); // 0, 2, 4, 6 ...
Stream<Double> randomStream = Stream.generate(Math::random); // 람다식
IntStream intStream = new Random().ints(5) // 난수 스트림(크기가 5)
stream.distinct().limit(5).sorted().forEach(System.out::println)
중간연산 중간연산 중간연산 최종연산
✏️ 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지는 않음.(Read only)
List<Integer> list = Arrays.asList(3,1,5,4,2); List<Integer> sortedList = list.strea().sorted().collect(Collectors.toList()); // list를 정렬해서 새로운 List에 저장 System.out.println(list); // [3, 1, 5, 3, 2] System.out.println(sortedList); // [1, 2, 3, 4, 5]
✏️ 스트림은 Iterator처럼 일회용임. (필요하면 다시 스트림 생성)strStream.forEach(System.out::println); // 모든 요소를 화면에 출력(최종연산) int numOfStr = strStream.count(); // 에러. 스트림이 이미 닫힘.
✏️ 최종 연산 전까지 중간연산이 수행되지 않음. (지연된 연산)IntStream intStream = new Random().ints(1, 46); //1 ~ 45 범위의 무한 스트림 intStream distinct().limit(6).sorted().forEach(i->System.out.print(i+",")); 중간연산 최종연산
✏️ 스트림은 작업을 내부 반복으로 처리함.stream.forEach(System.out::println); // 최종 연산
✏️ 스트림의 작업을 병렬로 처리함. (병렬 스트림)Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b"); int sum = strStream.parallel().mapToInt(s->length()).sum(); // 병렬 스트림으로 속성 변경 모든 문자열의 길이
✏️ 기본형 스트림
- IntStream, LongStream, DoubleStream, ...
- 오토박싱 & 언박싱의 비효율이 제거됨 (Stream<Integer> 대신 IntStream 사용)
- 숫자와 관련된 유용한 메서드를 Stream<T> 보다 더 많이 제공함.
스트림 만들기 - 컬렉션
- Collection 인터페이스의 Stream()으로 컬렉션을 스트림으로 변환함.
Stream<E> stream() // Collection인터페이스의 메서드
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
class ex {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream();
// list를 스트림으로 변환
// 스트림의 모든 요소 출력
intStream.forEach(System.out::print);
intStream.forEach(System.out::print); // 에러. 스트림이 이미 닫힘.
}
}
스트림 만들기 - 배열
✏️ 객체 배열로부터 스트림 생성하기
Stream<T> Stream.of(T... values) // 가변 인자 Stream<T> Stream.of(T[]) Stream<T> Arrays.stream(T[]) Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive) Stream<String> strStream = Stream.of("a","b","c"); // 가변인자 Stream<String> strStream = Stream.of(new String[] {"a","b","c"}); Stream<String> strStream = Arrays.stream(new String[] {"a","b","c"}); Stream<String> strStream = Arrays.stream(new String[] {"a","b","c"}, 0, 3);
✏️ 기본형 배열로부터 스트림 생성하기IntStream IntStream.of(int...value) // Stream이 아니라 IntStream IntStream IntStream.of(int[]) IntStream Arrays.Stream(int[]) IntStream Arrays.Stream(int[] array, int startInclusive, int endExclusive)
스트림 만들기 - 임의의 수
✏️ 난수를 요소로 갖는 스트림 생성하기
IntStreamintStream = new Random().ints(); // 무한 스트림 intStream.limit(5).forEach(Stream.out::println); // 5개의 요소만 출력함. IntStream intStream = new Random().ints(5); // 크기가 5인 난수 스트림을 반환 Integer.MIN_VALUE <= ints() <= Integer.Max_VALUE Long.MIN_VLUE <= longs() <=Long.MAX_VALUE 0.0 <= doubles() < 1.0
✏️ 지정된 범위의 난수를 요소로 갖는 스트림을 생성하는 메서드(Random 클래스)IntStream ints(int begin, int end) LongStream longs(long begin, long end) DoubleStream doubles(double begin, double end) IntStream ints(long streamSize, int begin, int end) LongStream longs(long streamSize, long begin, long end) DoubleStream doubles(long streamSize, double begin, double end)
스트림 만들기 - 특정 범위의 정수
✏️ 특정 범위의 정수를 요소로 갖는 스트림 생성하기
IntStream, LongStream, ...
IntStream IntStream.range(int begin, int end) // 끝 미포함 IntStream IntStream.rangeClosed(int begin, int end) // 끝 포함 IntStream IntStream.range(1, 5); // 1, 2, 3, 4 IntStream IntStream.rangeClosed(1, 5); // 1, 2, 3, 4, 5
스트림 만들기 - 람다식
- 무한 스트림
- seed - 초기값
✏️ 람다식을 소스로 하는 스트림 생성하기
static<T> Stream<T> iterate(T seed, UnaryOperator<T> f) // 초기값, 람다식 // 이전 요소에 종속적 static<T> Stream<T> generate(Supplier<T> s) //이전 요소에 독립적
✏️ iterate()는 이전 요소를 seed로 해서 다음 요소 계산Stream<Integer> evenStream = Stream.iterate(0, n->n+2); // 0, 2, 4, 6, ... 무한스트림
✏️ generate()는 seed를 사용하지 않음.
- 이전 결과와 관련없고 각 요소가 독립적임.Stream<Double> randomStream = Sream.generate(Math::random); Stream<Integer> oneStream = Sream.generate(()->1; // 1, 1, 1, 1, ...
스트림 만들기 - 파일과 빈 스트림
✏️ 파일을 소스로 하는 스트림 생성하기
Stream<Path> Files.list(Path.dir) // 폴더 경로 // Path는 파일 또는 디렉토리 Stream<String> Files.lines(Path path) // 파일 내용을 라인 단위로 읽어서 반환 Stream<String> Files.lines(Path path, Charset cs) Stream<String> lines() // BufferedReader 클래스의 메서드
✏️ 비어있는 스트림 생성하기Stream. emptyStream = Stream.empty() // empty()는 빈 스트림 생성해서 반환함. long count = emptyStream.count(); // count의 값은 0
스트림의 중간 연산
중간 연산 | 설명 | |
Stream<T> distinct() | 중복 제거 | |
Stream<T> filter(Predicate<T> predicate) | 조건에 안 맞는 요소 제거 여러번 사용할 수 있음. |
|
Stream<T> limit(long maxSize) | maxSize 이후의 요소는 잘라냄 스트림의 일부를 잘라냄. |
|
Stream<T> skip(long n) | 스트림의 일부를 건너뜀. 앞에서부터 n개 건너뛰기 |
|
Stream<T> peek(Consumer<T> action) // 스트림 소비 x | 스트림의 요소에 작업 수행 forEach와 비슷함. 스트림의 요소를 소비하지 않고 엿보기 중간 작업결과 확인할 때 사용함.(디버깅 용도) |
|
Stream<T> sorted() Stream<T> sorted(Comparator<T> comparator) |
스트림의 요소를 기본 정렬함. 지정된 Comparator로 정렬함. |
|
Stream<R> DoubleStream IntStream LongStream Stream<R> DoubleStream IntStream LongStream |
map(Function<T, R> mapper) mapToDouble(ToDoubleFunction<T> mapper mapToInt(ToIntFunction<T> mapper) mapToLong(ToLongFunction<T> mapper) flatMap(Function<T, Stream<R>> mapper) flatMapToDouble(Function<T, DoubleStream> m) flatMapToInt(Function<T, IntStream> m) flatMapToLong(Function<T, LongStream> m) |
스트림의 요소를 변환함. Stream<T> -> Stream<R> 스트림의 스트림을 스트림으로 변환해줌. |
스트림의 최종 연산
최종 연산 | 설명 |
void forEach(Consumer<? super T> action) // 병렬 스트림인 경우 순서보장 안 됨. 더 빠름. void forEachOrdered(Consumer<? super T> action) // 순서 유지 기능, 병렬 스트림 사용할 때 사용 |
각 요소에 지정된 작업 수행 반환타입은 void임. (peek와 다름) |
long count() | 스트림의 요소 개수를 반환 |
Optional<T> max(Comparator<? super T> comaprator) Optional<T> min(Comparator<? super T> comaprator) // Comparator는 정렬 기준 |
스트림의 최대값/최소값을 반환 |
Optional<T> findAny() // 아무거나 하나(병렬) Optional<T> findFirst() // 첫번째 요소(직렬) // fillter()와 함께 쓰임. |
조건에 맞는 스트림의 요소 하나를 반환 |
boolean allMatch(Predicate<T> p) // 모두 만족하는가 boolean anyMatch(Predicate<T> p) // 하나라도 만족하는가 boolean noneMatch(Predicate<T> p) // 모두 만족하지 않는가 |
주어진 조건을 모든 요소가 만족 시키는지, 만족 시키지 않는지 확인함. |
Object[] toArray() A[] to Array(IntFunction<A[]> generator) |
스트림의 모든 요소를 배열로 반환 특정 타입의 배열로 반환 |
Optional<T> reduce(BinaryOperator<T> accumulator) T reduce(T identity, BinaryOperator<T> accumlator) // 초기값, 수행할 연산 U reduce(U identity, BinaryOperator<U, T, U> accumlator, BinaryOperator<U> combiner) |
스트림의 요소를 하나씩 줄여가면서(리듀싱) 누적계산함. |
R collect(Collector<T, A, R> collector) R collect(Supplier<R> supplier, BiConsumer<R, T> accumulator, BiCounsumer<R, R> combiner) |
스트림의 요소를 수집함. 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용됨. |
Optional<T>
- T 타입 객체의 래퍼클래스
- null을 직접 다루는 것은 예외발생 할 수 있어서 위험하므로 객체 안에 담아서 간접적으로 사용함.
- null을 직접 다루게 되면 if문으로 null 체크를 해줘야 하므로 코드가 복잡해짐.
- NullPointerException 줄이기!
- get(), orElse(), orElseGet(), orElseThrow()
- ispresent() - Optional객체의 값이 null이면 false, 아니면 true를 반환함.
- OptionalInt, OptionalLong, OptionalDouble
Optional 클래스 | 값을 반환하는 메서드 |
Optional<T> | T get() |
OptionalInt | int getAsInt() |
OptionalLong | long getAsLong() |
OptionalDouble | dobule getAsDouble() |
public final class Optional<T> {
private final T value; // T타입의 참조변수
...
} // 모든 타입 객체 저장 가능
String str = "abc";
Optional<String> optVal = Optional.of(str);
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(null); // 예외 발생
Optional<String> optVal = Optional.ofNullable(null); // ok
Optional<String> optVal = null; // null로 초기화는 바람직하지 않음.
Optional<String> optVal = Optional.<String>empty(); // 빈 객체로 초기화
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get(); // optVal에 저장된 값을 반환하나 null이면 예외 발생(잘 안 씀)
String str2 = optVal.orElse(""); // 저장된 값이 null일 때는 "" 반환
String str3 = optVal.orElseGet(String::new);
// 람다식 사용 가능 () -> new String()
String str4 = optVal.orElseThrow(NullPointerException::new);
// null이면 예외 발생, 예외 종류 지정 가능
public final class OptionalInt {
...
private final boolean isPresent; // 값이 저장되어 있으면 true
private final int value; // int 타입의 변수
...
}
// 빈 Optional 객체와의 비교
OptionalInt opt = OptionalInt.of(0); // OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empty(); // OptionalInt에 0을 저장
System.out.println(opt.isPresent()); // true
System.out.println(opt2.isPresent()); // false
System.out.println(opt.equals(opt2)); // false
Collect()와 Collectors
- Collector를 매개변수로 하는 스트림의 최종연산
Object collect(Collector collector)
// Collector를 구현한 클래스의 객체를 매개변수로
Object collect(Subblier supplier, BiCosumer accumulator, BiConsumer com biner)
// 잘 안 쓰임.
- Collector는 수집(collect)에 필요한 메서드를 정의해 놓은 인터페이스
public interface Collector<T, A, R> {
// T(요소)를 A에 누적한 다음, 결과를 R로 변환해서 반환함.
Supplier<A> supplier() // StringBuilder::new 누적할 곳
BiConsumer<A, T> accumulator(); // (sb, a) -> sb.append(s) 누적방법
BinaryOperator<A> combiner(); // (sb1, sb2) -> sb1.append(sb2) 결합방법(병렬)
Function<A, R> finisher(); // sb -> sb.toString() 최종변환
Set<Characteristics> characteristics(); // 컬렉터의 특성이 담긴 Set을 반환
...
}
- Collectors 클래스는 다양한 기능의 컬렉터(Collector를 구현한 클래스)를 제공함.
✏️ 스트림을 컬렉션으로 변환
Collectors.toList(), toSet(), toMap(), toCollection()
List<String> names = stuStream.map(Student::getName) // Stream<Student> -> Stream<String> ArrayList<String> list = name.stream() .collect(Collectors.toCollection(ArrayList::new); // Stream<String> -> ArrayList<String> Map<String, Person> map = personStream .collect(Collectors.toMap.getRegId(), p->p)); // Stream<Person> -> Map<String, Person>
✏️ 스트림을 배열로 반환
toArray()
Student[] stuNames = studentStream.toArray(Student[]::new); // ok Student[] stuNames = studentStream.toArray(); // 에러 Object[] stuNames = studentStream.toArray(); // ok
✏️ 스트림의 통계정보 제공
counting(), summingInt(), maxBy(), minBy(), ...long count = stuStream.count(); long count = stuStream.collect(counting()); long totalScore = stuStream.mapToInt(Student::getTotalScore).sum(); // IntStream의 sum() long totalScore = stuStream.collect(summingInt(Student::getTotalScore); OptionalInt topScore = studentStream.mapToInt(Student::getTotalScore).max()); Optional<Student> topStudent = stuStream .max(Comparator.comparingInt(Student::getTotalScore)); Optional<Student> topStudent = stuStream .collect(maxBy(Comparator.comparingInt(Student::getTotalScore)));
✏️ 스트림을 리듀싱
reducing()Collector reducing(BinaryOperator<T> op) Collector reducing(T identity, BinaryOperator<T> op) Collector reducing(U identity, Function<T, U> mapper, BinaryOperator<U> op) // map + reduce IntStream intStream = new Random().ints(1, 46).distinct().limit(6); OptionalInt max = intStream.reduce(Integer::max); OptionalInt<Integer> max = intStream.boxed().collect(reducing(Integer::max)); long sum = intStream.reduce(0, (a,b) -> a + b); long sum = intStream.boxed().collect(reducing(0, (a, b) -> a + b)); int grandTotal = stuStream.map(Student::getTotalScore) .reduce(0, Integer::sum); int grandTotal = stuStream.collect(reducing(0, Student::getTotalScore, Integer::sum));
✏️ 문자열 스트림의 요소를 모두 연결
joining()String studentNames = stuStream.map(Student::getName).collect(joining()); String studentNames = stuStream.map(Student::getName) .collect(joining(",")); // 구분자 String studentNames = stuStream.map(Student::getName) .collect(joining("," "[", "]")); String studentInfo = stuStream.collect(joining(",")); // Student의 toString()으로 결합
스트림의 분할
- partitioningBy()는 스트림을 2분할 함.
Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downstream)
Map<Boolean, List<Student>> stuBySex = stuStream
.collect(partitioningBy(Student::isMale)) // 학생들을 성별로 분할
List<Student> maleStudent = stuBySex.get(true); // 남학생 목록
List<Student> femaleStudent = stuBySex.get(false); // 여학생 목록
스트림의 그룹화
- groupingBy()는 스트림을 n분할 함.
- 스트림의 요소를 그룹화
Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
Map<Integer, List<Student>> stuByBan = stuStream
.collect(groupingBy(Student::getBan, toList());
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream
.collect(groupingBy(Student::getHak, groupingBy(Student::get)));
// 다중그룹화 (학년별 그룹화, 반별 그룹화)
스트림의 변환
- 외우지 말고 변환할 일 있을 때 한 번씩 보고 사용하기
from | to | 변환 메서드 |
1. 스트림 ➡️ 기본형 스트림 | ||
Stream<T> | IntStream LongStream DoubleStream |
mapToInt(ToIntFunction<T> mapper) mapToLong(ToLongFunction<T> mapper) mapToDouble(ToDoubleFunction<T> mapper) |
2. 기본형 스트림 ➡️ 스트림 | ||
IntStream LongStream DoubleStream |
Stream<Integer> Stream<Long> Stream<Double> |
boxed() |
Stream<U> | mapToObject(DoubleFunction mapper) | |
3. 기본형 스트림 ➡️ 기본형 스트림 | ||
IntStream LongStream DoubleStream |
LongStream DoubleStream |
asLongStream() asDoubleStream() |
4. 스트림 ➡️ 부분 스트림 | ||
Steam<T> IntStream |
Stream<T> IntStream |
skip(long n) limit(long maxSize) |
5. 두 개의 스트림 ➡️ 스트림 | ||
Stream<T>, Stream<T> | Stream<T> | concat(Stream<T> a, Stream<T> b) |
IntStream, IntStream | IntStream | concat(Stream a, Stream b) |
LongStream, LongStream | LongStream | concat(LongStream a, LongStream b) |
DoubleStream, DoubleStream | DoubleStream | concat(DoubleStream a, DoubleStream b) |
6. 스트림의 스트림 ➡️ 스트림 | ||
Stream<Stream<T>> | Stream<T> | flatMap(Function mapper) |
Stream<IntStream> | IntStream | flatMapToInt(Function mapper) |
Stream<LongStream> | LongStream | flatMapToLong(Function mapper) |
Stream<DoubleStream> | DoubleStream | flatMapToDouble(Function mapper) |
from | to | 변환 메서드 |
7. 스트림 ↔️ 병렬 스트림 | ||
Stream<T> IntStream LongStream DoubleStream |
Stream<T> IntStream LongStream DoubleStream |
parallel() // 스트림 ➡️ 병렬 스트림 sequential() // 병렬 스트림 ➡️ 스트림 |
8. 스트림 ➡️ 컬렉션 | ||
Stream<T> IntStream LongStream DoubleStream |
Collection<T> | collect(Collectors, toCollection(Supplier factory)) |
List<T> | collect(Collectors, toList)) | |
Set<T> | collect(Collectors, toSet)) | |
9. 컬렉션 ➡️ 스트림 | ||
Collection<T> List<T> Set<T> |
Stream<T> | stream() |
10. 스트림 ➡️ Map | ||
Stream IntStream LongStream DoubleStream |
Map<K, V> | collect(Collectors.toMap(Function key, Function value)) collect(Collectors.toMap(Function k, Function v, BinaryOperator)) collect(Collectors.toMap(Function k, Function v, BinaryOperator merge, Supplier mapSupplier)) |
11. 스트림 ➡️ 배열 | ||
Stream<T> | Object[] | toArray() |
T[] | toArray(IntFunction<A[]> generator) | |
IntStream LongStream DoubleStream |
int[] long[] double[] |
toArray() |
🐣 해당 게시글은 자바의 정석(남궁성 님) 영상으로 함께 공부하며 요약/정리한 글입니다.
🐣 입문 개발자가 작성한 글이므로 틀린 내용이나 오타가 있을 수 있습니다.