자바 람다 표현식: 핵심 개념 및 활용 가이드

람다 표현식이란?

람다 표현식은 자바 8에 도입된 기능으로, 함수형 프로그래밍 스타일을 자바에 적용할 수 있게 해줍니다. 이는 단 하나의 추상 메서드를 가진 인터페이스(함수형 인터페이스)의 구현을 간결하게 표현할 수 있도록 돕습니다. 익명 내부 클래스를 작성하는 것에 비해 훨씬 적은 코드로 동일한 기능을 구현할 수 있어 코드의 가독성과 유지보수성을 향상시킵니다.

람다 표현식의 기본 문법은 다음과 같습니다:

(매개변수 목록) -> { 메서드 본문 }
  • 매개변수 목록: 괄호 안에 0개 이상의 매개변수를 지정합니다. 타입 추론이 가능하므로 대부분 생략할 수 있습니다.
  • -> (람다 연산자): 매개변수 목록과 메서드 본문을 구분하는 화살표 토큰입니다.
  • 메서드 본문: 람다가 수행할 작업을 정의합니다. 단일 표현식인 경우 중괄호 {}return 키워드를 생략할 수 있습니다. 여러 문장인 경우 중괄호 {}를 반드시 사용해야 하며, 값을 반환할 때는 return 키워드를 명시해야 합니다.

기본 사용 예시

1. 사용자 정의 함수형 인터페이스

람다 표현식은 함수형 인터페이스에만 적용될 수 있습니다. @FunctionalInterface 어노테이션을 사용하여 컴파일러에게 해당 인터페이스가 단 하나의 추상 메서드만 가지도록 강제할 수 있습니다.

@FunctionalInterface
interface TaskExecutor {
    void perform(String operation);
}

2. 익명 내부 클래스와 람다 비교

다음 예시는 익명 내부 클래스를 사용하여 TaskExecutor 인터페이스를 구현하는 전통적인 방식과 람다 표현식을 사용한 간결한 방식을 비교합니다.

public class LambdaComparison {
    public static void main(String[] args) {
        // 1. 전통적인 익명 내부 클래스 방식
        TaskExecutor oldWay = new TaskExecutor() {
            @Override
            public void perform(String operation) {
                System.out.println("익명 클래스 실행: " + operation);
            }
        };
        oldWay.perform("데이터 로드");

        // 2. 람다 표현식을 사용한 간결한 방식
        TaskExecutor newWay = (op) -> System.out.println("람다 표현식 실행: " + op);
        newWay.perform("파일 처리");
    }
}

출력:

익명 클래스 실행: 데이터 로드
람다 표현식 실행: 파일 처리

3. 매개변수 및 반환 값 처리

람다는 매개변수가 없거나, 여러 개의 매개변수를 가지거나, 값을 반환하는 등 다양한 형태의 메서드 시그니처를 지원합니다.

@FunctionalInterface
interface SimpleAction {
    void execute();
}

@FunctionalInterface
interface NumericCalculator {
    int calculate(int val1, int val2);
}

public class LambdaSignatureTest {
    public static void main(String[] args) {
        // 매개변수 없는 람다
        SimpleAction myAction = () -> System.out.println("매개변수 없이 작업 수행");
        myAction.execute();

        // 여러 매개변수와 반환 값이 있는 람다 (단일 표현식)
        NumericCalculator adder = (x, y) -> x + y;
        int sumResult = adder.calculate(15, 25);
        System.out.println("두 수의 합: " + sumResult);

        // 여러 문장을 포함하는 람다 (중괄호와 return 사용)
        NumericCalculator complexCalc = (numA, numB) -> {
            System.out.println("계산 중: " + numA + " * " + numB);
            int product = numA * numB;
            return product;
        };
        System.out.println("두 수의 곱: " + complexCalc.calculate(7, 6));
    }
}

컬렉션 스트림 API와 람다

