stream 이란 ?
스트림이란 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓았다. 여기서 데이터 소스를 추상화 하였다는 것은, 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게 되었다는 것과 코드의 재 사용성이 높아진다는 것을 의미 한다..
(스트림을 이용하면, 배열이나 Collection 뿐만 아니라 파일에 저장된 데이터도 모두 같은 방식으로 다룰 수 있다.)
0. 스트림의 표준 함수형 인터페이스 요약
- Runnable : 매개변수가 없고 반환 값이 없을 때
- Supplier<T> : 매개변수가 없고 반환값이 있을 때
- Consumer<T> : 매개변수가 있고 반환값이 없을 때
- Function<T, R> : 매개변수가 있고 반환값이 있을 때
- Predicate<T> : 매개변수가 있고 반환 값이 boolean Type 일때
스트림 객체를 사용하는 방법을 알아보자
1. 문자열 배열 및 문자열 컬렉션(List) 를 스트림(stream) 을 사용하여 콘솔에 출력 하는 방법
public class StreamEx1 {
public static void main(String[] args) {
String[] strArr = {"aaa", "ccc", "bbb"};
List<String> strList = Arrays.asList(strArr);
// System.out.println("strList = " + strList); // strList = [aaa, ccc, bbb]
// 위의 데이터 소스를 기반으로 하는 스트림 생성
Stream<String> strStream1 = strList.stream();
System.out.println("strStream1 = " + strStream1); // Array 스트림의 참조값
Stream<String> strStream2 = Arrays.stream(strArr);
System.out.println("strStream2 = " + strStream2); // List 스트림의 참조값
/*strStream1.sorted().forEach(System.out::print);
System.out.println();
strStream2.sorted().forEach(System.out::print);*/
strStream1.sorted().forEach(s -> System.out.print(s + " "));
System.out.println();
strStream2.sorted().forEach(s -> System.out.print(s + " "));
}
}
- 문자열 배열 strArr 와 해당 문자열을 컬렉션 객체 strList 에 담았다.
이 두 데이터를 stream 으로 변환 하려면 먼저 Stream 객체를 제네릭 타입<String> 으로 선언하고 인스턴스를 생성한다.
( 배열 : strStream1, 컬렉션 : strStream2) 으로 선언
두 인스턴스를 먼저 sorted() 메서드를 통해 정렬 시키고, forEach 메서드를 사용하여 람다식으로 변환 후 출력한다.
참고로 위의 주석처리된 부분은 stream 객체의 공백 없이 람다식의 메서드 참조 형식을 사용하여 출력하는 방식이다.
※ 주의 : 스트림은 일회용이다.
스트림은 Iterator 처럼 일회용이다. Iterator 로 컬렉션의 요소를 모두 읽고 나면 다시 사용할 수 없는 것처럼, 스트림도 한번 사용하면 닫혀서 다시 사용할 수 없다. 필요하다면 스트림(Stream) 을 다시 생성해야 한다.
package study.stream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamEx1 {
public static void main(String[] args) {
String[] strArr = {"aaa", "ccc", "bbb"};
List<String> strList = Arrays.asList(strArr);
// System.out.println("strList = " + strList); // strList = [aaa, ccc, bbb]
// 위의 데이터 소스를 기반으로 하는 스트림 생성
Stream<String> strStream1 = strList.stream();
System.out.println("strStream1 = " + strStream1); // Array 스트림의 참조값
Stream<String> strStream2 = Arrays.stream(strArr);
System.out.println("strStream2 = " + strStream2); // List 스트림의 참조값
strStream1.sorted().forEach(System.out::print);
System.out.println();
strStream2.sorted().forEach(System.out::print);
strStream1.sorted().forEach(s -> System.out.print(s + " "));
System.out.println();
strStream2.sorted().forEach(s -> System.out.print(s + " "));
}
}
(위의 코드와 같이 스트림을 재사용하게 되면 아래와 같이 JVM 이 Runtime 에러를 띄운다.)
2. 스트림은 작업을 내부 반복으로 처리한다.
스트림을 이용한 작업이 간결한 이유는 '내부 반복' 이다. 내부 반복이라는 것은 반복문을 메서드의 내부에 숨길 수 있다는 것을 의미한다.
(아래는 forEach() 문을 메서드 내부로 넣은 예시이다.)
public class StreamEx2 {
public static void main(String[] args) {
String[] strArr = {"aaa", "bbb", "ccc"};
// 배열 내부의 데이터를 for 문을 사용하여 출력
for (String string : strArr) {
System.out.print(string + " ");
}
System.out.println();
// 스트림을 사용한 배열의 데이터를 Collection 변환 + 정렬 후 출력
Stream<String> stream = Arrays.stream(strArr).sorted();
stream.forEach(System.out::print);
}
}
3. Stream 의 중간 연산 메서드 (forEach() 메서드가 수행되기 전까지의 메서드는 중간 연산)
- 지연된 연산 : 스트림 연산에서 중간 연산 메서드를 추가한다고 해서 중간연산이 되는 것이 아니다
중간연산 메서드 선언 후 최종 연산 메서드를 호출해야 중간에 선언한 메서드가 실행되는 것이다.
- distinct() : 중복 제거
- filter() : 조건에 안 맞는 요소 제외
- skip() : 스트림의 일부를 잘라낸다
- peek() : 스트림의 일부를 건너뛴다
- sorted() : 스트림 요소를 정렬
public class StreamEx3 {
public static void main(String[] args) {
String[] strArr = {"dd", "aaa", "CC", "ccc", "b", "b", "aaa"};
// Stream.of() : 문자열 배열 strArr 를 Stream 객체로 변환하는 메서드
Stream<String> stream = Stream.of(strArr); // 문자열이 배열이 소스인 스트림
// stream.sorted().forEach(System.out::print);
// filter() : stream 배열에서 조건을 수행하여 조건에 만족하는 값만 출력하는 코드
Stream<String> filterdStream = stream.filter(s -> s.length() > 1);
filterdStream.forEach(System.out::print);
System.out.println();
// distinct() : 주어진 데이터의 중복을 제거 하는 메서드
/* filter() 메서드를 수행하면서 생성된 stream 객체를 한번 사용 했기 때문에
newStream1 라는 스트림 객체를 다시 생성 했다.*/
Stream<String> newStream1 = Stream.of(strArr);
Stream<String> distinctedStream = newStream1.distinct();
distinctedStream.forEach(System.out::print);
System.out.println();
// sorted() : 스트림 객체를 정렬
Stream<String> newStream2 = Stream.of(strArr);
Stream<String> sortededStream = newStream2.sorted();
sortededStream.forEach(System.out::print);
System.out.println();
// limit() : 배열의 요소중 '0부터 n-1' 까지 출력하는 메서드
// ex) limit(4) 를 입력하면 0부터 3까지의 인덱스의 value 3개를 출력한다.
Stream<String> newStream3 = Stream.of(strArr);
Stream<String> limitedStream = newStream3.limit(3);
limitedStream.forEach(System.out::print);
System.out.println();
// count() : 요소의 개수 세기
Stream<String> newStream4 = Stream.of(strArr);
long total = newStream4.count();
System.out.println("total = " + total);
}
}
4. Stream 의 최종 연산 메서드 (forEach() 등이 수행 후 Stream 은 삭제되며 다시 생성 해야 한다)
- forEach() : 각 요소에 지정된 작업 수행
- count() : 스트림 요소의 개수 반환
- Optional<T> max() : 스트림의 최대 값을 반환
- Optional<T> min() : 스트림의 최소 값을 반환
- Optional<T> reduce() : 스트림의 요소를 하나씩 줄여 가면서 계산한다.
- R collect() : 스트림의 요소를 수집한다. >> 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용
1) .of : 배열 및 Collection 객체를 Stream 객체로 변환하는 메서드
// Collection 객체를 Stream 으로 변환
Stream<List<String>> list1 = Stream.of(list);
2) filter() : 스트림 내부에 조건을 수행하여 해당 조건을 만족하는 데이터를 출력하기위한 메서드
3) distinct() : 데이터의 중복을 제거
4) sorted() : 내부의 데이터를 정렬 (Arrays.sort() 또는 Collections.sort() 와 같은 개념)
5) limit() : 스트림 배열 및 Collection 내부의 요소중 0부터 n-1 까지 출력하는 메서드
ex ) limit() 메서드 동작 방식
limit(n) 은 스트림에서 최초의 'n' 개의 요소만을 반환한다. 여기서 n 은 포함할 요소의 수이다.
n 이 스트림의 전체 요소 개수보다 크면, 스트림의 모든 요소를 반환한다.
6) count() : 스트림 배열 및 Collection 내부의 인덱스 개수를 반환