📖 Java의 Stream은 컬렉션(Collection)의 요소롤(element)를 하나씩 참조하여 순차적으로 요소에 접근하는 메소드이다.
↳ 요소를 하나씩 참조하여, 람다식(함수형 인터페이스)으로 처리할 수 있도록 해주는 일종의 반복자
↳ java.io 패키지는 Stream API와는 관련이 없으며, Stream API는 java.util 패키지 하위에 있는 API이다.
📖 스트림은 외부 반복을 통해 작업하는 Collection과는 달리 내부 반복을 통해 작업을 수행한다.
📖 스트림은 재사용이 가능한 컬렉션과는 달리 단 한번만 사용할 수 있다.
↳ 아래 코드와 같이 nameStream 객체를 한 번 사용하는 경우, 반복문 종료와 함게 사용이 불가능하다.
String[] nameArr = {"IronMan", "Captain", "Hulk", "Thor"}
List<String> nameList = Arrays.asList(nameArr);
Stream<String> nameStream = nameList.stream();
nameStream.sorted().forEach(System.out::println);
int count = nameStream.count();
/* 아래의 에러가 발생하게 된다.
java.lang.IllegalStateException:
stream has already been operated upon or closed
*/
📖 스트림은 원본데이터를 변경하지 않는다.
↳ Stream API는 원본의 데이터를 조회하여 원본의 데이터가 아닌 별도의 요소들로 Stream을 생성한다.(원본의 데이터를 읽기만 함.)
📖 스트림의 연산은 필터-맵(filter-map) 기반의 API를 사용하여 지연(lazy) 연산을 통해 성능을 최적화한다.
↳ Stream의 연산을 위해서는 Stream 객체를 생성해주어야 한다.
Stream은 컬렉션, 배열, 가변매개변수, 지정된 범위의 연속된 정수, 난수, 람다식, 파일, 빈스트림에서 생성이 가능하다.
다만, Stream연산시 연산이 끝나면 Stream이 닫히기 때문에 Stream이 닫혔을 경우 다시 Stream을 생성해야 한다.
// 1. Collection (ArrayList)
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(4);
list.add(2);
list.add(3);
list.add(1);
Stream<Integer> stream = list.stream();
// forEach() 메소드를 이용한 스트림 요소의 순차 접근
stream.forEach(System.out::println);
String[] arr = new String[]{"넷", "둘", "셋", "하나"};
// 2. 배열에서 스트림 생성
Stream<String> stream1 = Arrays.stream(arr);
stream1.forEach(e -> System.out.print(e + " "));
System.out.println();
// 배열의 특정 부분만을 이용한 스트림 생성
Stream<String> stream2 = Arrays.stream(arr, 1, 3);
stream2.forEach(e -> System.out.print(e + " "));
// 3. 람다 표현식
IntStream stream = Stream.iterate(2, n -> n + 2); // 2, 4, 6, 8, 10, ...
↳ 원본의 데이터를 별도의 데이터로 가공하기 위한 중간 연산 단계이다.
↳ 스트림 API에 의해 생성된 초기 스트림은 중개연산을 통해 또 다른 스트림으로 변환되는데, 반환되는 값도 스트림이기 때문에 계속해서 중개 연산을 이어갈 수 있다.
- filter(), distinct(), map(), skip(), limit(), sorted(), peek()
필터링 : filter(), distinct()
1-1. filter()
↳ 주어진 조건에 맞는 요소만 구성된 새로운 스트림을 반환.
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList.stream().filter(s -> s.startWith("c");
1-2. distinct()
↳ 해당 스트림에 중복된 요소가 제거된 새로운 스트림을 반환.
(내부적으로 Object 클래스의 equals() 메소드를 사용하여 중복을 비교한다.)
List<String> myList = Arrays.asList("a1", "a1", "b1", "c2", "c1");
myList.stream().distinct();
변환 : map(), flatMap()
2-1. map()
↳ 해당 스트림에 요소들을 특정 조건에 해당하는 값으로 변환
Stream<String> stream = Stream.of("HTML", "CSS", "JAVA", "JAVASCRIPT");
stream.map(s -> s.length()).forEach(System.out::println);
제한 : limit(), skip()
3-1. limit()
↳ 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림을 반환.
IntStream stream1 = IntStream.range(0, 10);
stream1.limit(5).foreach(n -> System.out.println(n + " "));
// 0 1 2 3 4
3-2. skip()
↳ 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소를 제외한 나머지 요소만으로 이루어진 새로운 스트림을 반환.
IntStream stream1 = IntStream.range(0, 10);
stream1.skip(4).foreach(n -> System.out.println(n + " "));
// 4 5 6 7 8 9
정렬 : sorted()
4-1. sorted()
↳ 해당 스트림을 주어진 비교자(comparator)를 이용하여 정렬한다.(default : 사전 편찬순)
Stream<String> stream1 = Stream.of("JAVA", "HTML", "CSS", "JAVASCRIPT");
stream1.sorted.foreach(System.out::println);
/*
CSS
HTML
JAVA
JAVASCRIPT
*/
연산 결과 확인 : peek()
5-1. peek()
↳ 결과 스트림으로부터 요소를 소모하여 추가로 명시된 동작을 수행.
주로, 원본 스트림에서 요소를 소모하지 않으므로 연산과 연산 사이에 결과를 확인하고 싶을 때 사용한다.
IntStream stream = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
stream.peek(s -> System.out.println("원본 스트림 : " + s))
.skip(2)
.peek(s -> System.out.println("skip(2) 실행 후 : " + s))
.limit(5)
.peek(s -> System.out.println("limit(5) 실행 후 : " + s))
.sorted()
.peek(s -> System.out.println("sorted() 실행 후 : " + s))
.forEach(n -> System.out.println(n));
- forEach(), reduce(), findFirst(), findAny(), anyMatch(), allMatch(), noneMatch(),
count(), min(), max(), sum(), average(), collect()
Stream<String> stream1 = Stream.of("넷", "둘", "셋", "하나");
Optional<String> result = stream1.reduce(s1,s2 -> s1 + "+" + s2);
// 넷+둘+셋+하나
개인적으로 공부하며 기록한 내용으로, 틀린 내용이 있는 경우 덧글을 달아주시면 감사하겠습니다. 😍