람다 표현식은 자바 8의 스트림(Stream) API와 결합될 때 강력한 시너지를 발휘합니다. 스트림은 컬렉션 데이터를 처리하는 추상화된 방법을 제공하며, 람다를 통해 데이터를 필터링, 매핑, 정렬하는 등의 작업을 간결하게 수행할 수 있습니다.

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamLambdaExample {
    public static void main(String[] args) {
        List<String> fruitList = new ArrayList<>();
        fruitList.add("Apple");
        fruitList.add("Banana");
        fruitList.add("Cherry");
        fruitList.add("Date");
        fruitList.add("Fig");

        // 1. 모든 요소 순회 및 출력 (forEach)
        System.out.println("--- 모든 과일 ---");
        fruitList.forEach(item -> System.out.println("과일: " + item));

        // 2. 특정 조건으로 필터링 (filter)
        List<String> longNames = fruitList.stream()
                .filter(s -> s.length() > 5)
                .collect(Collectors.toList());
        System.out.println("\n--- 이름이 5자 초과하는 과일 ---");
        System.out.println(longNames);

        // 3. 요소 변환 (map)
        List<String> upperCaseFruits = fruitList.stream()
                .map(String::toUpperCase) // 메서드 참조 사용
                .collect(Collectors.toList());
        System.out.println("\n--- 대문자로 변환된 과일 ---");
        System.out.println(upperCaseFruits);

        // 4. 정렬 (sorted)
        List<String> sortedFruits = fruitList.stream()
                .sorted((s1, s2) -> s1.compareTo(s2)) // 알파벳 순으로 정렬
                .collect(Collectors.toList());
        System.out.println("\n--- 알파벳 순으로 정렬된 과일 ---");
        System.out.println(sortedFruits);
    }
}

메서드 참조로 람다 간소화

람다 표현식이 단순히 기존 메서드를 호출하는 역할을 할 때는 메서드 참조(Method Reference)를 사용하여 코드를 더욱 간결하게 만들 수 있습니다. 이는 특정 람다 형태의 축약형이라고 볼 수 있습니다.

import java.util.Arrays;
import java.util.List;

public class MethodReferenceExample {

    // 예시로 사용할 정적 메서드
    public static void displayItem(String item) {
        System.out.println("항목: " + item);
    }

    public static void main(String[] args) {
        List<String> productNames = Arrays.asList("Laptop", "Monitor", "Keyboard");

        // 람다 표현식 원래 형태
        System.out.println("--- 람다 표현식 ---");
        productNames.forEach(name -> displayItem(name));

        // 정적 메서드 참조로 간소화
        System.out.println("\n--- 메서드 참조 ---");
        productNames.forEach(MethodReferenceExample::displayItem);
    }
}

람다 사용 시 유의사항

  • 함수형 인터페이스에만 적용: 람다는 단일 추상 메서드를 가진 함수형 인터페이스에만 사용할 수 있습니다. 여러 개의 추상 메서드를 가진 인터페이스에는 적용할 수 없습니다.
  • 캡처링 변수의 제약: 람다 내부에서 외부 스코프의 지역 변수를 사용할 경우, 해당 변수는 final이거나 사실상 final(effectively final)이어야 합니다. 즉, 람다 외부에서 해당 변수의 값을 변경할 수 없습니다.
  • this의 의미: 람다는 자신만의 this 컨텍스트를 가지지 않습니다. 람다 내부의 this는 람다를 선언한 외부 클래스의 인스턴스를 참조합니다. 이는 익명 내부 클래스와의 주요 차이점 중 하나입니다.
  • 가독성 유지: 복잡한 로직을 무리하게 한 줄 람다로 압축하기보다는, 가독성을 최우선으로 고려하여 여러 줄의 람다 또는 일반 메서드를 사용하는 것이 좋습니다.

태그: java lambda StreamAPI FunctionalProgramming Java8

7월 26일 04:24에 게시됨