데이터 준비 -> 중간 연산 -> 최종 연산 순으로 처리됨컬렉션(List, Set 등) 과 함께 자주 활용됨forEach() 는 매개변수에 대입된 람다식을 데이터 소스의 모든 요소에 적용한다.Stream<Integer> 대신 IntStream 이 제공된다..sum() , average() 등)| 단계 | 설명 | API |
|---|---|---|
| 1. 데이터 준비 | 컬렉션을 스트림으로 변환 | stream() , parallelStream() 등 |
| 2. 중간 연산 등록 | 데이터 변환 및 필터링 | map() , filter() , sorted() 등 |
| 3. 최종 연산 | 최종 처리 및 데이터 변환 | collect() , forEach() , count() 등 |
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
위와 같은 arrayList 리스트가 있다고 치자.
각 요소에 10을 곱한 후 반환하는 코드를 작성해보자.
public class Main {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// ✅ 반복문 - 명령형 스타일
List<Integer> ret = new ArrayList<>();
for (Integer num : arrayList) {
int multipliedNum = num * 10;
ret1.add(multipliedNum);
}
System.out.println("ret = " + ret);
}
}
public class Main {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// ✅ 스트림 - 선언적 스타일
List<Integer> ret = arrayList
.stream()
.map(num -> num * 10)
.collect(Collectors.toList());
System.out.println("ret = " + ret);
}
}
같은 기능을 하지만, 가독성이 훨씬 좋아진 모습을 보인다.
stream() → map() → collect() 순으로 데이터 흐름을 처리합니다.arrayList.stream() // 1. 데이터 준비
.map() // 2. 중간 연산 등록
.collect() // 3. 최종 연산
stream() : 데이터 준비 // 1. 데이터 준비: 스트림 생성
Stream<Integer> stream = arrayList.stream();
map() : 중간 연산 등록// 2. 중간 연산 등록: 각 요소를 10배로 변환하는 로직 등록
Stream<Integer> mappedStream = stream.map(num -> num * 10);
collect() : 최종 연산List, Set)로 수집합니다.// 3. 최종 연산: 최종 결과 리스트로 변환
List<Integer> ret2 = mappedStream.collect(Collectors.toList());
// ✅ 한 줄로 표현 가능
List<Integer> ret2 = arrayList.stream() // 1. 데이터 준비
// 2. 중간 연산 등록
.map(num -> num * 10)
// 3. 최종 연산
.collect(Collectors.toList());
public class Main {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// ✅ 1. 익명클래스를 변수에 담아 활용
Function<Integer, Integer> function = new Function<>() {
@Override
public Integer apply(Integer integer) {
return integer * 10;
}
};
List<Integer> ret = arrayList.stream()
.map(function)
.collect(Collectors.toList());
System.out.println("ret = " + ret);
}
}
public class Main {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// ✅ 2. 람다식을 변수에 담아 활용
Function<Integer, Integer> functionLambda = (num -> num * 10);
List<Integer> ret = arrayList.stream()
.map(functionLambda)
.collect(Collectors.toList());
System.out.println("ret = " + ret);
}
}
public class Main {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// ✅ 3. 람다식을 직접 활용
List<Integer> ret = arrayList.stream()
.map(num -> num * 10)
.collect(Collectors.toList());
System.out.println("ret = " + ret);
}
}
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// ✅ for 사용 예제
List<Integer> ret = new ArrayList<>();
for (int num : arrayList) {
int remain = num % 2;
if (remain == 0) {
int data = num * 10;
ret.add(data);
}
}
System.out.println(ret); // 출력: [20, 40]
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// ✅ filter() + map() 사용 예제
List<Integer> ret = arrayList.stream() // 1. 데이터 준비: 스트림 생성
// 2. 중간 연산: 짝수만 필터링
.filter(num -> num % 2 == 0)
// 3. 중간 연산: 10배로 변환
.map(num -> num * 10)
// 4. 최종 연산: 리스트로 변환
.collect(Collectors.toList());
System.out.println(ret); // 출력: [20, 40]
Arrays.stream()String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
.stream()List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream();
Stream<String> builderStream = Stream.<String>builder()
.add("a")
.add("b")
.add("c")
.build();
Stream.generate() , iterate()Stream<String> generatedStream = Stream.generate(() -> "a")
.limit(3);
Stream<Integer> iteratedStream = Stream.iterate(0, n -> n + 2)
.limit(5); //0,2,4,6,8
생성할 때 스트림의 크기가 정해져있지 않기(무한하기)때문에 최대 크기를 제한해줘야 한다.
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
parallelStream()Stream<String> parallelStream = list.parallelStream();
List<String> list = Arrays.asList("a","b","c");
위와 같이 a , b , c 가 있는 리스트로 데이터를 가공해보자.
Stream<String> stream = list.stream()
// 'a'가 들어간 요소만 선택 [a]
.filter(list -> list.contains("a"));
람다식의 리턴값은 boolean
true 인 경우만 다음 단계 진행
Stream<String> stream = list.stream()
// [A,B,C]
.map(String::toUpperCase);
// 문자열 -> 정수로 변환
.map(Integers::parseInt);
스트림에 있는 값을 원하는 메소드에 입력값으로 넣으면
메소드 실행 결과(반환 값)가 담긴다.
Stream<String> stream = list.stream()
// [a,b,c] 오름차순 정렬
.sorted()
// [c,b,a] (내림차순)
.sorted(Comparator.reverseOrder())
List<String> list = Arrays.asList("a","bb","ccc");
Stream<String> stream = list.stream()
// [ccc,bb,a]
// 문자열 길이 기준 정렬
.sorted(Comparator.comparingInt(String::length))
Stream<String> stream = list.stream()
// 중복 제거
.distinct()
// 최대 크기 제한
.limit(max)
// 앞에서부터 n개 skip하기
.skip(n)
// 중간 작업결과 확인
.peek(System.out::println)
기본형 타입을 사용하는 경우
스트림 내 요소들로 최소, 최대, 합, 평균 등을 구하는 연산을 수행할 수 있다.
IntStream stream = list.stream()
.count() // 스트림 요소 개수 반환
.sum() // 스트림 요소의 합 반환
.min() // 스트림의 최소값 반환
.max() // 스트림의 최대값 반환
.average() // 스트림의 평균값 반환
스트림의 요소를 하나씩 줄여가며 누적연산을 수행
IntStream stream = IntStream.range(1,5);
// reduce(초기값, (누적 변수,요소) -> 수행문)
// 10 + 1+2+3+4+5 = 25
stream.reduce(10, (total, num) -> total + num);
스트림의 요소를 원하는 자료형으로 반환
// 예시 리스트
List<Person> members = Arrays.asList(new Person("lee",26),
new Person("kim", 23),
new Person("park", 23));
toList() - 리스트로 반환members.stream()
.map(Person::getLastName)
// [lee, kim, park]
.collect(Collectors.toList());
joining() - 작업 결과를 하나의 스트링으로 이어 붙이기members.stream()
.map(Person::getLastName)
// <lee+kim+park>
.collect(Collectors.joining(delimiter = "+" , prefix = "<", suffix = ">");
groupingBy() - 그룹지어서 Map 으로 반환members.stream()
.collect(Collectors.groupingBy(Person::getAge));
// { 26 = [Person{lastName = "lee", age = 26}],
// 23 = [Person{lastName = "kim", age = 23},
// Person{lastName = "park", age = 23}] }
collectingAndThen() - collecting 이후 추가 작업 수행members.stream()
// Set으로 collect한 후 수정불가한 set으로 변환하는 작업 실행
.collect(Collectors.collectingAndThen (Collectors.toSet(),
Collections::unmodifiableSet));
특정 조건을 만족하는 요소가 있는지 체크한 결과를 반환
anyMatch() - 하나라도 만족하는 요소가 있는지allMatch() - 모두 만족하는지noneMatch() - 모두 만족하지 않는지List<String> members = Arrays.asList("Lee", "Park", "Hwang");
boolean matchResult = members.stream()
// w를 포함하는 요소가 있는지, True
.anyMatch(members->members.contains("w"));
boolean matchResult = members.stream()
// 모든 요소의 길이가 4 이상인지, False
.allMatch(members->members.length() >= 4);
boolean matchResult = members.stream()
// t로 끝나는 요소가 하나도 없는지, True
.noneMatch(members->members.endsWith("t"));
forEach() 로 스트림을 돌면서 실행되는 작업
members.stream()
.map(Person::getName)
// 결과 출력 (peek는 중간, forEach는 최종)
.forEach(System.out::println);
스트림에서 하나의 요소를 반환함
Person person = members.stream()
// 먼저 찾은 요소 하나를 반환함
// 병렬 스트림의 경우 첫 번째 요소가 보장되지 않음
.findAny()
// 첫 번째 요소를 반환함
.findFirst()
✅ 스트림 체이닝 작성 스타일
위 예시 코드에서 작성한 스타일로 들여쓰기를 적용한다.
List<Integer> ret = arrayList.stream() .map(num -> num * 10) .collect(Collectors.toList());
.stream()은List의 메소드이므로, 같은 줄에 둔다.- 체이닝 시 들여쓰기는
.과 동일한 위치에 둔다.❌ 잘못된 스타일 예시
// 불필요한 줄 개행 List<Integer> ret = arrayList .stream() .map(num -> num * 10) .collect(Collectors.toList());// 들여쓰기 불균형 List<Integer> ret = arrayList.stream() .map(num -> num * 10) .collect(Collectors.toList());
챕터 3-6 : 스트림(Stream)
[Java] 스트림 (Stream) 정리