자바는 철저한 객체 지향 언어다. 따라서 어떤 기능(메서드)을 실행하거나 다른 메서드의 파라미터로 전달하려면, 반드시 그 기능을 포함하는 ‘객체(클래스)’를 통째로 넘겨주어야 한다.
단순히 “주어진 숫자가 짝수인지 확인”하는 로직 하나를 전달하고 싶을 뿐인데, 매번 EvenNumberFilter라는 클래스 파일을 새로 만들고 인터페이스를 구현(implements)해야 하는 과정은 너무나도 비효율적이다.

이러한 불편함을 해결하고 코드를 극적으로 간결하게 만들기 위해 등장한 세 가지 개념의 진화 과정을 단계별로 알아본다.

1. 1단계: 익명 클래스 (Anonymous Class)

매번 .java 클래스 파일을 새로 만드는 번거로움을 피하기 위해 자바가 처음 내놓은 타협안이다. 이름이 없는 1회용 클래스를 코드 중간에 즉석에서 선언하고 객체를 생성하는 방식이다.

🔗 Reference

[Java 기초] [참조] 익명 클래스(Anonymous Class)는 도대체 어떤 '번거로움'을 피하게 해줄까?

// 숫자를 검사하는 인터페이스 정의
interface NumberFilter {
    boolean check(int number);
}

public class Main {
    public static void main(String[] args) {
        
        // [익명 클래스] 인터페이스를 즉석에서 구현하여 1회용 객체를 생성!
        NumberFilter isEven = new NumberFilter() {
            @Override
            public boolean check(int number) {
                return number % 2 == 0; // 짝수인지 검사하는 핵심 로직
            }
        };
        
        System.out.println(isEven.check(4)); // true
    }
}

❗️한계: 클래스 파일은 만들지 않아도 되지만, 여전히 코드가 뚱뚱하다. 우리가 진짜 전달하고 싶은 핵심은 return number % 2 == 0; 단 한 줄뿐인데, 이를 위해 5줄의 무의미한 껍데기 코드(보일러플레이트)를 작성해야 한다.

2. 2단계: 함수형 인터페이스 (Functional Interface)

껍데기를 다 벗겨내고 핵심 로직 한 줄만 남기려면, 컴파일러와 개발자 사이에 “이 인터페이스는 무조건 메서드가 1개뿐이다”라는 강력한 약속이 필요하다. 메서드가 2개 이상이면, 나중에 코드를 줄였을 때 자바가 도대체 어떤 메서드를 실행하라는 것인지 알 수 없기 때문이다.

이렇게 구현해야 할 추상 메서드가 딱 1개만 존재하는 인터페이스를 ‘함수형 인터페이스’라고 부른다.

// 💡 @FunctionalInterface 어노테이션
// "이 인터페이스는 코드를 간략화(람다식)해서 쓸 거니까, 절대 메서드를 2개 이상 넣지 마!" 
// 라고 컴파일러에게 감시를 지시하는 역할
@FunctionalInterface 
interface NumberFilter {
    boolean check(int number); 
    // void anotherMethod(); // 💥 주석 해제 시 컴파일 에러 발생!
}

3. 3단계: 람다식 (Lambda Expression)

함수형 인터페이스라는 약속이 지켜진다면, 마침내 람다식을 통해 군더더기 코드를 모두 날려버리고 ‘핵심 로직(함수)’ 자체만 쏙 뽑아서 작성할 수 있게 된다.

익명 클래스로 작성했던 코드를 람다식 (매개변수) -> { 실행문 }으로 바꾸면 마법처럼 단 한 줄로 압축된다.

public class Main {
    public static void main(String[] args) {
        
        // [람다식] 컴파일러가 문맥을 유추할 수 있는 모든 껍데기를 생략!
        NumberFilter isEven = (number) -> number % 2 == 0;
        
        System.out.println(isEven.check(4)); // true
    }
}

어차피 NumberFilter에는 check() 메서드 하나밖에 없으므로 메서드 이름과 매개변수 타입(int)을 생략할 수 있고, 실행문이 한 줄이므로 return과 중괄호 {}까지 모두 생략된 것이다.

💡 한눈에 보는 공통점과 차이점

이 세 가지 개념은 모두 “클래스 파일을 따로 만들지 않고, 필요한 기능을 즉석에서 구현한다”는 완벽히 동일한 목적(공통점)을 가지고 있다.

구분 익명 클래스 함수형 인터페이스 람다식
역할 이름 없는 1회용 클래스를 즉석에서 만드는 과거의 문법 람다식을 사용하기 위한 필수 전제 조건 (설계도) 익명 클래스를 극적으로 압축한 현대의 문법
메서드 개수 인터페이스 내 메서드 개수 제한 없음 (여러 개 구현 가능) 반드시 단 1개의 추상 메서드만 가져야 함 함수형 인터페이스만 구현 가능 (1대1 매칭)
코드 길이 매우 길고 복잡함 (인터페이스 설계에 해당함) 극도로 짧고 직관적임 (-> 사용)

💡 결론적으로 람다식은 새로운 창조물이 아니라, 함수형 인터페이스를 구현하는 익명 클래스의 코드를 극적으로 짧게 줄여주는 축약형 문법일 뿐이다.