# 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>`: 두 매개변수 변환
'BackEnd > Java' 카테고리의 다른 글
| String 주요 메서드 예제 (0) | 2026.02.09 |
|---|---|
| Stream API 연습문제 - 도서 관리 시스템 문제 - 풀이 (0) | 2026.01.30 |
| 11_1. 은행 계좌 관리 시스템 연습문제 (0) | 2026.01.27 |
| 9_1장 HashMap을 활용한 커피 메뉴 관리 예제 (0) | 2026.01.23 |
| 8_3. 도서 관리시스템 (0) | 2026.01.20 |