[Career] Java Stream 완전 정리
Java Stream 완전 정리
Java 8에서 도입된 Stream API를 활용하여 Collection 데이터를 효율적으로 처리하는 방법을 정리합니다.
Java Stream이란?
Java Stream은 Java 8에서 도입된 기능으로, 선언형으로 Collection 데이터를 처리할 수 있게 해주는 API입니다.
함수형 프로그래밍의 개념을 Java에 도입하여 더욱 간결하고 읽기 쉬운 코드 작성을 가능하게 합니다.
핵심 특징
- 선언형 프로그래밍: "어떻게"가 아닌 "무엇을" 수행할지 선언
- 병렬 처리: 멀티스레드 코드 없이도 자동으로 병렬 처리 가능
- 함수형 프로그래밍: 람다 표현식과 함께 사용하여 간결한 코드 작성
기본 구조
javaList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // Stream 파이프라인 long count = numbers.stream() // 스트림 생성 .filter(x -> x < 4) // 중간 연산 (스트림 변환) .count(); // 최종 연산 (결과 반환)
스트림 파이프라인 구성:
- 스트림 생성:
stream()- Collection에서 Stream 생성 - 중간 연산:
filter(),map(),sorted()등 - 연속적으로 수행 가능 - 최종 연산:
count(),collect(),forEach()등 - 마지막에 단 한 번만 사용
Stream의 핵심 특징
1. 데이터를 변경하지 않음 (Immutable)
Stream은 원본 데이터를 변경하지 않고, 새로운 Stream을 생성합니다.
javaList<String> names = Arrays.asList("Alice", "Bob", "Charlie"); List<String> upperNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList()); // 원본 리스트는 변경되지 않음 System.out.println(names); // [Alice, Bob, Charlie] System.out.println(upperNames); // [ALICE, BOB, CHARLIE]
2. 1회용 (One-time use)
Stream은 한 번만 사용할 수 있습니다. 최종 연산 후에는 Stream이 소비되어 재사용할 수 없습니다.
javaStream<Integer> stream = Arrays.asList(1, 2, 3).stream(); stream.forEach(System.out::println); // 정상 동작 stream.forEach(System.out::println); // IllegalStateException 발생!
3. 지연 연산 (Lazy Evaluation)
중간 연산은 최종 연산이 호출될 때까지 실행되지 않습니다.
javaList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> stream = numbers.stream() .filter(n -> { System.out.println("필터링: " + n); // 이 코드는 실행되지 않음 return n > 2; }); // 최종 연산이 호출될 때 비로소 실행됨 long count = stream.count(); // 이 시점에 filter가 실행됨
4. 병렬 실행 가능
parallelStream()을 사용하면 자동으로 병렬 처리됩니다.
javaList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 순차 처리 long sequentialSum = numbers.stream() .mapToInt(Integer::intValue) .sum(); // 병렬 처리 long parallelSum = numbers.parallelStream() .mapToInt(Integer::intValue) .sum();
Stream 생성 방법
1. Collection에서 생성
javaList<String> list = Arrays.asList("a", "b", "c"); Stream<String> stream = list.stream();
2. 배열에서 생성
javaString[] array = {"a", "b", "c"}; Stream<String> stream = Arrays.stream(array);
3. Stream.of() 사용
javaStream<String> stream = Stream.of("a", "b", "c");
4. 빈 Stream 생성
javaStream<String> emptyStream = Stream.empty();
5. 무한 Stream 생성
java// 무한 스트림 생성 (0부터 시작) Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2); // 처음 10개만 가져오기 infiniteStream.limit(10).forEach(System.out::println);
중간 연산 (Intermediate Operations)
중간 연산은 Stream을 변환하여 새로운 Stream을 반환합니다. 여러 개를 연속적으로 사용할 수 있습니다.
filter() - 필터링
조건에 맞는 요소만 필터링합니다.
javaList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); // 결과: [2, 4, 6, 8, 10]
map() - 변환
각 요소를 다른 형태로 변환합니다.
javaList<String> names = Arrays.asList("alice", "bob", "charlie"); List<String> upperNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList()); // 결과: [ALICE, BOB, CHARLIE] // 숫자를 제곱으로 변환 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> squares = numbers.stream() .map(n -> n * n) .collect(Collectors.toList()); // 결과: [1, 4, 9, 16, 25]
flatMap() - 평탄화
중첩된 구조를 평탄화합니다.
javaList<List<String>> nestedList = Arrays.asList( Arrays.asList("a", "b"), Arrays.asList("c", "d"), Arrays.asList("e", "f") ); List<String> flatList = nestedList.stream() .flatMap(List::stream) .collect(Collectors.toList()); // 결과: [a, b, c, d, e, f]
distinct() - 중복 제거
중복된 요소를 제거합니다.
javaList<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 5); List<Integer> uniqueNumbers = numbers.stream() .distinct() .collect(Collectors.toList()); // 결과: [1, 2, 3, 4, 5]
sorted() - 정렬
요소를 정렬합니다.
javaList<String> names = Arrays.asList("Charlie", "Alice", "Bob"); // 오름차순 정렬 List<String> sorted = names.stream() .sorted() .collect(Collectors.toList()); // 결과: [Alice, Bob, Charlie] // 내림차순 정렬 List<String> reverseSorted = names.stream() .sorted(Comparator.reverseOrder()) .collect(Collectors.toList()); // 결과: [Charlie, Bob, Alice]
최종 연산 (Terminal Operations)
최종 연산은 Stream 파이프라인을 실행하고 결과를 반환합니다.
collect() - 수집
javaList<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // List로 수집 List<String> list = names.stream() .filter(n -> n.length() > 3) .collect(Collectors.toList()); // Set으로 수집 Set<String> set = names.stream() .collect(Collectors.toSet()); // Map으로 수집 Map<String, Integer> map = names.stream() .collect(Collectors.toMap( name -> name, String::length ));
forEach() - 반복
각 요소에 대해 작업을 수행합니다.
javaList<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream() .forEach(System.out::println);
count() - 개수
요소의 개수를 반환합니다.
javaList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); long count = numbers.stream() .filter(n -> n > 2) .count(); // 결과: 3
reduce() - 축소
요소들을 하나의 값으로 축소합니다.
javaList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 합계 구하기 Optional<Integer> sum = numbers.stream() .reduce((a, b) -> a + b); // 결과: Optional[15] // 초기값과 함께 사용 Integer sumWithInitial = numbers.stream() .reduce(0, (a, b) -> a + b); // 결과: 15
실전 예제
예제 1: 사용자 데이터 처리
javaclass User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } } List<User> users = Arrays.asList( new User("Alice", 25), new User("Bob", 30), new User("Charlie", 20), new User("David", 35) ); // 30세 이상 사용자의 이름만 추출하여 정렬 List<String> names = users.stream() .filter(user -> user.getAge() >= 30) .map(User::getName) .sorted() .collect(Collectors.toList()); // 결과: [Bob, David]
예제 2: 문자열 처리
javaList<String> words = Arrays.asList("hello", "world", "java", "stream"); // 모든 단어를 대문자로 변환하고 길이가 4 이상인 것만 필터링 List<String> result = words.stream() .map(String::toUpperCase) .filter(s -> s.length() >= 4) .collect(Collectors.toList()); // 결과: [HELLO, WORLD, JAVA, STREAM]
정리
Java Stream API는 Collection 데이터를 효율적이고 선언적으로 처리할 수 있게 해주는 강력한 도구입니다.
핵심 포인트:
- Stream은 데이터를 변경하지 않음 (Immutable)
- Stream은 1회용
- 중간 연산은 지연 연산
- 최종 연산이 호출될 때 비로소 실행됨
- 병렬 처리가 쉬움