본문 바로가기
  • 코딩, 허쌤이 떠먹여 줄게
BackEnd/Java

Java 람다 표현식

by 허쌤 2026. 1. 28.

# Java 람다 표현식(Lambda Expression) 완벽 가이드

## 📋 목차
1. [람다란 무엇인가?](#1-람다란-무엇인가)
2. [람다 표현식 기본 문법](#2-람다-표현식-기본-문법)
3. [함수형 인터페이스](#3-함수형-인터페이스)
4. [람다 표현식 예제](#4-람다-표현식-예제)
5. [메서드 참조](#5-메서드-참조)
6. [실전 활용 예제](#6-실전-활용-예제)

---

## 1. 람다란 무엇인가?

### 1.1 정의

**람다 표현식(Lambda Expression)**은 Java 8에서 도입된 기능으로, **익명 함수(Anonymous Function)**를 간결하게 표현하는 방법입니다.

### 1.2 왜 사용하는가?

**기존 방식 (익명 클래스):**
```java
// 버튼 클릭 이벤트 처리
button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        System.out.println("버튼 클릭됨");
    }
});
```

**람다 표현식:**
```java
button.setOnClickListener(v -> System.out.println("버튼 클릭됨"));
```

**장점:**
- 코드가 간결해짐
- 가독성 향상
- 함수형 프로그래밍 스타일 지원

---

## 2. 람다 표현식 기본 문법

### 2.1 기본 구조

```java
(매개변수) -> { 실행문 }
```

### 2.2 문법 규칙

1. **매개변수가 1개인 경우**: 괄호 생략 가능
   ```java
   x -> x * 2
   (x) -> x * 2  // 둘 다 동일
   ```

2. **매개변수가 없는 경우**: 빈 괄호 사용
   ```java
   () -> System.out.println("Hello")
   ```

3. **실행문이 1개인 경우**: 중괄호 생략 가능
   ```java
   x -> x * 2
   x -> { return x * 2; }  // 둘 다 동일
   ```

4. **return 문만 있는 경우**: return 생략 가능
   ```java
   x -> x * 2
   x -> { return x * 2; }  // 둘 다 동일
   ```

---

## 3. 함수형 인터페이스

### 3.1 정의

**함수형 인터페이스(Functional Interface)**는 **단 하나의 추상 메서드만** 가진 인터페이스입니다.

### 3.2 @FunctionalInterface 어노테이션

```java
@FunctionalInterface
public interface MyFunction {
    int calculate(int a, int b);
    // 추상 메서드가 1개만 있어야 함
}
```

### 3.3 Java에서 제공하는 주요 함수형 인터페이스

#### 1. `Predicate<T>` - 조건 검사
```java
Predicate<Integer> isEven = x -> x % 2 == 0;
System.out.println(isEven.test(4));  // true
```

#### 2. `Function<T, R>` - 변환
```java
Function<String, Integer> length = s -> s.length();
System.out.println(length.apply("Hello"));  // 5
```

#### 3. `Consumer<T>` - 소비 (반환값 없음)
```java
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello");  // Hello 출력
```

#### 4. `Supplier<T>` - 공급 (매개변수 없음)
```java
Supplier<String> greeting = () -> "Hello World";
System.out.println(greeting.get());  // Hello World
```

#### 5. `BiFunction<T, U, R>` - 두 매개변수 받아서 변환
```java
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(3, 5));  // 8
```

---

## 4. 람다 표현식 예제

### 4.1 기본 예제

```java
// 1. 정수 두 개를 더하는 람다
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(10, 20));  // 30

// 2. 문자열 길이 반환
Function<String, Integer> getLength = s -> s.length();
System.out.println(getLength.apply("Java"));  // 4

// 3. 짝수 판별
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4));  // true
System.out.println(isEven.test(5));  // false

// 4. 출력
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello Lambda");  // Hello Lambda
```

### 4.2 컬렉션과 함께 사용

#### ArrayList 정렬
```java
List<String> names = Arrays.asList("김철수", "이영희", "박민수");

// 기존 방식
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

// 람다 방식
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
// 또는
names.sort((s1, s2) -> s1.compareTo(s2));
```

#### Stream API와 함께 사용
```java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 짝수만 필터링
List<Integer> evens = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
// 결과: [2, 4, 6, 8, 10]

// 각 숫자에 2 곱하기
List<Integer> doubled = numbers.stream()
    .map(n -> n * 2)
    .collect(Collectors.toList());
// 결과: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

// 합계 구하기
int sum = numbers.stream()
    .reduce(0, (a, b) -> a + b);
// 결과: 55
```

---

## 5. 메서드 참조

### 5.1 정의

**메서드 참조(Method Reference)**는 람다 표현식을 더 간결하게 만드는 방법입니다.

### 5.2 문법

```java
클래스명::메서드명
인스턴스::메서드명
클래스명::new  // 생성자 참조
```

### 5.3 예제

```java
// 1. 정적 메서드 참조
Function<String, Integer> parseInt = Integer::parseInt;
// 람다: s -> Integer.parseInt(s)

// 2. 인스턴스 메서드 참조
String str = "Hello";
Predicate<String> isEmpty = str::isEmpty;
// 람다: s -> str.isEmpty()

// 3. 임의 객체의 인스턴스 메서드 참조
Function<String, Integer> length = String::length;
// 람다: s -> s.length()

// 4. 생성자 참조
Supplier<List<String>> listSupplier = ArrayList::new;
// 람다: () -> new ArrayList<>()

// 5. 출력 예제
Consumer<String> printer = System.out::println;
// 람다: s -> System.out.println(s)
```

---

## 6. 실전 활용 예제

### 6.1 학생 관리 예제

```java
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;

class Student {
    private String name;
    private int score;
    
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    
    public String getName() { return name; }
    public int getScore() { return score; }
    
    @Override
    public String toString() {
        return name + ": " + score + "점";
    }
}

public class LambdaExample {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("김철수", 85),
            new Student("이영희", 92),
            new Student("박민수", 78),
            new Student("최지영", 95),
            new Student("정수진", 88)
        );
        
        // 1. 점수로 정렬
        students.sort((s1, s2) -> s2.getScore() - s1.getScore());
        System.out.println("점수 내림차순:");
        students.forEach(s -> System.out.println(s));
        
        // 2. 90점 이상 학생만 필터링
        List<Student> topStudents = students.stream()
            .filter(s -> s.getScore() >= 90)
            .collect(Collectors.toList());
        System.out.println("\n90점 이상 학생:");
        topStudents.forEach(System.out::println);
        
        // 3. 학생 이름만 추출
        List<String> names = students.stream()
            .map(Student::getName)
            .collect(Collectors.toList());
        System.out.println("\n학생 이름 목록: " + names);
        
        // 4. 평균 점수 계산
        double average = students.stream()
            .mapToInt(Student::getScore)
            .average()
            .orElse(0.0);
        System.out.println("\n평균 점수: " + average);
    }
}
```

### 6.2 이벤트 처리 예제

```java
import java.util.function.Consumer;

public class EventHandler {
    public static void main(String[] args) {
        // 버튼 클릭 이벤트
        Consumer<String> onClick = (buttonName) -> {
            System.out.println(buttonName + " 버튼이 클릭되었습니다!");
        };
        
        onClick.accept("저장");
        onClick.accept("삭제");
        
        // 조건부 실행
        Runnable saveAction = () -> {
            System.out.println("데이터를 저장합니다...");
            System.out.println("저장 완료!");
        };
        
        Runnable deleteAction = () -> {
            System.out.println("데이터를 삭제합니다...");
            System.out.println("삭제 완료!");
        };
        
        executeIf(true, saveAction);
        executeIf(false, deleteAction);
    }
    
    static void executeIf(boolean condition, Runnable action) {
        if (condition) {
            action.run();
        }
    }
}
```

### 6.3 계산기 예제

```java
import java.util.function.BiFunction;

public class Calculator {
    public static void main(String[] args) {
        // 사칙연산을 람다로 표현
        BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
        BiFunction<Integer, Integer, Integer> subtract = (a, b) -> a - b;
        BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
        BiFunction<Integer, Integer, Integer> divide = (a, b) -> b != 0 ? a / b : 0;
        
        int x = 20;
        int y = 5;
        
        System.out.println("덧셈: " + add.apply(x, y));      // 25
        System.out.println("뺄셈: " + subtract.apply(x, y)); // 15
        System.out.println("곱셈: " + multiply.apply(x, y)); // 100
        System.out.println("나눗셈: " + divide.apply(x, y)); // 4
        
        // 계산 실행
        calculate(10, 3, add);
        calculate(10, 3, multiply);
    }
    
    static void calculate(int a, int b, BiFunction<Integer, Integer, Integer> operation) {
        int result = operation.apply(a, b);
        System.out.println("결과: " + result);
    }
}
```

### 6.4 필터링 및 변환 예제

```java
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;

public class FilterTransformExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 1. 짝수만 필터링
        Predicate<Integer> isEven = n -> n % 2 == 0;
        List<Integer> evens = numbers.stream()
            .filter(isEven)
            .collect(Collectors.toList());
        System.out.println("짝수: " + evens);  // [2, 4, 6, 8, 10]
        
        // 2. 5보다 큰 수만 필터링
        Predicate<Integer> greaterThan5 = n -> n > 5;
        List<Integer> largeNumbers = numbers.stream()
            .filter(greaterThan5)
            .collect(Collectors.toList());
        System.out.println("5보다 큰 수: " + largeNumbers);  // [6, 7, 8, 9, 10]
        
        // 3. 각 숫자를 제곱으로 변환
        Function<Integer, Integer> square = n -> n * n;
        List<Integer> squares = numbers.stream()
            .map(square)
            .collect(Collectors.toList());
        System.out.println("제곱: " + squares);  // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
        
        // 4. 조건을 조합
        Predicate<Integer> isEvenAndGreaterThan5 = isEven.and(greaterThan5);
        List<Integer> result = numbers.stream()
            .filter(isEvenAndGreaterThan5)
            .collect(Collectors.toList());
        System.out.println("짝수이면서 5보다 큰 수: " + result);  // [6, 8, 10]
    }
}
```

---

## 7. 람다 표현식의 장단점

### 7.1 장점

1. **코드 간결성**: 불필요한 코드 제거
2. **가독성**: 의도가 명확하게 드러남
3. **함수형 프로그래밍**: 함수를 값처럼 다룰 수 있음
4. **병렬 처리**: Stream API와 함께 사용 시 효율적

### 7.2 단점

1. **디버깅 어려움**: 익명 함수라 스택 트레이스가 복잡
2. **재사용 어려움**: 한 곳에서만 사용되는 경우가 많음
3. **학습 곡선**: 함수형 프로그래밍 개념 필요

---

## 8. 주의사항

### 8.1 변수 캡처

```java
int x = 10;
Runnable r = () -> {
    // x = 20;  // 에러! 람다 내부에서 외부 변수 수정 불가
    System.out.println(x);  // 읽기는 가능
};
```

**규칙:**
- 람다 내부에서 외부 변수를 **읽는 것은 가능**
- 외부 변수를 **수정하는 것은 불가능** (final 또는 effectively final이어야 함)

### 8.2 this 키워드

```java
class MyClass {
    public void method() {
        Runnable r = () -> {
            // this는 MyClass의 인스턴스를 가리킴
            System.out.println(this.toString());
        };
    }
}
```

---

## 9. 실습 문제

### 문제 1: 문자열 리스트 정렬
문자열 리스트를 길이 순으로 정렬하는 람다 표현식을 작성하세요.

```java
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
// 여기에 코드 작성
```

### 문제 2: 숫자 리스트의 최댓값 찾기
람다 표현식을 사용하여 숫자 리스트의 최댓값을 찾으세요.

```java
List<Integer> numbers = Arrays.asList(3, 7, 2, 9, 5, 1);
// 여기에 코드 작성
```

### 문제 3: 조건부 실행
조건이 true일 때만 실행되는 함수를 람다로 작성하세요.

```java
// 조건이 true일 때만 "실행됨" 출력
```

---

## 10. 정리

람다 표현식은:
- **간결한 코드** 작성 가능
- **함수형 인터페이스**와 함께 사용
- **Stream API**와 함께 사용하면 강력함
- **메서드 참조**로 더 간결하게 표현 가능

**핵심 문법:**
```java
(매개변수) -> { 실행문 }
매개변수 -> 실행문  // 간단한 경우
```

**주요 함수형 인터페이스:**
- `Predicate<T>`: 조건 검사
- `Function<T, R>`: 변환
- `Consumer<T>`: 소비
- `Supplier<T>`: 공급
- `BiFunction<T, U, R>`: 두 매개변수 변환