자바(Java)는 8버전에서 **람다 표현식(Lambda Expression)**과 **스트림 API(Stream API)**라는 혁신적인 기능을 도입하며 함수형 프로그래밍 스타일을 적극적으로 수용했습니다. 이 두 가지 핵심 요소는 기존의 장황하고 반복적인 코드를 간결하고 가독성 높은 코드로 변모시켰습니다. 자바 개발자라면 이 문법을 필수적으로 숙지해야 합니다.
🚀 1. 람다(Lambda) 표현식: 간결한 익명 함수
람다 표현식은 **익명 함수(Anonymous Function)**를 간결하게 표현하는 방식입니다. 주로 단 하나의 추상 메서드를 가진 **함수형 인터페이스(Functional Interface)**의 인스턴스를 만들 때 사용됩니다.
📝 람다 기본 문법
기본적인 형태는 (매개변수) -> { 실행문 }입니다.
상황 | 예시 | 설명 |
기본 형태 | (a, b) -> { return a + b; } | 일반적인 매개변수와 실행문 |
매개변수 타입 추론 | (a, b) -> a + b | 매개변수 타입을 생략할 수 있고, 실행문이 한 줄이면 {}와 return 생략 가능 |
매개변수가 하나 | s -> System.out.println(s) | 매개변수가 하나일 경우 () 생략 가능 |
매개변수가 없음 | () -> "Hello" | 매개변수가 없을 경우 ()는 필수 |
💡 활용 예시: 리스트 정렬
람다를 사용하면 기존의 익명 클래스 사용을 대체하여 코드가 얼마나 간결해지는지 명확하게 알 수 있습니다.
자바 8 이전 (익명 클래스)
List<String> names = Arrays.asList("John", "Alice", "Bob");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
자바 8 이후 (람다 표현식)
List<String> names = Arrays.asList("John", "Alice", "Bob");
// 매개변수 타입, return, 중괄호 모두 생략 가능
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
🌊 2. 스트림(Stream) API: 데이터 처리의 혁신
스트림 API는 컬렉션(Collection)이나 배열에 저장된 요소를 **선언형(Declarative)**으로 처리할 수 있게 해주는 기능입니다. 데이터를 '흐름'으로 보고, 그 흐름에 원하는 작업을 연결하여 수행합니다. 기존의 for-loop를 이용한 반복적인 처리 방식보다 훨씬 유연하고 가독성이 뛰어납니다.
🌟 스트림의 주요 특징
- 원본 데이터 변경 없음: 스트림은 원본 데이터 소스를 건드리지 않고, 별도의 스트림 객체를 만들어 작업합니다.
- 일회용: 한 번 사용한 스트림은 재사용할 수 없습니다. 다시 사용하려면 스트림을 새로 생성해야 합니다.
- 지연된 연산(Lazy Evaluation): 중간 연산(Intermediate Operation)은 최종 연산(Terminal Operation)이 호출되기 전까지 실제로 실행되지 않습니다.
🔗 스트림 파이프라인 구조
스트림을 이용한 데이터 처리는 보통 다음과 같은 3단계의 파이프라인으로 구성됩니다.
데이터 소스→스트림 생성→중간 연산 (0개 이상)→최종 연산 (1개)
1. 스트림 생성 (Source)
생성 방식 | 예시 |
컬렉션 | list.stream() |
배열 | Arrays.stream(array) |
특정 값 | Stream.of("a", "b", "c") |
2. 중간 연산 (Intermediate Operations)
중간 연산은 스트림을 반환하므로, 여러 연산을 연속적으로 연결(Chaining) 할 수 있습니다.
메서드 | 기능 | 예시 |
filter() | 조건에 맞는 요소만 추출 | filter(n -> n > 10) |
map() | 요소를 변환 (다른 형태로 매핑) | map(s -> s.toUpperCase()) |
distinct() | 중복 요소 제거 | distinct() |
sorted() | 요소 정렬 | sorted() |
peek() | 스트림 요소에 영향을 주지 않고 작업 수행 (디버깅 용이) | peek(System.out::println) |
3. 최종 연산 (Terminal Operation)
최종 연산은 스트림을 소모하여 결과를 반환하고, 파이프라인을 종료합니다.
메서드 | 기능 | 예시 |
forEach() | 각 요소를 순회하며 작업 수행 | forEach(System.out::println) |
collect() | 결과를 List, Map 등 원하는 컬렉션으로 수집 | collect(Collectors.toList()) |
count() | 요소의 개수 반환 | count() |
reduce() | 스트림의 요소를 하나의 값으로 집계 | reduce(0, (a, b) -> a + b) |
anyMatch(), allMatch() | 조건에 일치하는 요소가 있는지 검사 | anyMatch(n -> n > 0) |
💡 활용 예시: 데이터 필터링 및 가공
정수 리스트에서 짝수만 필터링하여 10을 더하고, 그 결과를 리스트로 수집하는 코드입니다.
자바 8 이전
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = new ArrayList<>();
for (int n : numbers) {
if (n % 2 == 0) { // 짝수 필터링
result.add(n + 10); // 값 변환
}
}
// 결과: [12, 14]
자바 8 이후 (스트림 파이프라인)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // 중간 연산: 짝수만 필터링
.map(n -> n + 10) // 중간 연산: 각 요소에 10을 더함 (변환)
.collect(Collectors.toList()); // 최종 연산: 결과를 List로 수집
// 결과: [12, 14]
스트림을 사용한 코드가 훨씬 선언적이고 직관적이며, 데이터 처리의 흐름이 명확하게 드러납니다.
🎯 결론: 왜 람다와 스트림을 사용해야 할까?
자바 8의 람다와 스트림 API는 단순한 문법적 변화를 넘어, 자바를 함수형 스타일로 개발할 수 있게 해주는 핵심 도구입니다.
- 가독성 및 간결성: 익명 클래스와 반복문을 대체하여 코드의 양을 줄이고 의미를 명확하게 만듭니다.
- 유연한 데이터 처리: 필터링, 매핑, 정렬 등 복잡한 데이터 처리 로직을 연결된 파이프라인 형태로 쉽게 구현할 수 있습니다.
- 병렬 처리의 용이성: .parallelStream() 메서드만으로 멀티코어 환경에서 데이터를 병렬로 처리하여 성능을 향상할 수 있습니다 (물론 항상 성능이 좋아지는 것은 아니며, 상황에 따라 신중하게 사용해야 합니다).
현대 자바 개발에서 람다와 스트림은 이제 선택이 아닌 필수입니다. 이 두 기능을 숙달하여 더 효율적이고 유지보수가 용이한 코드를 작성해 보세요! 👍
'백엔드 > Java' 카테고리의 다른 글
동시성(Concurrency)과의 싸움: 자바 멀티스레딩의 이해와 동기화 기법 (0) | 2025.10.11 |
---|---|
☕️ 자바 개발자의 필수 지식: JVM의 작동 원리 깊이 이해하기 - 메모리 구조와 가비지 컬렉션(GC) (0) | 2025.10.09 |
🌟 예외(Exception) 처리, 깔끔하게 하는 법: try-catch-finally 실전 노하우 (0) | 2025.10.07 |
자바 컬렉션 프레임워크 뽀개기: List, Set, Map 언제 사용해야 할까? (0) | 2025.10.06 |
Spring Boot와 Java: 백엔드 개발의 기본기 (0) | 2025.10.05 |