자바의 stream 간단 정리
🤔 스트림(stream) 이란?
- 자바 8부터 추가된 기술로 데이터의 추상화된 연속적인 흐름을 다루는 API다.
- 람다를 활용해 배열과 컬렉션을 함수형으로 간단하게 처리할 수 있다.
- 스트림은 데이터 소스(타입)를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드를 정의해 놓아서 데이터 소스에 상관없이 모두 같은 방식으로 다룰 수 있어 코드의 가독성과 재사용성이 높아진다.
[기존의 for문과 Iterator 비교]
- 기존의
for문
과Iterator
를 사용하면 코드가 길어져서 가독성과 재사용성이 떨어지고 데이터 타입마다 다른 방식으로 다뤄야한다. 스트림을 사용하면 코드가 더 간결하고 명시적인 반복을 제거할 수 있다.
- for 문 예시
1 2 3 4 5 6 7 8
List<String> names = Arrays.asList("John", "Jane", "Michael", "Sarah", "James"); List<String> filteredNames = new ArrayList<>(); for (String name : names) { if (name.startsWith("J")) { filteredNames.add(name); } } System.out.println(filteredNames); // John, Jane
- stream 사용
1 2 3 4 5
List<String> names = Arrays.asList("John", "Jane", "Michael", "Sarah", "James"); List<String> filteredNames = names.stream() .filter(name -> name.startsWith("J")) .collect(Collectors.toList()); System.out.println(filteredNames); // John, Jane
🥸 스트림의 특징
- 원본 데이터 소스를 변경하지 않는다.
- 읽기만 수행하여 데이터의 무결성을 보장한다.
- 한번 사용하면 닫혀서 재사용이 불가능한 일회용이다.
- 최종 연산 전까지 중간 연산을 수행하지 않는다.
- 불필요한 계산을 최소화한다.
- 작업을 내부 반복으로 처리한다.
- 반복을 명시적으로 나타내지 않음으로써 간결하게 표현할 수 있다.
- 병렬 처리가 쉽다.
- 멀티쓰레드 환경을 지원한다.
- 기본형 스트림을 제공한다.
Stream<Integer>
대신IntStream
이 제공됨으로써int <-> Integer
의 오토박싱과 언박싱 등의 불필요한 과정이 생략되고 성능 향상으로 이어진다.그러나
Stream<Integer>
을 쓰면 오토박싱과 언박싱이 수행된다.- 숫자의 경우 유용한 메서드를 추가로 제공한다. (
sum()
,average()
등)
😄 스트림 생성
- 기존의 배열이나 스트림에
.stream()
메서드를 사용하여 생성할 수도 있고 직접 stream을 만들 수도 있다.
[배열 스트림]
Arrays.stream();
안에 인자로 넣어줘야 한다.
1
2
String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
[컬렉션 스트림]
.stream()
메서드로 바로 스트림으로 변환할 수 있다.
1
2
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
[builder로 생성]
Stream.builder()
로 생성 후add
로 요소를 추가할 수 있다.
1
2
3
Stream<String> builderStream = Stream.<String>builder()
.add("a").add("b").add("c")
.build();
[람다식으로 생성]
generate()
또는iterate()
메서드를 사용하여 람다식을 이용한 스트림 생성이 가능하다.- 생성할 때 스트림의 크기가 정해져있지 않기 때문에 최대 크기를
limit()
로 제한해야 한다.
1
2
3
Stream<String> generatedStream = Stream.generate(()->"a").limit(3); // a, a, a
Stream<Integer> iteratedStream = Stream.iterate(0, n->n+2).limit(5); // 0, 2, 4, 6, 8
[기본 타입형 스트림]
- 타입이 지정된 스트림이다.
1
IntStream intStream = IntStream.range(1, 5); // 1, 2, 3, 4
[병렬 스트림]
parallelStream
으로 병렬 스트림을 생성할 수 있다.
1
Stream<String> parallelStream = list.parallelStream();
😤 중간 연산(가공)
[Filtering]
- 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업을 한다.
- 람다식의 반환값이
true
인 경우만 다음 연산을 진행한다.
1
2
3
4
5
List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream()
.filter(list -> list.contains("a"));
// 'a'가 들어간 요소만 선택
// a
[Mapping]
- 스트림 내 요소들을 하나씩 특정 값으로 변환한다.
1
2
3
4
5
List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream()
.map(String::toUpperCase);
// 대문자로 변경
// A, B, C
[Sorting]
- 스트림 내 요소들을 정렬하는 작업
- Comparator를 사용한다.
1
2
3
4
5
List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream()
.sorted(Comparator.reverseOrder());
// 내림차순 정렬
// c, b, a
[기타 연산]
1
2
3
4
5
Stream<String> stream = list.stream()
.distinct() // 중복 제거
.limit(max) // 최대 크기 제한
.skip(n) // 앞에서부터 n개 skip하기
.peek(System.out::println) // 중간 작업결과 확인
최종 연산
[Calculating]
- 기본형 타입을 사용하는 경우 스트림 내 요소들로 최소, 최대, 합, 평균 등을 구하는 연산을 수행할 수 있다.
1
2
3
4
5
6
IntStream stream = list.stream()
.count() //스트림 요소 개수 반환
.sum() //스트림 요소의 합 반환
.min() //스트림의 최소값 반환
.max() //스트림의 최대값 반환
.average() //스트림의 평균값 반환
[Reduction]
- 스트림의 요소를 하나씩 줄여가며 누적연산을 수행한다.
1
2
3
4
5
6
7
IntStream stream = IntStream.range(1,5);
int result = stream.reduce(10, (total,num)->total+num);
//reduce(초기값, (누적 변수,요소)->수행문)
// 10 + 1 + 2 + 3 + 4 = 20
System.out.println(result); // 20
[Collecting]
- 스트림의 요소들을 원하는 형태로 변환하고 수집한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Stream<String> stream = Stream.of("Apple", "Banana", "Cherry", "Avocado");
// toList() - 리스트로 반환
List<String> list = stream.collect(Collectors.toList());
// toSet() - 세트로 반환
Set<String> set = stream.collect(Collectors.toSet());
// joining() - 하나의 문자열로 결합
String result = stream.collect(Collectors.joining(", "));
System.out.println(result); // Apple, Banana, Cherry, Avocado
// groupingBy() - 조건에 맞게 그룹지어서 Map으로 반환
Map<Character, List<String>> map = stream
.collect(Collectors.groupingBy(s -> s.charAt(0)));
System.out.println(map);
// {A=[Apple, Avocado], B=[Banana], C=[Cherry]}
// collectingAndThen() - collecting 이후 추가 작업 수행
List<String> unmodifiableList = stream
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList)); // collecting 후 값을 변경하지 못하도록 설정
System.out.println(unmodifiableList);
try {
unmodifiableList.add("New Fruit"); // 수정불가능하도록 설정했으므로 예외 발생
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify the list");
}
[Matching]
- 특정 조건을 만족하는 요소가 있는지의 결과를 반환한다.
- anyMatch : 하나라도 만족하는 요소가 있으면 true
- allMatch : 모두 만족하면 true
- noneMatch : 모두 만족하지 않으면 true
1
2
3
4
5
6
7
8
9
10
List<String> members = Arrays.asList("Lee", "Park", "Hwang");
boolean matchResult = members.stream()
.anyMatch(members->members.contains("w")); //w를 포함하는 요소가 있는지, True
boolean matchResult = members.stream()
.allMatch(members->members.length() >= 4); //모든 요소의 길이가 4 이상인지, False
boolean matchResult = members.stream()
.noneMatch(members->members.endsWith("t")); //t로 끝나는 요소가 하나도 없는지, True
[Iterating]
- forEach로 스트림을 돌면서 실행되는 작업이다.
1
2
3
4
5
6
7
8
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Avocado");
Stream<String> fruitStream = fruits.stream();
fruitStream.forEach(System.out::println);
// Apple
// Banana
// Cherry
// Avocado
[Finding]
- 스트림에서 특정 요소를 찾는 작업이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Avocado");
// findFirst() - 첫번째 요소 반환
Optional<String> firstFruit = fruits.stream()
.findFirst();
firstFruit.ifPresent(System.out::println); // Apple
// findAny() - 제일 먼저 찾은 요소 반환, 병렬 스트림의 경우 첫번째 요소 반환이 보장되지 않는다.
Optional<String> anyFruit = fruits.stream()
.findAny();
anyFruit.ifPresent(System.out::println); // 임의의 과일 출력 (대부분 "Apple")
출처
누군가가 물어본다면
스트림은 데이터의 추상화된 연속적인 흐름을 다루는 API 입니다.
데이터 컬렉션을 함수형 스타일로 연속적이고 선언적으로 처리할 수 있게 되어 코드의 가독성과 유지보수성이 증가합니다.
데이터 컬렉션을 함수형 스타일로 연속적이고 선언적으로 처리할 수 있게 되어 코드의 가독성과 유지보수성이 증가합니다.
This post is licensed under CC BY 4.0 by the author.