[Java ] 개념노트 55 람다식이란 무엇인가

[Java 개념노트 55] 람다식이란 무엇인가?

안녕하세요. Java 개념노트 시리즈 쉰다섯 번째 글입니다.

지난 글에서는 제네릭의 타입 범위를 제한하는 제한된 제네릭와일드카드에 대해 정리했습니다. 이번 글에서는 자바에서 코드를 더 짧고 간결하게 표현할 수 있는 람다식에 대해 알아보겠습니다.

람다식은 처음 보면 () -> 모양 때문에 낯설게 느껴질 수 있습니다. 하지만 핵심은 간단합니다. 메서드처럼 실행할 수 있는 코드를 짧게 표현하는 문법입니다.


1. 람다식이란?

람다식은 메서드처럼 동작하는 코드를 짧게 표현하는 문법입니다.

자바에서는 람다식을 사용해서 인터페이스의 메서드 구현을 간결하게 작성할 수 있습니다. 특히 추상 메서드가 하나뿐인 인터페이스와 함께 사용됩니다.

쉽게 말하면
람다식은 “이 메서드가 실행되면 이런 일을 해라”라는 코드를 짧게 적는 방법입니다.

2. 람다식 기본 형태

람다식의 기본 형태는 다음과 같습니다.

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

예를 들어 매개변수 없이 문장 하나를 출력하는 람다식은 다음처럼 작성할 수 있습니다.

() -> {
    System.out.println("Hello Lambda");
}

화살표 -> 왼쪽에는 매개변수를 적고, 오른쪽에는 실행할 코드를 적습니다.

3. 람다식은 혼자 사용할 수 없다

람다식은 아무 곳에나 단독으로 작성해서 사용하는 문법이 아닙니다. 람다식은 보통 함수형 인터페이스 타입에 대입해서 사용합니다.

// () -> System.out.println("Hello"); 
// 이렇게 단독으로만 쓰면 사용할 대상이 없습니다.

람다식은 “어떤 인터페이스의 어떤 메서드를 구현하는 코드인지”가 정해져야 사용할 수 있습니다.

4. 함수형 인터페이스란?

함수형 인터페이스는 추상 메서드를 하나만 가진 인터페이스입니다.

interface Hello {
    void say();
}

위 인터페이스에는 추상 메서드가 say() 하나만 있습니다. 이런 인터페이스는 람다식으로 구현할 수 있습니다.

핵심 포인트
람다식은 추상 메서드가 하나뿐인 인터페이스를 간단하게 구현할 때 사용합니다.

5. 람다식 기본 예제

매개변수 없이 문장을 출력하는 람다식을 만들어보겠습니다.

public class Main {
    public static void main(String[] args) {
        Hello hello = () -> {
            System.out.println("안녕하세요. 람다식입니다.");
        };

        hello.say();
    }
}

interface Hello {
    void say();
}

실행 결과는 다음과 같습니다.

안녕하세요. 람다식입니다.

Hello 인터페이스의 say() 메서드가 람다식으로 구현되었습니다. hello.say()를 호출하면 람다식 오른쪽의 코드가 실행됩니다.

6. 익명 구현 객체와 람다식 비교

람다식은 익명 구현 객체를 더 짧게 표현한 형태로 이해할 수 있습니다.

익명 구현 객체

Hello hello = new Hello() {
    @Override
    public void say() {
        System.out.println("안녕하세요.");
    }
};

람다식

Hello hello = () -> {
    System.out.println("안녕하세요.");
};

두 코드는 비슷한 역할을 합니다. 람다식을 사용하면 반복되는 코드가 줄어들어 더 간결하게 표현할 수 있습니다.

7. 실행 코드가 한 줄이면 중괄호를 생략할 수 있다

람다식의 실행 코드가 한 줄이면 중괄호 { }를 생략할 수 있습니다.

Hello hello = () -> System.out.println("안녕하세요.");

hello.say();

실행 결과는 다음과 같습니다.

안녕하세요.

한 줄짜리 람다식은 이렇게 짧게 작성할 수 있습니다.

8. 매개변수가 있는 람다식

람다식은 매개변수를 받을 수도 있습니다.

public class Main {
    public static void main(String[] args) {
        MessagePrinter printer = (String message) -> {
            System.out.println(message);
        };

        printer.print("Java Lambda");
    }
}

interface MessagePrinter {
    void print(String message);
}

실행 결과는 다음과 같습니다.

Java Lambda

MessagePrinter 인터페이스의 print(String message) 메서드를 람다식으로 구현했습니다.

9. 매개변수 타입 생략하기

람다식에서는 매개변수 타입을 생략할 수 있습니다. 자바가 인터페이스의 메서드 형태를 보고 타입을 추론하기 때문입니다.

MessagePrinter printer = message -> System.out.println(message);

printer.print("타입을 생략한 람다식");

위 코드에서 messageString 타입입니다. 하지만 인터페이스의 메서드가 void print(String message)이기 때문에 람다식에서 타입을 생략할 수 있습니다.

