백엔드/Java

🔥 자바 8 이후 필수 문법: 람다(Lambda)와 스트림(Stream) API 활용법

hawon6691 2025. 10. 8. 11:19
728x90

자바(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는 단순한 문법적 변화를 넘어, 자바를 함수형 스타일로 개발할 수 있게 해주는 핵심 도구입니다.

  1. 가독성 및 간결성: 익명 클래스와 반복문을 대체하여 코드의 양을 줄이고 의미를 명확하게 만듭니다.
  2. 유연한 데이터 처리: 필터링, 매핑, 정렬 등 복잡한 데이터 처리 로직을 연결된 파이프라인 형태로 쉽게 구현할 수 있습니다.
  3. 병렬 처리의 용이성: .parallelStream() 메서드만으로 멀티코어 환경에서 데이터를 병렬로 처리하여 성능을 향상할 수 있습니다 (물론 항상 성능이 좋아지는 것은 아니며, 상황에 따라 신중하게 사용해야 합니다).

현대 자바 개발에서 람다와 스트림은 이제 선택이 아닌 필수입니다. 이 두 기능을 숙달하여 더 효율적이고 유지보수가 용이한 코드를 작성해 보세요! 👍
 

728x90