Sream
스트림(Stream)은 자바에서 컬렉션, 배열 등의 데이터를 효율적으로 처리하기 위해 제공되는 API(고유한 기능을 가진 모든 소프트웨어)로, 데이터를 필터링하거나, 변환, 정렬, 집계 등의 작업을 할 때 유용합니다.
특징
1. 선언형 프로그래밍 : 스트림을 통해 데이터를 다루면 for 문이나 if 문 같은 반복과 조건문 없이, 선언형으로 데이터 처리 작업을 기술할 수 있습니다. 예를 들어, filter(), map(), collect() 등을 사용해 데이터 흐름을 간결하게 정의합니다.
2. 데이터 흐름 : 스트림은 데이터를 반복(iterate)하며 한 번에 하나씩 처리합니다. 따라서 스트림을 사용하면 데이터를 일종의 파이프라인으로 흘려보내며 중간의 연산과 최종 연산을 수행하게 됩니다.
3. 중간 연산과 최종 연산
- 중간 연산은 스트림을 변환하며, 결과적으로 새로운 스트림을 반환합니다. 예) filter(), map(), sorted()
- 최종 연산은 스트림을 소비하고, 결과를 반환하거나 특정 작업을 완료합니다. 예) collect(), foreach(), count()
4. 레이지 연산(lazy evaluation) : 스트림은 최종 연산이 호출될 때까지 계산을 미룹니다. 이로 인해 불필요한 연산을 피하고, 최적의 퍼포먼스를 발휘합니다.
5. 무상태 연산 : 스트림 연산은 대부분 데이터를 변경하지 않고, 기존 컬렉션이나 배열을 그대로 둔
6. 내부반복자 : for이나 iterator 같은 외부반복자는 순차적(단건)으로 처리하지만, 내부반복자인 stream은 병렬처리를 하기 때문에 더 효율적으로 요소에 대한 처리를 할 수 있다.
예제
1) hashset -> int[] 로 변환
Set<Integer> set = new HashSet<>(Set.of(1, 2, 3, 4, 5));
// Set<Integer>를 int[]로 변환
int[] arr = set.stream()
.mapToInt(Integer::intValue) // Integer를 int로 변환
.toArray();
2) map 에서 value를 기준으로 정렬하기1
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 20);
map.put(2, 40);
map.put(3, 30);
// 값을 기준으로 오름차순 정렬된 Map 생성
Map<Integer, Integer> sortedMap = map.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue()) // 값을 기준으로 정렬
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1, // 중복 키 병합 전략
LinkedHashMap::new // 순서 유지
));
System.out.println("값 기준 오름차순 정렬: " + sortedMap);
// 값을 기준으로 내림차순 정렬된 Map 생성
Map<Integer, Integer> sortedMapDesc = map.entrySet()
.stream()
.sorted(Map.Entry.<Integer, Integer>comparingByValue().reversed()) // 내림차순 정렬
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
System.out.println("값 기준 내림차순 정렬: " + sortedMapDesc);
3) map 에서 value의 max값 찾기
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 20);
map.put(2, 40);
map.put(3, 40);
// Map의 가장 큰 값 찾기
int maxValue = Collections.max(map.values());
// 가장 큰 값을 가진 키들만 추출
List<Integer> topKeys = map.entrySet()
.stream()
.filter(entry -> entry.getValue() == maxValue) // 가장 큰 값과 같은 키 찾기
.map(Map.Entry::getKey) // 키만 추출
.collect(Collectors.toList()); // List로 수집
System.out.println("1등: " + topKeys); // [2, 3] 출력
4) map을 value 기준으로 정렬하기2
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 2);
map.put("cherry", 5);
map.put("date", 1);
// Map을 값 기준으로 정렬
Map<String, Integer> sortedMap = map.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue()) // 값 기준으로 정렬
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1, // 중복 키 병합 전략
LinkedHashMap::new // 순서 유지
));
System.out.println("Sorted Map: " + sortedMap);
5) int배열을 -> treeset 변환
int[] arr = {5, 3, 1, 4, 2};
// int[] 배열을 TreeSet으로 변환
Set<Integer> treeSet = Arrays.stream(arr) // int형 스트림 생성
.boxed() // Integer 객체 스트림으로 변환
.collect(Collectors.toCollection(TreeSet::new));
System.out.println("TreeSet: " + treeSet); // 출력: [1, 2, 3, 4, 5]
6) stream을 이용한 set 병합
Set<Integer> set1 = new HashSet<>(Set.of(1, 2, 3));
Set<Integer> set2 = new HashSet<>(Set.of(3, 4, 5));
// Stream으로 두 Set 병합
Set<Integer> mergedSet = Stream.concat(set1.stream(), set2.stream())
.collect(Collectors.toSet());
System.out.println("Merged Set: " + mergedSet); // 출력: [1, 2, 3, 4, 5]
7) TreeSet -> int배열 변환
// Set 생성 및 초기화
Set<Integer> set = new TreeSet<>(Set.of(5, 3, 1, 4, 2)); // 자동 정렬됨
// Set을 int 배열로 변환
int[] arr = set.stream()
.mapToInt(Integer::intValue) // Integer -> int 변환
.toArray(); // int 배열로 변환
System.out.println(Arrays.toString(arr)); // 출력: [1, 2, 3, 4, 5]
8) 배열 -> List로 변환
int[] arr = {1, 2, 3, 4, 5};
// 배열을 컬렉션으로 변환하고 출력
List<Integer> list = Arrays.stream(arr)
.boxed() // int를 Integer로 변환
.toList(); // List<Integer>로 변환
list.iterator().forEachRemaining(System.out::print); // 출력: 12345
9) Collectors 메소드
1. toList() / toSet()
toList(): 스트림의 요소를 List로 수집합니다. 기본으로 ArrayList를 반환합니다.
toSet(): 스트림의 요소를 Set으로 수집합니다. 기본으로 HashSet을 반환합니다.
ex)
List<Integer> list = stream.collect(Collectors.toList());
Set<Integer> set = stream.collect(Collectors.toSet());
2. toMap()
toMap(Function keyMapper, Function valueMapper): 스트림의 요소를 Map으로 수집합니다. 키와 값을 지정할 수 있으며, 기본으로 HashMap을 반환합니다.
toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction): 동일한 키가 존재할 때 값을 병합하는 방법을 지정합니다.
toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction, Supplier mapSupplier): Map의 구체적인 타입을 지정할 수 있습니다 (예: TreeMap).
ex)
Map<Integer, String> map = stream.collect(Collectors.toMap(
keyMapper, valueMapper
));
3. toCollection()
특정 Collection 타입으로 수집할 때 사용합니다. 예를 들어 TreeSet, LinkedList 등 특정 타입을 지정할 수 있습니다.
ex)
Set<Integer> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));
List<Integer> linkedList = stream.collect(Collectors.toCollection(LinkedList::new));
4. joining()
문자열 스트림을 하나의 문자열로 합칠 때 사용합니다.
joining(): 기본적으로 구분자 없이 합칩니다.
joining(CharSequence delimiter): 지정된 구분자로 합칩니다.
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix): 구분자, 접두사, 접미사를 지정할 수 있습니다.
ex)
String result = stream.collect(Collectors.joining(", ", "[", "]"));
5. counting()
스트림의 요소 개수를 세고, Long으로 반환합니다.
ex)
long count = stream.collect(Collectors.counting());
6. summarizingInt(), summarizingDouble(), summarizingLong()
숫자 스트림에서 합계, 평균, 최대값, 최소값 등을 포함한 통계를 계산하고 IntSummaryStatistics 등의 객체로 반환합니다.
ex)
IntSummaryStatistics stats = stream.collect(Collectors.summarizingInt(Integer::intValue));
7. reducing()
요소를 하나의 값으로 합치는 리듀싱 연산을 수행할 때 사용합니다. 기본값을 지정하거나 누적 함수를 사용할 수 있습니다.
예)
Optional<Integer> sum = stream.collect(Collectors.reducing((a, b) -> a + b));
8. groupingBy()
요소를 기준에 따라 그룹화하여 Map으로 반환합니다.
groupingBy(Function classifier): 키 매퍼 함수를 사용해 그룹화합니다.
groupingBy(Function classifier, Collector downstream): 각 그룹에 대해 추가적인 수집 연산을 지정할 수 있습니다.
groupingBy(Function classifier, Supplier mapFactory, Collector downstream): 결과 Map의 타입을 지정할 수 있습니다.
ex)
Map<Integer, List<String>> groupedByLength = stream.collect(Collectors.groupingBy(String::length));
9. partitioningBy()
요소를 조건에 따라 두 그룹으로 분할합니다. Map<Boolean, List<T>> 형식으로 반환되며, true 또는 false로 그룹화합니다.
ex)
Map<Boolean, List<Integer>> partitioned = stream.collect(Collectors.partitioningBy(n -> n % 2 == 0));
이외에도 maxBy(), minBy(), mapping(), filtering(), flatMapping() 등 다양한 수집기 메서드가 있습니다. Collectors를 활용하면 스트림의 데이터를 다양한 방식으로 가공하여 수집할 수 있습니다.
10) sort와 sorted의 차이
ed의 의미는
sort는 정렬해라~ 행동을 지시하는 느낌이라면
sorted는 이미 정렬됨 혹은 정렬된 상태의 결과를 말해주는거라고 보면됩니다.
정렬이 완료된 새로운 데이터가 반환된다는 의미를 강조하려고 ed를 붙인것입니다.
sort(Collections.sort)
: Java의 Collections.sort() 메서드는 주어진 리스트를 오름차순으로 정렬하며, 원본 리스트를 직접 변경합니다.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(1);
numbers.add(3);
Collections.sort(numbers); // 원본 리스트가 변경됨
System.out.println(numbers); // [1, 3, 5]
}
}
sorted(Steam.sorted)
: Java8 부터 제공되는 Steam.sorted() 메서드는 원본 컬렉션을 변경하지 않고, 정렬된 스트림을 반환합니다. 스트림을 이용해 새로운 정렬된 리스트를 생성할 수 있습니다.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
// 새로운 정렬된 스트림 생성
List<String> sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(names); // [Charlie, Alice, Bob] (원본 유지)
System.out.println(sortedNames); // [Alice, Bob, Charlie]
}
}
11) List, Set, Map
List와 Set은 Collection 인터페이스의 자손이지만, Map은 별도의 인터페이스이기 때문에 Collection의 자손이 아닙니다.
자바의 컬렉션 프레임 워크는 java.util.Collection과 java.util.Map 을 중심으로 구성되어 있습니다. 여기서 Collection 인터페이스를 구현하는 List와 Set은 데이터를 모아놓은 단순한 컬렉션을 의미하지만, Map 은 키-값 쌍으로 데이터를 저장하는 구조를 나타냅니다.
- Collection 인터페이스 : 단순히 여러 개의 요소를 모아놓은 구조입니다. 이를 구현하는 대표적인 인터페이스로는 List와 Set 이 있습니다.
- List : 순서가 있는 요소들의 컬렉션으로, 중복 요소를 허용합니다. 예) ArrayList, LinkedList
- Set : 중복을 허용하지 않는 컬렉션으로, 요소들의 순서가 보장되지 않습니다. 예) HashSet, TreeSet
- Map 인터페이스 : Collection의 자손이 아닌, 별도의 인터페이스로 존재합니다. 키와 값 쌍으로 데이터를 저장하며, 키는 중복을 허용하지 않고, 값은 중복을 허용합니다. 예) HashMap, TreeMap
12) Collectors와 Collection 관련성
Collectors와 Collection 의 관련성
Collectors 클래스는 Stream API와 함께 사용되며 스트림의 결과를 List, Set, Map 등으로 쉽게 변환해줍니다.
예를들어, Collectors.toList()나 Collectors.toSet()을 사용하면, 스트림의 요소들을 List나 Set으로 수집할 수 있습니다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> nameList = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList()); // List로 수집
Map과 관련된 Collectors
Map은 Collection의 자손이 아니기 때문에, Collectors는 List나 Set을 만들때와는 약간 다른 방식으로 Map을 생성합니다. Collectors.toMap()을 사용하여 스트림의 결과를 Map으로 수집할 수 있습니다. 이 때, toMap 메서드는 키와 값을 지정해주는 람다식이 필요합니다.
Map<String, Integer> nameLengthMap = names.stream()
.collect(Collectors.toMap(name -> name, name -> name.length())); // Map으로 수집
'서버&백엔드 > 🔥 JAVA' 카테고리의 다른 글
Java | 스트림(Stream) 완전 정복 - 문자형 (0) | 2024.11.03 |
---|---|
Java | 람다식 (0) | 2024.11.03 |
톰캣 자바 메모리풀 사이즈 설정 (0) | 2024.08.07 |
FTP서버에 파일 저장 (0) | 2024.06.04 |
이클립스, 톰캣 | 파일업로드시 프로젝트 새로고침해야지 뜰때 (0) | 2024.03.12 |