10. 매개변수가 하나면 괄호도 생략할 수 있다

매개변수가 하나이고 타입을 생략하면 괄호 ( )도 생략할 수 있습니다.

MessagePrinter printer = message -> System.out.println(message);

하지만 매개변수가 두 개 이상이면 괄호를 생략할 수 없습니다.

Calculator calculator = (a, b) -> a + b;

기억하기
매개변수가 하나일 때만 괄호를 생략할 수 있습니다. 매개변수가 없거나 두 개 이상이면 괄호가 필요합니다.

11. 값을 반환하는 람다식

람다식은 값을 반환할 수도 있습니다.

public class Main {
    public static void main(String[] args) {
        Calculator calculator = (a, b) -> {
            return a + b;
        };

        int result = calculator.add(10, 20);

        System.out.println(result);
    }
}

interface Calculator {
    int add(int a, int b);
}

실행 결과는 다음과 같습니다.

30

Calculator 인터페이스의 add() 메서드는 int를 반환합니다. 그래서 람다식 안에서 return a + b;를 작성했습니다.

12. 반환 코드가 한 줄이면 return을 생략할 수 있다

반환하는 코드가 한 줄이면 중괄호와 return을 생략할 수 있습니다.

Calculator calculator = (a, b) -> a + b;

int result = calculator.add(10, 20);

System.out.println(result);

위 람다식은 아래 코드와 비슷한 의미입니다.

Calculator calculator = (a, b) -> {
    return a + b;
};

짧게 쓸 수 있지만, 코드가 복잡해지면 중괄호를 사용하는 편이 더 읽기 쉬울 수 있습니다.

13. 중괄호를 쓰면 return을 명확히 작성해야 한다

반환값이 있는 람다식에서 중괄호를 사용했다면 return을 작성해야 합니다.

올바른 예

Calculator calculator = (a, b) -> {
    return a + b;
};

잘못된 예

Calculator calculator = (a, b) -> {
    a + b; // 오류 발생
};

중괄호를 사용하면 실행 블록이 되므로 반환값이 필요한 경우 return을 명확히 작성해야 합니다.

14. 람다식 문법 정리

람다식 문법을 정리하면 다음과 같습니다.

형태 예시 설명
매개변수 없음 () -> 실행문 괄호 필요
매개변수 1개 x -> 실행문 타입 생략 시 괄호 생략 가능
매개변수 2개 이상 (a, b) -> 실행문 괄호 필요
실행문 여러 줄 (a, b) -> { ... } 중괄호 사용
한 줄 반환 (a, b) -> a + b return 생략 가능

15. @FunctionalInterface

함수형 인터페이스에는 @FunctionalInterface를 붙일 수 있습니다.

@FunctionalInterface
interface Calculator {
    int add(int a, int b);
}

이 어노테이션을 붙이면 해당 인터페이스가 추상 메서드 하나만 가지는지 컴파일러가 확인해줍니다. 만약 추상 메서드를 두 개 작성하면 오류가 발생합니다.

@FunctionalInterface
interface Calculator {
    int add(int a, int b);

    // int minus(int a, int b); // 오류 발생
}

람다식으로 사용할 인터페이스라면 @FunctionalInterface를 붙여두면 실수를 줄일 수 있습니다.

16. 람다식과 Runnable

자바에서 자주 만나는 함수형 인터페이스 중 하나가 Runnable입니다. Runnable은 매개변수도 없고 반환값도 없는 run() 메서드를 가집니다.

public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println("작업을 실행합니다.");
        };

        task.run();
    }
}

실행 결과는 다음과 같습니다.

작업을 실행합니다.

Runnable은 이미 자바에서 제공하는 인터페이스입니다. 람다식으로 run() 메서드의 실행 내용을 작성했습니다.

17. ArrayList forEach와 람다식

컬렉션의 forEach() 메서드에서도 람다식을 사용할 수 있습니다.

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> subjects = new ArrayList<>();

        subjects.add("Java");
        subjects.add("Spring");
        subjects.add("SQL");

        subjects.forEach(subject -> System.out.println(subject));
    }
}

실행 결과는 다음과 같습니다.

Java
Spring
SQL

subject -> System.out.println(subject)는 목록의 값을 하나씩 받아 출력하는 람다식입니다.

18. Comparator와 람다식 맛보기

람다식은 정렬 기준을 간단하게 작성할 때도 자주 사용됩니다. 예를 들어 문자열 길이를 기준으로 정렬할 수 있습니다.

import java.util.ArrayList;
import java.util.Collections;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> words = new ArrayList<>();

        words.add("Java");
        words.add("SQL");
        words.add("Spring");

        Collections.sort(words, (a, b) -> a.length() - b.length());

        System.out.println(words);
    }
}

실행 결과는 다음과 같습니다.

[SQL, Java, Spring]

문자열 길이가 짧은 순서로 정렬되었습니다. (a, b) -> a.length() - b.length()가 정렬 기준 역할을 합니다.

19. 람다식은 코드를 짧게 만들지만 무조건 좋은 것은 아니다

