stream에 대한 정리
스트림을 사용하는 이유
많은 수의 데이터를 다룰 때, 컬랙션이나 배열에 데이터를 담고 원하는 결과를 얻기 위해 반복문이나 Iterator를 이용 하는 방법이 있지만, 코드가 너무 길어질 수 있고 재사용성도 떨어진다.
List를 정렬 할때는 Collections.sort()를 사용하고, 배열을 정렬할 때는 Arrays.sort()를 사용하는 것처럼,
각 컬랙션 클래스에는 같은 기능의 메서드들이 중복해서 정의되어 있다.
스트림(Stream)은 이러한 문제점들을 해결해준다. 스트림은 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용하는 메서드들을 정의했다. 스트림을 이용하면, 배열이나 컬렉션뿐만 아니라 파일에 저장된 데이터도 모두 같은 방식으로 다룰 수 있다. 스트림을 사용하면 코드가 간결해지고 재사용성도 높다.
스트림 만들기
스트림 소스가 될 수 있는 대상은 배열, 컬렉션, 임의의 수 등 다양하다.
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> stream = list.stream();
컬랙션의 최고 조상인 Collection에 stream()이 정의되어 있다. 그래서 Collection의 자손인 List와 Set을 구현한 컬랙션 클레스들은 모두 이 메서드로 스트림을 생성할 수 있다. stream()은 해당 컬랙션을 소스로 하는 스트림을 반환한다.
위의 예시는 가변인자 1~5까지의 정수를 List에 담고, list를 소스로 하는 Stream을 생성하는 방법이다.
스트림 사용하기
stream.forEach(s->System.out.println(s));
stream.forEach(System.out::println);
forEach()는 지정된 작업을 스트림의 모든 요소에 대해 수행한다.
위의 두 문장은 같은 작업을 하는 문장이다. 그러나 첫째줄을 수행하면 1에서 5까지의 숫자가 출력되지만, 두번째 줄은 수행되지 않는다. 왜냐하면 첫번째 forEach()가 스트림의 요소를 소모하면서 작업을 수행하기 때문에 같은 스트림에 forEach()를 두 번 호출할 수 없다. 스트림 요소를 다시 출력하려면 새로운 스트림을 생성해야 한다.
stream.forEach(s->System.out.println(s)); //첫번째 스트림 출력
Stream<Integer> stream2 = list.stream();
stream2.forEach(System.out::println); //두번째 스트림 출력
여기서 알 수 있는 스트림의 특징은
- 스트림은 데이터 소스를 읽기만 할 뿐 데이터 소스를 변경하지 않는다.
- 스트림은 Iterator가 컬랙션의 요소를 모두 읽고 나면 사용할 수 없는 것 처럼 한번 사용하면 다시 사용할 수 없다.
- 스트림은 내부반복을 통해 반복문을 메서드의 내부에 숨길 수 있다. forEach()는 매개변수에 대입된 람다식을 데이터 소스의 모든 요소에 적용한다.
스트림 연산
스트림의 연산에는 중간연산과 최종연산이 있다. 중간연산은 연산결과가 스트림이므로 스트림에 연속해서 연결할 수 있다. 반면에 최종연산은 스트림의 요소를 소모하면서 연산을 수행하므로 단 한번만 연산이 가능하다.
중간연산 | 설명 |
Stream<T> distinct() | 중복을 제거 |
Stream<T> filter (Predicate<T> predicate) | 조건에 안 맞는 요소 제외 |
Stream<T> limit (long maxSize) | 스트림의 일부를 잘라낸다. |
Stream<T> skip (long n) | 스트림의 일부를 건너뛴다. |
Stream<T> peek (Consumer<T> action) | 스트림의 요소에 작업수행 |
Stream<T> sorted() Stream<T> sorted(Comparator<T> comparator) |
스트림의 요소를 정렬한다. |
Stream<R> map (Function<T, R> mapper) DoubleStream mapToDouble (ToDubleFunction<T> mapper) IntStream mapToInt (ToIntFunction<T> mapper) LongStream mapToLong (ToLongFunction<T> mapper) Stream<R> flatMap (Function<T, Stream<R>> mapper) DoubleStream faltMapToDouble (Function<T, DoubleStream> m) IntStream faltMapToInt (Function<T, IntStream> m) LongStream flatMapToLong (Function<T, LongStream> m) |
스트림의 요소를 변환한다. |
최종 연산 | 설명 |
void forEach (Consumer<? extends T> action) void forEachOrdered (Consumer<? super T> action) |
각 요소에 지정된 작업 수행 |
long count() | 스트림의 요소의 개수 반환 |
Optional<T> max (Comparator<? super T> comparator) Optional<T> min (Comparator<? super T> comparator) |
스트림의 최대값/최소값을 반환 |
Optional<T> findAny() //아무거나 하나 Optional<T> findFirst() //첫 번째 요소 |
스트림의 요소 하나를 반환 |
boolean allMatch (Predicate<T> p) //모두 만족하는지 boolean anyMatch (Predicate<T> p) //하나라도 만족하는지 boolean noneMatch (Predicate<T> p) //모두 만족하는지 않는지 |
주어진 조건을 모든 요소가 만족시키는지, 만족시키지 않는지 확인 |
Object[] toArray( ) | 스트림의 모든 요소를 배열로 반환 |
Optional<T> reduce (BinaryOperator<T> accumulator) T reduce (T identity, BinaryOperator<T> accumulator) U reduce (U identity, BinaryOperator<U,T,U> accumulator, BinaryOperator<U> combiner) |
스트림의 요소를 하나씩 줄여가면서(리듀싱) 계산한다. |
R collect (Collector<T,A,R> collector) R collect (Supplier<R> supplier, BiConsumer<R,T> accumulator, BiConsumer<R,R> combiner) |
스트림의 요소를 수집한다. 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용된다. |
IntStream intStream = IntStream.of(1,1,0,5,9,2,4,6,8,9,2,4,7,8,8,8,4,0);
intStream.distinct() //{1,0,5,9,2,4,6,8,7}
.sorted() //{0,1,2,4,5,6,7,8,9}
.limit(6) //{0,1,2,4,5,6}
.forEach(System.out::println);
위의 예시를 보면 메서드들에 따라 요소들이 어떻게 처리 되는지 알 수 있다.
IntStream intStream = IntStream.range(1,5); //1,2,3,4
intStream.forEach(s->System.out.print(s));
System.out.println();
intStream = IntStream.rangeClosed(1,5);
intStream.forEach(s->System.out.print(s)); //1,2,3,4,5
IntStream과 LongStream은 range()와 rangeClosed() 를 사용해서 지정된 범위의 연속된 정수를 스트림으로 생성할 수 있다. range()는 끝값이 포함되지 않고, ranageClosed()는 포함된다.
IntStream random = new Random().ints(); //무한 스트림
random.limit(20); //무한스트림을 유한스트림으로 만들기
Random클래스에는
- IntStream ints()
- LongStream longs()
- DoubleStream doubles()
와 같은 메서드들이 포함되어 있다. 이 메서드들을 이용하면 해당 타입의 난수들로 이루어진 스트림을 반환하는데 이때 반환된 스트림은 크기가 정해지지 않은 '무한스트림'이므로 limt()메서드를 통해서 스트림의 갯수를 제한시켜 유한스트림으로 만들어서 사용해야한다.
IntStream random = new Random().ints(); //무한 스트림
random.limit(5).forEach(System.out::println); //무한스트림을 유한스트림으로 만들기
System.out.println();
random = new Random().ints(9); //매개변수로 크기를 지정해서 limit()을 사용하지 않아도 됨.
random.forEach(System.out::println);
ints()에 매개변수로 크기를 지정하면 limit()을 사용하지 않아도 된다.
studentStream.sorted(Comparator.comparing(Student::getBan) //반별 정렬
.thenComparing(Comparator.naturalOrder())) //기본정렬
.forEach(System.out :: println);
스트림을 정렬할때 sorted()메서드를 사용한다. 스트림의 요소가 Comparable을 구현하면 Comparator을 지정하지 않아도 스트림 요소의 기본 정렬 기준(Comparable)으로 정렬한다. 정렬 조건을 추가할때는
- thenComparing(Comparator<T> other)
- thenComparing(Comparator<T, U> keyExtractor)
thenComparing()을 사용한다.
Comparator의 static메서드
- naturalOrder()
- reverseOrder()
- comparing(Function<T, U> keyExtractor)
Optional
IntStream과 같은 기본형 스트림의 메서드 중 최종연산의 타입이 Optional인 경우가 있다.
Optional<T>는 'T타입의 객체'를 감싸는 래퍼 클래스이다. 그래서 Optional타입의 객체에는 모든 타입의 참조변수를 담을 수 있다. 최종연산의 결과를 그냥 반환하지 않고 Optional객체에 담아서 반환하는 것이다.
Optional객체 생성하기
String str = "Hello";
Optional<String> opt = Optional.of(str);
Optional<String> opt2 = Optional.ofNullable(null); //null발생 가능성이 있면 of대신 ofNullable을 사용하기.
Optional객체 값 가져오기
String str = "Hello";
Optional<String> opt = Optional.of(str);
Optional<String> opt2 = Optional.ofNullable(null); //null발생 가능성이 있면 of대신 ofNullable을 사용하기.
String result = opt.get(); //get을 사용해서 지정된 값 가져오기
String result2 = opt2.orElse("ddd"); //null인 값을 가져올때는 "ddd"반환
System.out.println(result + "," + result2);
Optional<T>를 반환하는 Stream클래스의 메서드
- Optional<T> findAny()
- Optional<T> findFirst()
- Optional<T> max(Comparator<? super T> comparator)
- Optional<T> min(Comparator<? super T> comparator)
- Optional<T> reduce(BinaryOperator<? super T> accmulator)
OptionalInt, OptionalLong, OptionalDouble
IntStream과 같은 기본형 스트림에는 Optional도 기본값으로 하는 OptionalInt, OptionalLong, OptionalDouble을 반환한다. IntStream에 정의된 메서드들
- OptionalInt findAny()
- OptionalInt findFirst()
- OptionalInt reduce(IntBinaryOperator op)
- OptionalInt max()
- OptionalInt min()
- OptionaDouble average()
클래스 | 값 반환하는 메서드 |
Optional<T> | T get() |
OptionalInt | int getAsInt() |
OptionalLong | long getAsLong() |
OptionalDouble | double getAsDouble() |
Optional<String> optStr = Optional.of("abcde");
Optional<Integer> optInt = optStr.map(String :: length);
System.out.println("optStr="+optStr.get());
System.out.println("optInt="+optInt.get());
int result1 = Optional.of("123").filter(x->x.length()>0).map(x->Integer.parseInt(x)).get();
int result2 = Optional.of("").filter(x->x.length()>0).map(Integer::parseInt).orElse(-1);
System.out.println("result1="+result1);
System.out.println("result2="+result2);
Optional.of("456").map(Integer::parseInt).ifPresent(x->System.out.printf("result3=%d%n",x));
// System.out.println(Optional.of("456").get());
OptionalInt optInt1 = OptionalInt.of(0); //0을 저장
OptionalInt optInt2 = OptionalInt.empty(); //빈 객체를 생성
System.out.println(optInt1.isPresent()); //true
System.out.println(optInt2.isPresent()); //false
System.out.println(optInt1.getAsInt()); //0
// System.out.println(optInt2.getAsInt()); NoSuchElementException
System.out.println("optInt1="+optInt1);
System.out.println("optInt1="+optInt2);
System.out.println("optInt1.equals(optInt2)?"+optInt1.equals(optInt2));
Optional<String> opt = Optional.ofNullable(null); //null을 저장
Optional<String> opt2 = Optional.empty(); //비 객체를 생성
System.out.println("opt="+opt);
System.out.println("opt2="+opt2);
System.out.println("opt1.equals(opt)?"+opt.equals(opt2)); //true
reduce()
스트림 요소를 줄여나가면서 연산을 수행하고 최종결과를 반환한다. 처음 두 요소를 가지고 연산환 결과를 가지고 그 다음 요소와 연산한다. 스트림의 요소를 하나씩 소모하게 되며, 모든 요소를 소모하게 되면 그 결과를 반환한다.
Stream<Integer> intStream = Stream.of(0,1,2,3,4,5,6,7,8,9);
// int count = intStream.reduce(0, (a,b) -> a + 1); //count() : 10
// int sum = intStream.reduce(0,(a,b) -> a + b); //sum() : 45
// int max = intStream.reduce(Integer.MIN_VALUE, (a,b) -> a>b ? a:b); //max() : 9
int min = intStream.reduce(Integer.MAX_VALUE, (a,b) -> a<b ? a:b); //min() : 0
IntStream intStream = IntStream.rangeClosed(0,9);
OptionalInt result = intStream.reduce((a,b) -> a>b ? a:b); //IntStream에 정의된 reduce()의 반환타입이 OptionalInt이기 때문에