java

[Java] 람다가 필요한 이유와 생략 규칙

inhooo00 2025. 4. 7. 23:19

📍개요

김영한님의 실전 자바 - 고급 3편 강의가 드디어 공개되었다.
이번 편은 코딩 테스트나 실무에서도 빈번하게 활용되는 람다(Lambda)와 스트림(Stream)을 집중적으로 다루는 강의다.
이번 기회에 강의를 따라가며 배운 개념과 인사이트를 정리하고, 내 코드에 적용하고, 기록으로 남겨두려고 한다.

 

 

📍자바에서 람다가 필요한 이유

코드의 본질을 이해해보자.

public static void helloJava() {
    System.out.println("시작");
    System.out.println("Hello Java");
    System.out.println("끝");
}

public static void helloSpring() {
    System.out.println("시작");
    System.out.println("Hello Spring");
    System.out.println("끝");
}

- 중복되는 값(시작, 끝)을 매개변수화 해보면.

 

public static void hello(String message) {
    System.out.println("시작");
    System.out.println(message);
    System.out.println("끝");
}

-  이런 방식으로 표현할 수 있다. 변화하는 값을 외부에서 주입해서 로직의 범용성을 높이는 것 → 전략 패턴의 시작점.

 

 

 

행동을 넘겨라

public static void helloDice() {
    long start = System.nanoTime();
    System.out.println("주사위 = " + new Random().nextInt(6) + 1);
    long end = System.nanoTime();
    System.out.println("소요 시간: " + (end - start));
}

- 이런 코드가 수십개가 된다면? 시작/종료 타이머가 중복된다.

 

@FunctionalInterface
public interface Procedure {
    void run();
}

public class Dice implements Procedure {
    public void run() {
        System.out.println("주사위 = " + new Random().nextInt(6) + 1);
    }
}

public static void hello(Procedure procedure) {
    long start = System.nanoTime();
    procedure.run();
    long end = System.nanoTime();
    System.out.println("소요 시간: " + (end - start));
}

- 인터페이스 정의, 전달하고 싶은 행동을 구현체로 만들기, 행동을 매개변수로 넘겨 실행 방식으로 해결할 수 있다.

 

 

 

람다의 도입: 행동을 객체화하지 않고 전달

hello(() -> {
        System.out.println("주사위 = " + new Random().nextInt(6) + 1);
        });

- 기존 방법의 한계

  • 클래스 생성 → 구현 → 오버라이딩 → 인스턴스 생성 → 전달 = 너무 번거로움.
  • 익명 클래스 → 길고 복잡, 가독성 떨어짐.

- 해결 방법

  • 코드 블록을 값처럼 넘긴다. 이것이 함수형 프로그래밍의 본질이다.
‼️함수형 인터페이스: 왜 꼭 SAM이어야 하나?
람다는 함수다. 이름 없는 함수.
함수를 저장하려면 형(타입)이 필요 → 자바에선 인터페이스가 그 역할을 함.
그런데 인터페이스에 메서드가 2개 이상이면 어떤 걸 구현한 건지 모호함.
그래서 자바는 단 하나의 추상 메서드만 가지는 함수형 인터페이스에만 람다를 할당 가능하게 제한한다.

 

 

 

📍람다의 시그니처와 생략 규칙

람다는 함수형 인터페이스의 추상 메서드에 바인딩되며,
그 메서드의 형태(= 시그니처)에 맞춰 작성해야 한다.

함수형 인터페이스의 시그니처란?

  • 메서드의 이름은 중요하지 않음 ❌
  • 매개변수의 개수 / 타입 / 순서
  • 반환 타입

📌 즉, 람다는 이름 없는 함수이기 때문에 메서드 이름은 무시하고, 나머지 시그니처가 정확히 일치해야 함.

 

람다의 시그니처

@FunctionalInterface
public interface MyFunction {
    int apply(int a, int b); // 시그니처: (int, int) -> int
}

MyFunction f = (int a, int b) -> { return a + b; };

- MyFunction 인터페이스의 apply 메서드에 맞는 람다식은 똑같은 int형 매개변수 2개와 int형 반환값이어야 한다.

 

기본 생략 규칙

// 1. 풀 버전
MyFunction f1 = (int a, int b) -> { return a + b; };

// 2. 타입 생략
MyFunction f2 = (a, b) -> { return a + b; };

// 3. 중괄호 + return 생략 (단일 표현식)
MyFunction f3 = (a, b) -> a + b;

- 이런식으로 자바에서 람다는 생략할 수 있는 로직은 생략하는 걸 선호한다.

 

‼️예외적인 생략 규칙
매개변수가 하나라면 ()도 생략 가능 (매개변수가 없을 경우 ()생략은 불가능)

 

 

 

📍람다의 고차함수

고차함수는 함수를 인자로 받는 경우, 함수를 반환하는 경우가 있다. 람다는 모두 가능하다.

public static void calculate(MyFunction function, int x, int y) {
    System.out.println("계산 결과: " + function.apply(x, y));
}
public static MyFunction getOperation(String op) {
    switch (op) {
        case "add": return (a, b) -> a + b;
        case "mul": return (a, b) -> a * b;
        default: return (a, b) -> 0;
    }
}

- 람다는 자기 혼자서는 의미가 없다. 사용되는 문맥 속에서 어떤 하뭇형 인터페이스에 대입되느냐에 따라 의미가 결정된다. 지금은 반환 값이 MyFunction이기 때문에 자동으로 MyFunction에 매핑되는 것.

 

 

참고 : 김영한의 실전 자바 - 고급 3편