Java Stream API 활용법
Java Stream API를 사용하여 데이터를 효율적으로 처리하는 방법을 알아보고, 실제 코드 예시를 통해 활용법을 익혀봅니다.
왜 Stream API를 사용해야 할까요?
① 기존 방식의 문제점
Java 8 이전에는 컬렉션 데이터를 처리할 때 반복문(for, while)을 사용하는 경우가 많았는데요. 이 방식은 코드가 길어지고 가독성이 떨어지는 단점이 있었어요. 예를 들어, 리스트에서 특정 조건을 만족하는 요소만 추출하려면 다음과 같은 코드를 작성해야 했죠.
java List names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Anna"); List filteredNames = new ArrayList<>(); for (String name : names) { if (name.startsWith("A")) { filteredNames.add(name); } } System.out.println(filteredNames); // [Alice, Anna]
이렇게 직접 반복문을 사용하면 코드가 복잡해지고, 어떤 로직인지 한눈에 파악하기 어려워요.
② Stream API의 등장
Java 8에서 Stream API가 도입되면서 컬렉션 데이터를 더 쉽고 효율적으로 처리할 수 있게 되었어요. Stream API는 데이터를 추상화하고, 선언적인 방식으로 데이터를 처리할 수 있도록 도와주거든요. 위에서 언급한 예제를 Stream API로 다시 작성하면 다음과 같아요.
java List names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Anna"); List filteredNames = names.stream() .filter(name -> name.startsWith("A")) .collect(Collectors.toList()); System.out.println(filteredNames); // [Alice, Anna]
훨씬 간결하고 가독성이 좋아졌죠? Stream API를 사용하면 코드를 더 짧고 명확하게 작성할 수 있고, 병렬 처리도 쉽게 적용할 수 있다는 장점이 있어요.
Stream API 핵심 개념
| 항목 | 설명 | 예시 |
|---|---|---|
| Stream | 데이터의 흐름. 컬렉션, 배열 등 다양한 소스로부터 생성 가능 | names.stream() |
| Intermediate Operation | Stream을 변환하는 연산. 여러 개 연결 가능 (filter, map, sorted 등) | .filter(name -> name.startsWith("A")) |
| Terminal Operation | Stream을 소비하여 결과를 생성하는 연산 (collect, forEach, count 등) | .collect(Collectors.toList()) |
Stream API는 크게 Stream 생성, Intermediate Operation, Terminal Operation 세 단계로 구성돼요.
- Stream 생성: 컬렉션이나 배열 등의 데이터 소스에서 Stream을 생성합니다.
- Intermediate Operation: Stream을 변환하는 중간 연산을 수행합니다.
filter(),map(),sorted()등이 여기에 해당하며, 이 연산들은 Stream을 반환하기 때문에 여러 개를 연결해서 사용할 수 있어요. - Terminal Operation: Stream을 소비하여 최종 결과를 생성하는 연산을 수행합니다.
collect(),forEach(),count()등이 여기에 해당하며, 이 연산은 Stream을 반환하지 않기 때문에 마지막에 한 번만 사용할 수 있어요.
Stream API 활용법: 단계별 예제
① 데이터 필터링 (filter)
filter()는 Stream에서 특정 조건을 만족하는 요소만 선택하는 연산이에요. 예를 들어, 숫자 리스트에서 짝수만 추출하려면 다음과 같이 사용할 수 있어요.
java List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List evenNumbers = numbers.stream() .filter(number -> number % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers); // [2, 4, 6, 8, 10]
② 데이터 변환 (map)
map()은 Stream의 각 요소를 다른 값으로 변환하는 연산이에요. 예를 들어, 문자열 리스트의 각 문자열 길이를 구하려면 다음과 같이 사용할 수 있어요.
java List names = Arrays.asList("Alice", "Bob", "Charlie", "David"); List nameLengths = names.stream() .map(String::length) .collect(Collectors.toList()); System.out.println(nameLengths); // [5, 3, 7, 5]
③ 데이터 정렬 (sorted)
sorted()는 Stream의 요소를 정렬하는 연산이에요. 기본적으로 오름차순으로 정렬되며, Comparator를 사용하여 정렬 기준을 변경할 수도 있어요.
java List numbers = Arrays.asList(5, 2, 8, 1, 9, 4); List sortedNumbers = numbers.stream() .sorted() .collect(Collectors.toList()); System.out.println(sortedNumbers); // [1, 2, 4, 5, 8, 9]
④ 데이터 그룹핑 (groupingBy)
groupingBy()는 Stream의 요소를 특정 기준으로 그룹화하는 연산이에요. 예를 들어, 객체 리스트를 특정 필드 값으로 그룹화하려면 다음과 같이 사용할 수 있어요.
java class Person { String name; int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
List people = Arrays.asList( new Person("Alice", 25), new Person("Bob", 30), new Person("Charlie", 25), new Person("David", 30) );
Map<Integer, List> peopleByAge = people.stream() .collect(Collectors.groupingBy(Person::getAge)); System.out.println(peopleByAge); // {25=[Person@1, Person@2], 30=[Person@3, Person@4]}
Stream API 사용 시 주의사항
① Stream은 일회용
Stream은 한 번 사용하면 다시 사용할 수 없어요. Terminal Operation을 수행한 후에는 Stream이 닫히기 때문에, 다시 사용하려면 새로운 Stream을 생성해야 해요.
② Lazy Evaluation
Stream API는 Lazy Evaluation 방식으로 동작해요. 즉, Terminal Operation이 호출되기 전까지는 Intermediate Operation이 실제로 수행되지 않아요. 따라서 불필요한 연산을 줄일 수 있어요.
마무리
Java Stream API는 데이터를 효율적으로 처리할 수 있는 강력한 도구인데요. 다양한 연산을 조합하여 복잡한 데이터 처리 로직을 간결하게 구현할 수 있다는 점이 매력적인 것 같아요. Stream API를 사용하면 코드 가독성이 높아지고 유지보수도 쉬워지기 때문에, 앞으로 Java 개발에서 Stream API 활용이 더욱 중요해질 거라고 생각해요.