IT

자바 람다식과 스트림: 사용법, 연습법, 그리고 Optional까지

0and24 2024. 12. 1. 16:07

출처: https://www.udemy.com/course/java-8-and-beyond-for-testers/?couponCode=CMCPSALE24


1. 소개: 람다식과 스트림의 중요성

자바 8은 함수형 프로그래밍의 요소를 도입하면서 개발자들에게 함수를 값으로 다루는 방식과 더불어 데이터 처리 방식을 혁신적으로 바꾸는 도구를 제공했습니다.
람다식스트림은 자바 8에서 등장한 대표적인 기능으로, 코드의 간결성과 가독성을 대폭 향상시킵니다.


2. 람다식: 함수를 값처럼 다루는 자바의 새로운 방식

람다식이란?

람다식은 익명 함수(Anonymous Function)를 간결하게 표현하는 문법으로, 주로 함수형 인터페이스와 함께 사용됩니다.
이를 통해 메서드를 변수처럼 다루거나 매개변수로 전달할 수 있습니다.

사용 이유

  1. 코드 간결화: 불필요한 선언부 제거.
  2. 가독성 향상: 익명 클래스보다 간단한 형태로 표현.
  3. 유연성 제공: 함수형 인터페이스를 활용한 다양한 문맥(Context)에서의 사용 가능.

기본 문법

  • 매개변수가 하나라면 () 생략 가능.
  • 실행문이 하나라면 {} 생략 가능.

예제: 메서드 참조

람다식에서 특정 메서드를 호출만 할 경우 메서드 참조를 사용해 간결하게 표현할 수 있습니다.

Predicate<Apple> filterByRed = (Apple a) -> "RED".equals(a.getColor());
Function<String, Integer> getLength1 = (String s) -> s.length();
Function<String, Integer> getLength2 = String::length;


함수형 인터페이스와 활용

람다식은 함수형 인터페이스에서 사용됩니다.
함수형 인터페이스는 메서드가 하나만 정의된 인터페이스입니다.

filterBy(apples, (Apple apple) -> "RED".equals(apple.getColor()));
filterBy(apples, (a) -> a.getWeight() > 15);

3. 스트림: 데이터 처리의 새로운 패러다임

스트림이란?

스트림은 데이터 소스(컬렉션, 배열 등)에서 데이터를 추출하고, 선언형 코드로 처리할 수 있도록 지원하는 API입니다.

스트림의 주요 특징

  1. 선언형 코드: 데이터를 처리하는 방법보다 "무엇을 할 것인지"에 집중.
    출처: 원티드 (남성 회원의 평균 나이를 계산해야한다면?)


  2. 내부 반복 사용: 외부 반복(for 문) 대신 내부 반복(내부적으로 처리)을 통해 효율적인 데이터 처리.
    출처: 원티드


  3. 데이터 불변성: 원본 데이터를 변경하지 않고 새로운 스트림 생성.

스트림 연산

스트림의 동작 방식

  1. 중간 연산 (Intermediate Operations): 데이터 변환.
    • filter: 조건에 맞는 데이터만 선택.
    • map: 데이터를 변환.
    • sorted: 정렬.
  2. 최종 연산 (Terminal Operations): 결과 생성.
    • forEach: 각 요소에 대해 동작 수행.
    • collect: 결과를 컬렉션으로 반환.
    • reduce: 데이터를 축약.

예제

  1. map: 데이터 변환
    List<Integer> numbers = Arrays.asList(1, 2, 3);
    numbers.stream()
        .map(n -> n * 2)
        .forEach(System.out::println); // 출력: 2, 4, 6
  2. filter: 조건에 맞는 데이터 필터링

    List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    names.stream()
        .filter(name -> name.startsWith("A"))
        .forEach(System.out::println); // 출력: Alice



4. Optional: 안전한 null 처리

null 문제의 근본 원인

  • 가독성 저하: null 체크를 위한 if-else 문이 반복됨.
  • 오류 발생 가능성: NullPointerException의 주된 원인.
  • 자바 철학 위배: 안전한 코드 작성을 어렵게 만듦.

Optional이란?

Optional<T>는 값이 있거나 없는 상황을 명확히 표현하기 위해 도입된 클래스입니다.
null 대신 Optional.empty()와 같은 명확한 표현을 사용하여 더 안전하고 읽기 쉬운 코드를 작성할 수 있습니다.

Optional의 생성 방식

  • Optional.empty()
    • 값이 전혀 없음을 나타낼 때 사용.
Optional<String> emptyValue = Optional.empty();
  • Optional.of()
    • null이 아닌 값을 감싸는 데 사용.
    • null 값을 전달하면 NullPointerException이 발생하므로, 반드시 값이 존재한다고 확신할 때 사용해야 합니다.
Optional<String> nonNullValue = Optional.of("Some Value");
  • Optional.ofNullable()
    • 값이 null일 수도 있고 아닐 수도 있는 경우에 사용.
    • 가장 일반적이며 유연한 방식으로, null-safe 코드를 작성할 때 주로 사용됩니다.
Optional<String> nullableValue = Optional.ofNullable(null); // 결과: Optional.empty()


주요 메서드

  • isPresent() : 값이 있으면 true, 없으면 false.
Optional<String> optionalValue = Optional.ofNullable("Hello");
System.out.println(optionalValue.isPresent()); // 출력: true
  • get() : 값이 존재하면 반환. 없으면 예외 발생.
Optional<String> optionalValue = Optional.of("Hello");
System.out.println(optionalValue.get()); // 출력: Hello
  • orElse(T other) : 값이 없을 경우 기본값을 반환.
Optional<String> optionalValue = Optional.ofNullable(null);
String result = optionalValue.orElse("Default Value");
System.out.println(result); // 출력: Default Value
  • orElseGet(Supplier<? extends T> other) : 값이 없을 때만 Supplier가 실행되도록 설정.
Optional<String> optionalValue = Optional.ofNullable(null);
String result = optionalValue.orElseGet(() -> "Generated Default");
System.out.println(result); // 출력: Generated Default

5. 장단점

람다와 스트림의 장점

  • 코드 간결성: 반복 코드 제거.
  • 가독성 향상: 데이터 흐름을 쉽게 파악 가능.
  • 병렬 처리 지원: 대규모 데이터 처리에 유리.

단점

  • 디버깅 어려움: 내부 동작을 추적하기 힘듦.
  • 가독성 저하 가능성: 너무 복잡한 람다식은 오히려 혼란을 초래.

7. 마무리

람다식과 스트림은 자바를 한층 더 현대적으로 만들어준 기능입니다.
Optional을 활용하면 null 관련 문제를 더 안전하게 처리할 수 있어 코드의 신뢰성이 높아집니다.
작은 예제부터 시작해 프로젝트에 점진적으로 적용해 보세요.

추가 학습 자료: