자바에는 데이터를 가공하는방법이 여러가지가있다. collection을 쓴다거나 배열이나 리스트를 쓴다거나 데이터를 가공하는 여러가지 방법이 있다. 그중에 Java 8 부터 사용이 가능한 야무진 Stream에 대해서 알아보자~ 코테준비하면서 Stream만 쓰는 한줄좌가 늘 있었는데 이거 진짜 편해보이긴하더라구요 ㅎㅎ 그래서 정리하면서 배울라구요
stream
은 실제 데이터 처리의 이상화된 흐름을 의미한다. 예를들어 내가 택배를 배송받을때에는 집화처리→간선상차→간선하차→배송출고→배송완료 순으로 도착하듯이 있듯이 자바에서 stream은 데이터를 처리하는 중간매개자 역할을 한다고 보면된다!
먼저 스트림의 동작 흐름을 알아두면 좋은데
순으로 진행이 된다. 약간 데이터를 처리하려고 포장하는느낌? 이 Stream을 잘 쓴다면 데이터를 처리하는부분은 이 API를 사용하면 될정도로 기능이 다양하고 유용하다! 이제 쓰는방법을 알아보자~
String[] myArray = { "apple", "banana", "orange", "grape", "watermelon" };
Stream<String> myStream = Arrays.stream(myArray);
IntStream intStream = IntStream.range(1, 5);
// [1, 2, 3, 4]
LongStream longStream = LongStream.rangeClosed(1, 5);
// [1, 2, 3, 4, 5]
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0, 4.0, 5.0); // [1.0, 2.0, 3.0, 4.0, 5.0)]
여기서 코테풀때 유용하게 쓰이는건
IntStream stream = "Hello,World".chars(); //(72, 101, 108, 108, 111, 44, 87, 111, 114, 108, 100)
이건듯? chars() 메소드를 사용해서 문자열의 문자를 IntStream으로 변환한것
List<String> strList = Arrays.asList("apple", "orange", "banana");
// 람다식을 이용한 경우
strList.forEach(str -> System.out.println(str));
// 메서드 레퍼런스를 이용한 경우 (:: 연산자 사용)
strList.forEach(System.out::println);
표현방법은 두가지인데 람다식으로 사용하거나 ::
연산자를 사용해서 메서드를 참조하면된다!
public interface Stream<T> extends BaseStream<T, Stream<T>> {
// 주어진 조건에 따라 요소를 걸러냅니다.
Stream<T> filter(Predicate<? super T> predicate);
// 각 요소를 다른 요소로 변환합니다.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
// 요소를 int로 변환한 IntStream을 반환합니다.
IntStream mapToInt(ToIntFunction<? super T> mapper);
// 요소를 long으로 변환한 LongStream을 반환합니다.
LongStream mapToLong(ToLongFunction<? super T> mapper);
// 요소를 double로 변환한 DoubleStream을 반환합니다.
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
// 각 요소를 다른 스트림으로 변환하고 평탄화시킵니다.
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
// 요소를 int로 변환한 IntStream을 반환하고 평탄화시킵니다.
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
// 요소를 long으로 변환한 LongStream을 반환하고 평탄화시킵니다.
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
// 요소를 double로 변환한 DoubleStream을 반환하고 평탄화시킵니다.
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
// 중복된 요소를 제거한 스트림을 반환합니다.
Stream<T> distinct();
// 요소를 정렬된 순서로 스트림을 반환합니다.
Stream<T> sorted();
// 지정된 비교자에 따라 요소를 정렬한 스트림을 반환합니다.
Stream<T> sorted(Comparator<? super T> comparator);
// 각 요소를 확인하거나 디버깅을 위해 사용됩니다.
Stream<T> peek(Consumer<? super T> action);
// 최대 크기를 지정하여 스트림의 크기를 제한합니다.
Stream<T> limit(long maxSize);
// 처음 n개의 요소를 건너뜁니다.
Stream<T> skip(long n);
...
}
종류가 엄청많죠? 중간연산은 보통 람다식을 혼합해서 쓰는경우가 많이 있다. 반환값으론 stream을 리턴하기때문에 이어서 호출하기 유용하다. 그 후 모든 중간연산을 합치고 마지막에 한번에 처리한다. = 지연(lazy)연산을 한다!
List<String> myList = Arrays.asList("apple", "banana", "orange", "grape", "watermelon");
Stream<String> filteredStream = myList.stream().filter(s -> s.startsWith("a"));
// "apple"
myList를 생성한다음 filter()
메소드안에 람다식을 통해 "a"로 시작하는 문자열만 filteredStream에 담는다 즉 apple만 담기겠죠?
List<String> myList = Arrays.asList("apple", "banana", "orange", "grape", "watermelon");
Stream<String> mappedStream = myList.stream().map(String::toUpperCase);
// "APPLE", "BANANA", "ORANGE", "GRAPE", "WATERMELON"
이 외에도 특정 데이터타입으로 변환할때도 쓰이는데 방법을 보여주자면
List<String> numbersAsString = Arrays.asList("1", "2", "3", "4", "5");
// 문자열을 정수로 변환하는 예시
IntStream intStream = numbersAsString.stream()
.mapToInt(Integer::parseInt);
// 정수로 변환된 스트림 요소들을 합산하는 예시
int sum = numbersAsString.stream()
.mapToInt(Integer::parseInt)
.sum();
주로 Integer인 arraylist를 int배열로 바꾸거나 할때 쓰이기도한다!
list.stream()
.mapToInt(i->i)
.toArray();
람다를 활용하자면 요로코롬 말이죠
List<String> myList = Arrays.asList("apple", "banana", "orange", "grape", "watermelon");
Stream<String> sortedStream = myList.stream().sorted();
//"apple", "banana", "grape", "orange", "watermelon"
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 처음 5개의 요소를 가져오는 스트림
List<Integer> limitedList = numbers.stream()
.limit(5)
.collect(Collectors.toList());
//[1, 2, 3, 4, 5]
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 처음 5개의 요소를 건너뛴 스트림
List<Integer> skippedList = numbers.stream()
.skip(5)
.collect(Collectors.toList());
//[6, 7, 8, 9, 10]
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4, 5, 5);
// 중복을 제거한 요소로 이루어진 스트림
List<Integer> distinctList = numbers.stream()
.distinct()
.collect(Collectors.toList());
//[1, 2, 3, 4, 5]
public interface Stream<T> extends BaseStream<T, Stream<T>> {
// 각 요소에 대해 주어진 작업을 수행합니다.
void forEach(Consumer<? super T> action);
// 요소의 순서가 유지된 상태로 각 요소에 대해 주어진 작업을 수행합니다.
void forEachOrdered(Consumer<? super T> action);
// 스트림의 요소를 배열로 변환하여 반환합니다.
Object[] toArray();
// 지정된 생성자로 배열을 생성하여 스트림의 요소를 반환합니다.
<A> A[] toArray(IntFunction<A[]> generator);
// 스트림의 요소를 줄여 하나의 값을 반환합니다.
T reduce(T identity, BinaryOperator<T> accumulator);
// 스트림의 요소를 반복적으로 처리하여 최종 값을 반환합니다.
Optional<T> reduce(BinaryOperator<T> accumulator);
// 병렬 실행 시 스트림의 요소를 반복적으로 처리하고 병합하여 최종 값을 반환합니다.
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
// 스트림의 요소를 수집하여 결과를 반환합니다.
<R, A> R collect(Collector<? super T, A, R> collector);
// 지정된 비교자에 따라 스트림의 최소 요소를 찾아 반환합니다.
Optional<T> min(Comparator<? super T> comparator);
// 지정된 비교자에 따라 스트림의 최대 요소를 찾아 반환합니다.
Optional<T> max(Comparator<? super T> comparator);
// 스트림의 요소 개수를 반환합니다.
long count();
// 스트림의 요소 중 주어진 조건에 부합하는 요소가 하나라도 있는지 확인합니다.
boolean anyMatch(Predicate<? super T> predicate);
// 스트림의 모든 요소가 주어진 조건을 만족하는지 확인합니다.
boolean allMatch(Predicate<? super T> predicate);
// 스트림의 모든 요소가 주어진 조건을 만족하지 않는지 확인합니다.
boolean noneMatch(Predicate<? super T> predicate);
// 스트림에서 첫 번째 요소를 찾아 반환합니다.
Optional<T> findFirst();
// 스트림에서 아무 요소나 찾아 반환합니다.
Optional<T> findAny();
// ...
}
중간연산은 stream을 리턴하는 반면 최종연산은 stream을 리턴하지않아서 메소드를 연결할수없다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 각 요소에 대해 작업을 수행
names.stream().forEach(System.out::println);
근데 forEach
는 연산순서를 보장하지않기때문에 다수의 스트림을 사용할 때 순서를 보장받고싶다면 forEachOrdered
를 사용하면된다!
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// String 배열로 변환
String[] namesArray = names.stream().toArray(String[]::new);
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 모든 요소의 합을 구함
int sum = numbers.stream().reduce(0, Integer::sum);
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 리스트로 수집
List<String> collectedNames = names.stream()
.collect(Collectors.toList());
// 집합으로 수집
Set<String> collectedSet = names.stream()
.collect(Collectors.toSet());
// 문자열로 결합
String concatenatedNames = names.stream()
.collect(Collectors.joining(", "));
List<Integer> numbers = Arrays.asList(3, 1, 5, 2, 4);
// 최소값 찾기
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
// 최대값 찾기
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
List<Integer> numbers = Arrays.asList(3, 1, 5, 2, 4);
// 조건에 부합하는 요소가 하나라도 있는지 확인
boolean anyGreaterThanFive = numbers.stream().anyMatch(num -> num > 5);
// 모든 요소가 조건을 만족하는지 확인
boolean allGreaterThanZero = numbers.stream().allMatch(num -> num > 0);
// 조건을 만족하는 요소가 없는지 확인
boolean noneGreaterThanTen = numbers.stream().noneMatch(num -> num > 10);
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 첫 번째 요소 찾기
Optional<String> first = names.stream().findFirst();
// 아무 요소나 찾기
Optional<String> any = names.stream().findAny();
stream의 가장 큰 장점은 가독성도 높고 잘 알고 쓸수록 다양하게 표현할 수 있는 좋은 API인듯! Java 8 이니까 실무에서도 쓰면 좋지않을까? 하는생각이 들기도한다~ 잘 써봐야지 ㅎㅎ