Stream의 단계는 3가지가 있다.
1. 생성하기
2. 가공하기
3. 결과 만들기
생성하기
- Array의 스트림 생성
String[] arr2 = {"1", "2", "3", "4", "5"};
Stream<String> stream = Arrays.stream(arr2);
만약 String이 아닌 int[] 배열이 들어온다면, 자동적으로 IntStream이 생성된다.
int[] array = {1, 2, 3, 4, 5};
IntStream stream1 = Arrays.stream(array);
- Collection의 스트림 생성
Stream<String> stream = arrList.stream();
스트림 생성은 매우 간단하다.
필자가 느끼기에 자주 쓰인다고 느끼는 Stream은 위의 두 형태지만, 다른 형태의 Stream 또한 생성할 수 있다.
ex)
IntStream intStream = IntStream.range(1, 6);
가공하기
1) Map - 데이터 변환
기존의 Stream 요소들을 변환하여 새로운 Stream을 형성하는 연산이다.
사용 예시
public static void main(String[] args) {
List<String> arr = List.of("ABC", "BBB", "CCC");
List<String> mapResult = arr.stream()
.map(a -> a.toLowerCase())
.collect(Collectors.toList());
for (String s : mapResult) {
System.out.println(s);
}
// abc
// bbb
// ccc
}
collect는 뒤에서 확인하고, 우선은 map에 집중하자.
map의 매개변수를 확인해보면 다음과 같다.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Function에 대해 이해가 안 된다면 이전 포스팅을 읽고 돌아오자.
https://codingmasterlsw.tistory.com/30
함수형 인터페이스
함수형 인터페이스란? java는 기본적으로 객체지향 언어이기 때문에 순수 함수와 일반 함수를 다르게 취급하고 있으며, Java에서는 이를 구분하기 위해 함수형 인터페이스가 등장했다. 람다를
codingmasterlsw.tistory.com
결국 Function 함수를 통해 객체 a 를 매개변수로 받고 a.toLowerCase()로 반환해주는... 그런 내부적인 코드가 들어있는것이다.
Map을 통해 데이터를 바꿀 수 있다. 타입을 바꿀수도 있고, 형태 또한 바꿀 수 있다.
Map vs MapToInt
기본적으로 Stream의 반환값은 Stream<T> 의 형식이다. 하지만 MapToInt로 타입을 변경할 경우, IntStream 객체가 반환이 된다.
Stream<Integer> stream1 = arr.stream()
.map(a -> Integer.parseInt(a));
IntStream stream2 = arr.stream()
.mapToInt(a -> Integer.parseInt(a));
mapToInt()를 사용했을 때, IntSream에서 사용할 수 있는 기능들을 사용할 수 있다. 예시를 살펴보자.
예시 1)
예시 2)
예시를 보면 알다시피, mapToInt를 통해 String -> Int로 변경한 경우에는 IntStream이 제공하는 계산 메서드를 사용해 코드가 훨씬 직관적이다.
만약 계산이 필요하다면, 무지성으로 map 을 사용하는것이 아닌 상황에 맞게 mapToInt, mapToDouble 등등... 을 고려하자.
2) Filter - 필터링
해당되는 조건만 가져오는 코드다.
예시를 살펴보자.
public static void main(String[] args) {
List<String> arr = List.of("a", "bb", "ccc", "dddd");
List<String> filteringResult = arr.stream()
.filter(a -> a.length() > 2)
.collect(Collectors.toList());
for (String s : filteringResult) {
System.out.println(s);
}
// ccc
// dddd
}
String의 길이가 2를 넘는 것들만 추출을 했다. 마찬가지로, filter의 매개변수를 확인해보자.
Stream<T> filter(Predicate<? super T> predicate);
Stream 내부에서 predicate.test(t) = true 인 것들만 알아서 필터링 해주는 것 같다.
3) Sort - 정렬
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
메서드 오버로딩이 되어있는걸 볼 수 있는데, 매개변수가 없다면 기본적으로 오름차순 정렬이다.
만약 내림차순 정렬을 하고 싶다면,
arr.stream()
.sorted(Comparator.reverseOrder());
다음과 같이 정렬을 하자.
만약 특수 정렬이 필요할 경우, 직접 Comparator를 구현하면 되지 않을까 싶다.
4) Distinct - 중복 제거
중복을 제거한다. (주의 : Distinct를 사용하기 위해선 해당 객체의 equals & hashCode 재정의를 해줘야한다.)
5) Peek - 특정 연산 수행
보통 로깅이나 추적 목적으로 사용하는 함수다.
Stream<T> peek(Consumer<? super T> action);
Consumer를 매개변수로 받고, 그대로 소비하고 끝이다.
Peek의 특징은 Stream에 영향을 주지 않고 특정 연산을 수행한다는 점이다. 즉, 결과에 영향을 미치지 않는다.
public static void main(String[] args) {
List<String> arr = List.of("a", "bb", "ccc", "dddd");
List<String> collect = arr.stream()
.filter(a -> a.length() > 2)
.peek(System.out::println)
.collect(Collectors.toList());
}
// ccc
// dddd
Stream의 중간에 디버깅요소로 사용하면 좋을 것 같다.
결과 만들기
collect - 데이터 수집
Stream의 요소들을 List, Map, Set 등 다른 종류의 결과로 수집하고 싶을 때 사용한다.
.collect(Collectors.toList());
자주 쓰이는 Collection의 경우, Collectors 객체에서 static으로 제공하고 있으니, 편하게 사용하면 될 것 같다.
Collectors.joining()
public static void main(String[] args) {
List<String> arr = List.of("a", "bb", "ccc", "dddd");
String collect = arr.stream()
.collect(Collectors.joining(" "));
System.out.println(collect);
// a bb ccc dddd
}
Collection이 아닌, Collectors.joining()을 사용하면, Stream의 결과값을 하나의 문자열로 합쳐 반환할 수 있다.
joining() 의 매개변수에는 delimiter가 들어오니 유용하게 사용할 수 있다고 생각한다.
Match - 조건 검사
- anyMatch : 1개의 요소라도 조건을 만족하는가?
anyMatch의 매개변수를 보면 predicate로 되어있다. 그렇기에 해당하는 조건을 넣으면 boolean 판별이 되는 것 같다.
public static void main(String[] args) {
List<String> arr = List.of("a", "bb", "ccc", "dddd");
boolean hasOverTwoSize = arr.stream()
.anyMatch(a -> a.length() > 2);
System.out.println(hasOverTwoSize);
// true
}
- allMatch : 모든 요소가 조건을 만족하는가?
위의 상황에서 false 반환
- nonMatch : 모든 요소가 조건을 만족하지 않는가?
위의 상황에서 false 반환
anyMatch는 상황에 따라 잘 쓰일 것 같다. filter를 사용하지 않고 동시에 값을 반환해준다고 생각하면 편할 것 같다.
만약 길이가 2보다 큰 원소가 있는지를 판별만 하고 boolean 값을 반환하고 싶다면 filter 대신에 anyMatch를,
예외를 처리하거나 해당 경우에 다른 값을 넘겨주고 싶다면
public static void main(String[] args) {
List<String> arr = List.of("a", "bb", "ccc", "dddd");
arr.stream()
.filter(a -> a.length() > 2)
.findAny()
.orElseThrow();
}
아래와 같이 코드를 작성할 것 같다.
특정 연산 수행 - forEach
peek와 비슷하지만, forEach의 경우에는 최종 연산으로 Stream에 영향을 준다. 반환값이 존재하지 않으며 출력을 원할경우 다음과 같이 작성할 수 있다.
public static void main(String[] args) {
List<String> arr = List.of("a", "bb", "ccc", "dddd");
arr.stream()
.filter(a -> a.length() > 2)
.forEach(System.out::println);
}
// ccc
// dddd
'java' 카테고리의 다른 글
Java 의 불변 객체 (final, 방어적 복사, unmodifiable) (0) | 2025.03.13 |
---|---|
Java Custom Exception (0) | 2025.03.04 |
함수형 인터페이스 (0) | 2025.02.25 |
Thread(3) - volatile, synchronize, lock (8) | 2025.01.19 |
Thread(2) - 스레드의 상태 (6) | 2025.01.15 |