람다식은 코드를 짧게 만들 수 있지만, 너무 복잡한 로직을 한 줄에 몰아넣으면 오히려 읽기 어려워질 수 있습니다.

읽기 쉬운 예

Calculator calculator = (a, b) -> a + b;

복잡해질 수 있는 예

Calculator calculator = (a, b) -> {
    int sum = a + b;
    int bonus = 10;
    return sum + bonus;
};

실행 코드가 여러 줄이거나 조건이 많아지면 메서드로 분리하는 것이 더 깔끔할 수 있습니다. 람다식은 짧고 명확한 동작을 표현할 때 가장 잘 어울립니다.

20. 람다식 전체 예제

이번에는 여러 형태의 람다식을 한 번에 사용하는 예제를 보겠습니다.

public class Main {
    public static void main(String[] args) {
        Hello hello = () -> System.out.println("람다식 시작");

        hello.say();

        MessagePrinter printer = message -> System.out.println("메시지: " + message);

        printer.print("Java");

        Calculator addCalculator = (a, b) -> a + b;
        Calculator multiplyCalculator = (a, b) -> a * b;

        System.out.println(addCalculator.calculate(10, 20));
        System.out.println(multiplyCalculator.calculate(10, 20));
    }
}

@FunctionalInterface
interface Hello {
    void say();
}

@FunctionalInterface
interface MessagePrinter {
    void print(String message);
}

@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}

실행 결과는 다음과 같습니다.

람다식 시작
메시지: Java
30
200

매개변수가 없는 람다식, 매개변수가 하나인 람다식, 값을 반환하는 람다식을 모두 사용했습니다.

21. 람다식 사용 시 자주 하는 실수

람다식을 처음 배울 때는 아래와 같은 실수를 자주 합니다.

실수 문제점 해결 방법
추상 메서드가 여러 개인 인터페이스에 람다식 사용 어떤 메서드를 구현할지 알 수 없음 추상 메서드가 하나인 함수형 인터페이스 사용
매개변수가 없는데 괄호 생략 문법 오류 발생 () -> 실행문 형태 사용
매개변수가 두 개인데 괄호 생략 문법 오류 발생 (a, b) -> 실행문 형태 사용
중괄호를 쓰고 return 누락 반환값이 필요한 메서드에서 오류 발생 { return 값; } 형태 사용
람다식을 무조건 짧게만 쓰려고 함 코드가 오히려 읽기 어려워질 수 있음 복잡하면 메서드로 분리

22. 직접 연습해보기

아래 코드를 직접 작성하고 실행해보세요.

import java.util.ArrayList;
import java.util.Collections;

public class Main {
    public static void main(String[] args) {
        ButtonClick click = () -> System.out.println("버튼을 클릭했습니다.");

        click.click();

        TextChecker checker = text -> text.length() >= 4;

        System.out.println(checker.check("Java"));
        System.out.println(checker.check("SQL"));

        ArrayList<String> words = new ArrayList<>();

        words.add("Spring");
        words.add("Java");
        words.add("SQL");

        Collections.sort(words, (a, b) -> a.length() - b.length());

        words.forEach(word -> System.out.println(word));
    }
}

@FunctionalInterface
interface ButtonClick {
    void click();
}

@FunctionalInterface
interface TextChecker {
    boolean check(String text);
}

실행 결과는 다음과 같습니다.

버튼을 클릭했습니다.
true
false
SQL
Java
Spring

23. 이번 글 정리

이번 글에서는 코드를 더 짧고 간결하게 표현할 수 있는 람다식에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.

  • 람다식은 메서드처럼 실행할 수 있는 코드를 짧게 표현하는 문법이다.
  • 람다식의 기본 형태는 (매개변수) -> { 실행 코드 }이다.
  • 람다식은 함수형 인터페이스와 함께 사용된다.
  • 함수형 인터페이스는 추상 메서드가 하나뿐인 인터페이스이다.
  • 실행 코드가 한 줄이면 중괄호를 생략할 수 있다.
  • 매개변수 타입은 생략할 수 있다.
  • 매개변수가 하나일 때는 괄호를 생략할 수 있다.
  • 매개변수가 없거나 두 개 이상이면 괄호가 필요하다.
  • 반환 코드가 한 줄이면 return을 생략할 수 있다.
  • 중괄호를 사용하면 반환값이 필요한 경우 return을 작성해야 한다.
  • @FunctionalInterface를 붙이면 함수형 인터페이스 규칙을 확인할 수 있다.
  • 람다식은 forEach(), 정렬 기준 작성 등에서 자주 사용된다.

한 줄 요약
람다식은 함수형 인터페이스의 추상 메서드 구현을 짧고 간결하게 표현하는 자바 문법입니다.

다음 글 예고
다음 글에서는 [Java 개념노트 56] 함수형 인터페이스 이해하기라는 주제로 Predicate, Consumer, Supplier, Function 같은 자바 기본 함수형 인터페이스를 정리해보겠습니다.

GWDEVELBlog Java 개념노트 시리즈