Java 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]
이렇게 직접 반복문을 사용하면 코드가 복잡해지고, 어떤 로직인지 한눈에 파악하기 어려워요.
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 | 데이터의 흐름. 컬렉션, 배열 등 다양한 소스로부터 생성 가능 | 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 세 단계로 구성돼요.
filter(), map(), sorted() 등이 여기에 해당하며, 이 연산들은 Stream을 반환하기 때문에 여러 개를 연결해서 사용할 수 있어요.collect(), forEach(), count() 등이 여기에 해당하며, 이 연산은 Stream을 반환하지 않기 때문에 마지막에 한 번만 사용할 수 있어요.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()은 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()는 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()는 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은 한 번 사용하면 다시 사용할 수 없어요. Terminal Operation을 수행한 후에는 Stream이 닫히기 때문에, 다시 사용하려면 새로운 Stream을 생성해야 해요.
Stream API는 Lazy Evaluation 방식으로 동작해요. 즉, Terminal Operation이 호출되기 전까지는 Intermediate Operation이 실제로 수행되지 않아요. 따라서 불필요한 연산을 줄일 수 있어요.
Java Stream API는 데이터를 효율적으로 처리할 수 있는 강력한 도구인데요. 다양한 연산을 조합하여 복잡한 데이터 처리 로직을 간결하게 구현할 수 있다는 점이 매력적인 것 같아요. Stream API를 사용하면 코드 가독성이 높아지고 유지보수도 쉬워지기 때문에, 앞으로 Java 개발에서 Stream API 활용이 더욱 중요해질 거라고 생각해요.
| Java 람다 표현식 정리 (0) | 2026.02.22 |
|---|---|
| Java Optional 사용법 (0) | 2026.02.22 |
| Java Stream에서 map과 forEach의 차이 (0) | 2025.06.21 |
| [Java] Stream 사용시 toList()와 collect(Collectors.toList())의 차이 점 (0) | 2024.12.17 |
| [Java] 날짜타입을 포맷 설정 후 문자열로 변환 (LocalDate, DateTimeFormatter) (0) | 2024.11.23 |