Stream을 왜 사용할까?
Stream은 다양한 데이터 소스를 표준화된 방법으로 다루기 위해 사용한다.
'표준화' 라고 하면 '컬렉션 프레임워크'가 떠오를 수 있다.
컬렉션 프레임워크란 다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화된 방법을 제공하는 클래스 집합이다.
컬렉션 프레임워크에는 List, Set, Map 인터페이스가 존재하는데 List와 Set은 Collection 인터페이스를 상속받지만, Map 인터페이스는 별도로 정의된다.
List, Set과 Map의 인터페이스가 다르니 사용방법도 다르다. 결국 컬렉션 프레임워크의 표준화는 다소 아쉬운점이 있다.
이때 등장한 것이 바로 Stream이다. (JDK 1.8에 등장) 덕분에 우리는 다양한 데이터 소스(컬렉션, 배열)를 Stream 형태로 만들 수 있고, 이는 곧 다양한 데이터를 동일한 작업으로 처리하는게 가능함을 의미한다. Stream 덕분에 진정한 의미의 표준화가 이루어졌다고 볼 수 있는 것이다.
Stream 생성 방법
① T.stream( )
• Collection 인터페이스를 구현한 클래스에서 제공하는 메서드로, List나 Set 같은 컬렉션에서 스트림을 생성할 때 사용
(자세한 사용 방법은 'Stream 작업 순서' 목록 참고)
② stream.of( )
• 주어진 요소들을 스트림으로 만들어주는 정적 메서드
• 가변 인자를 받아서 스트림을 생성할 수 있음
• 컬렉션이 아닌 개별 요소들을 스트림으로 만들 때 유용함
public class StreamOfExample {
public static void main(String[] args) {
// 개별 요소들을 스트림으로 생성
Stream<String> stream = Stream.of("apple", "banana", "orange");
}
}
③ Arrays.stream( )
• 배열을 스트림으로 변환할 때 사용하는 정적 메서드
• 배열 전체를 스트림으로 변환하거나 배열의 특정 부분을 스트림으로 변환할 수 있음
Stream 작업 순서
① Stream 만들기
• List → Stream 변환
public class ListToStream {
public static void main(String[] args) {
List<String> list = Arrays.asList("apple", "banana", "orange");
// 리스트에서 스트림 생성
Stream<String> stream = list.stream();
}
}
• Set → Stream 변환
public class SetToStream {
public static void main(String[] args) {
Set<String> set = new HashSet<>(Arrays.asList("apple", "banana", "orange"));
// 셋에서 스트림 생성
Stream<String> stream = set.stream();
}
}
• Map → Stream 변환
public class MapToStream {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("orange", 3);
// 키 스트림 생성
Stream<String> keyStream = map.keySet().stream();
// 값 스트림 생성
Stream<Integer> valueStream = map.values().stream();
// 엔트리 스트림 생성
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
}
}
🔹Entry란?🤔
엔트리(Entry)는 자바의 Map 인터페이스에서 사용되는 용어로, 키와 값의 쌍을 의미한다.
자바에서는 Map.Entry 인터페이스를 통해 이러한 엔트리를 나타내며, Map.Entry는 키와 값에 접근할 수 있는 메서드를 제공한다.
Map.Entry의 주요 메서드:
• getKey( ): 엔트리의 키를 반환
• getValue( ): 엔트리의 값을 반환
• setValue(V value): 엔트리의 값을 지정된 값으로 변경
• entrySet( ): Map에 있는 모든 엔트리의 집합을 얻을 수 있음. ( Set<Map.Entry<K, V>> 형식 )
• 배열 → Stream 변환
public class ArrayToStream {
public static void main(String[] args) {
String[] array = {"apple", "banana", "orange"};
// 배열에서 스트림 생성
Stream<String> stream = Arrays.stream(array);
}
}
② 중간 연산
• 연산 결과가 Stream인 연산
• 중간 연산은 여러차례 수행 가능하다.
• ex) filter( ), map( ), sorted( )
③ 최종 연산
• 연산 결과가 Stream이 아닌 연산
• 최종 연산은 1번만 수행 가능하다. 추가적인 연산을 수행하고 싶다면 Stream을 다시 만들어야한다.
• ex) forEach( ), collect( ), reduce( )
Stream의 특징
① Stream은 원본 데이터를 변경하지 않는다.
List<String> list = Arrays.asList("apple", "banana", "orange");
// 스트림을 사용하여 대문자로 변환 (원본 리스트는 변경되지 않음)
List<String> upperCaseList = list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// 결과 출력
System.out.println("원본 List: " + list); //[apple, banana, orange]
System.out.println("스트림 사용 List: " + upperCaseList); //[APPLE, BANANA, ORANGE]
② Stream은 일회용이다.
최종 연산 후 스트림이 필요할 경우 다시 스트림을 생성해야한다.
strStream.forEach(System.out::println); //.forEach()로 최종 연산 수행 완료. 스트림 닫힘.
int numOfStr=strStream.count(); // 에러 발생
③ 기본형 스트림을 제공한다. - IntStream, LongStream, DoubleStream
기본형 스트림은 숫자와 관련된 유용한 메서드를 Stream<T> 보다 많이 제공한다.
이유는 Stream<T>은 T에 어떤 요소가 들어올지 알 수 없지만, 기본형 스트림의 타입은 숫자로 정해져있다.
따라서 기본형 스트림이 숫자에 관련된 메서드를 더 많이 제공하는 것이다.
또한, Stream<T>에 정수 배열을 넣을 경우 각 int 타입 정수를 Integer 타입으로 박싱해줘야한다.
그리고 계산을 할 때는 Integer 타입을 다시 int 타입으로 언방식 해줘야한다.
약간의 자료를 처리하는데는 큰 문제가 없겠지만, 빅데이터처럼 자료가 방대한 경우에는 박싱&언박싱에 소요되는 시간이 그만큼 늘어나기 때문에 상당히 비효율적이다.
따라서 데이터소스가 기본형인 경우에는 기본형 스트림을 사용하는게 더 효율적이다.
Stream의 더 많은 특징은 아래 자료를 참고:
'TIL > JAVA' 카테고리의 다른 글
[TIL] DI(의존성 주입)와 IoC(제어의 역전) (0) | 2024.11.29 |
---|---|
[TIL] 람다식 (with. 익명 클래스, 익명 함수) (0) | 2024.11.27 |
[TIL] Comparator와 Comparable (0) | 2024.11.25 |
[TIL] 생긴게 똑같으면 같은거 아닌가요?🤔 (동일성 vs 동등성) (1) | 2024.11.21 |
[TIL] Getter와 Setter를 사용하는 이유를 항상 고민하자 (2) | 2024.11.14 |