컬렉션 프레임워크와 Stream API - 람다 표현식

urur-27·2025년 2월 19일

StreamAPI

목록 보기
4/6

functional interface는 보통 현업에서는 람다식으로 많이 쓰이고 있음.
Stream은 리스트 객체를 변환하고 새로운 객체를 얻고 싶을 때 많이 쓰임

function 뭘 받아서 돌려줌
Prediacte 뭘 받아서 boolean으로 돌려줌
Consumer 뭘 받아서 행동함(안 돌려주고 소비함)


람다 표현식

람다 표현식은 익명 클래스를 간결하게 표현하는 방식
람다는 항상 어떤 FunctionalInterface가 들어가야 하는 자리에 사용되며, 타입 추론이 가능함

람다 표현식의 기본 문법

(매개변수) -> { 실행 코드 }

람다 표현식 예제

Function<Integer, String> converter = num -> "Number: " + num;
System.out.println(converter.apply(10));  // Number: 10

람다 표현식에서 {} 생략 가능

Predicate<Integer> isPositive = num -> num > 0;
System.out.println(isPositive.test(5));  // true

변수 스코프와 캡처Caputure effective final

람다 표현식에서는 외부 지역 변수(메서드 내부에서 선언된 변수)를 활용할 때, 해당 변수가 effective final 상태여야 한다.

  • effecitve final : 코드상 final 키워드를 사용하지 않아도. 실질적으로 더 이상 값이 변경되지 않는 함수
  • 익명 클래스나 람다 안에서 이 변수를 수정하려 하면 컴파일 에러 발생

캡처(Capture) 개념

  • 람다식이 외부 스코프에 있는 변수를 참조하는 행위를 “캡처”라고 한다.
  • 실제로는 컴파일 시점에 람다 내부에서 외부 변수를 복제하거나, 외부 변수가 상수화되어 안전하게 참조되도록 처리된다.
  • 이때 외부 변수를 재할당하면(값을 바꾸려 하면) “이미 캡처된” 상태와 충돌이 생기므로 허용되지 않는다.

왜 지역 변수는 effective final이어야 할까?

  1. 생존 기간Scope 차이
    • 메서드 내부의 지역 변수는 메서드가 종료되면 스택 메모리에서 사라진다.
    • 람다는 메서드 종류 후에도 실행될 수 있으므로, 지역 변수를 안전하게 참조하려면 "값이 바꾸지 않는다"는 보장이 필요하다.
  2. 동기화/안정성 이슈
    • 람다가 비동기로 실행되거나, 메서드가 반환된 후에도 동작한다면, 해당 지역 변수가 변경되면 안 되는 상황이 발생한다.
    • 값이 바뀌지 않는다면(즉, effectively final), 컴파일러가 내부적으로 해당 변수를 “캡처”하여 안전하게 사용할 수 있다.
  3. 객체 필드와의 차이
    • 인스턴스 필드나 static 필드는 지역 변수와 달리 스택이 아닌 힙 영역 등에 존재하며, 객체의 생존 기간과 함께 유지된다.
    • 따라서 람다는 지역 변수가 아닌 필드를 변경해도 컴파일 시점에 에러가 나지 않는다.

정리하자면, 지역 변수가 effective final일 때만 람다 내에서 이를 안전하게 캡처하고 재사용할 수 있다. 이 제약을 통해 자바는 내부적으로 지역 변수의 스코프와 생명 주기 문제를 해결하고, 컴파일·런타임 오류를 방지하고 있다.


메서드 참조Method Reference

람다 표현식이 "이미 존재하는 메서드 하나만 호출"하는 형태라면, 메서드 참조로 더 간결하게 작성 가능하다.

  1. 정적 메서드 참조
    표현식 : (x) -> Math.abs(x)
    메서드 참조: ClassName::staticMethod
  2. 인스턴스 메서드 참조
    표현식 : (s) -> s.toUpperCase()
    메서드 참조: instance::instanceMethod
  3. 생성자 참조
    표현식 : () -> new ArrayList<>()
    메서드 참조: ArrayList::new
    예제
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);

forEach(name -> System.out.println(name)) 를 System.out::println 으로 대체 가능


주요 함수형 인터페이스

자바 8부터 제공되는 java.util.function 패키지에는 대표적인 함수형 인터페이스들이 준비되어 있다.
이들을 이용해 filter(), map() 등의 로직을 짧고 명확하게 작성할 수 있다.

Predicate

  • 정의: boolean test(T t);
  • 용도: 조건식을 람다로 표현할 때 사용 (필터링 등)

Function<T, R>

  • 정의: R apply(T t);
  • 용도: 입력값 T를 받아 가공 후 R 반환 (변환, 매핑)

Consumer

  • 정의: void accept(T t);
  • 용도: 입력값 T를 받아 소비(출력, 저장, 로깅 등)하고 반환값 없음

Supplier

  • 정의: T get();
  • 용도: 인자 없이 무언가를 공급(생성)하여 반환

정리

  • 람다 표현식은 익명 클래스를 간결하게 표현하는 방법으로 함수형 인터페이스를 활용해야 한다.
  • 메서드 참조를 사용하면 람다를 더 간결하게 표현할 수 있다.
  • 람다 내에서 지역 변수를 참조할 때는 effective final 상태여야 한다.
  • Stream API 를 사용하면 컬렉션 데이터를 간결하고 직관적으로 처리할 수 있다.
  • 함수형 인터페이스(Predicate, Function, Consumer, Supplier)를 적절히 활용하면 가독성이 높은 코드 작성이 가능하다.
profile
끄아악

0개의 댓글