Java 람다 표현식(Lambda) 완벽 이해하기
Java 8 이후 Java 스타일을 완전히 바꿔버린 핵심 기능이 바로:
Lambda Expression (람다 표현식)
입니다.
현대 Java에서는:
- Stream API
- Comparator
- CompletableFuture
- 이벤트 처리
- 함수형 프로그래밍
전부 람다와 연결됩니다.
예:
list.forEach(x -> System.out.println(x));
이런 코드가 바로 람다입니다.
처음 보면:
“함수처럼 생겼네?”
싶지만,
실제로 람다는:
- 함수형 인터페이스
- 익명 클래스
- JVM invokedynamic
- Closure
- Stream 내부 처리
까지 연결되는 매우 중요한 기능입니다.
이번 글에서는:
- 람다란?
- 왜 등장했는가
- 익명 클래스와 차이
- 함수형 인터페이스
- JVM 내부 동작
- 실무 사용 패턴
까지 깊게 정리해보겠습니다.
1. 람다(Lambda)란?
Lambda calculus 기반 개념.
Java에서 람다는:
메서드를 하나의 값처럼 전달하는 문법
입니다.
2. 가장 기본 예시
(a, b) -> a + b
의미:
두 값을 받아 더해서 반환
3. 왜 필요했을까?
Java 8 이전 문제부터 보겠습니다.
4. 예전 Java 방식
예:
//String은 내부에 Comparable 이미 구현되어 있음
//public final class String implements Comparable<String> {}
//그래서 a.compareTo(b) 가능
Collections.sort(list,
new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
}
);
//객체비교인 경우
//User는 age 꺼내서 비교
users.sort(
new Comparator<User>() {
@Override
public int compare(User u1, User u2) {
return Integer.compare(
u1.getAge(),
u2.getAge()
);
}
}
);
5. 문제점
코드가:
너무 장황(verbose)
함.
특히:
작은 동작 하나 정의하려고
익명 클래스 전체 작성
해야 했음.
6. 람다 등장 후
Collections.sort(
list,
(a, b) -> a.compareTo(b)
);
//객체
users.sort(
(u1, u2) ->
Integer.compare(
u1.getAge(),
u2.getAge()
)
);
훨씬 간단.
람다에서 한단계 더 간 3단계까지도 가능 (메서드 참조 + 유틸리티)
= 람다를 더 의도가 잘 보이게 만든 Comparator 팩토리 메서드
list.sort(
Comparator.naturalOrder()
);
//혹은 String -> length 추출해서 비교
list.sort(
Comparator.comparingInt(String::length)
);
//객체 속성비교 (user) -> user.getAge() 까지 넘김
users.sort(
Comparator.comparingInt(User::getAge)
);
체이닝 기법으로 연결 가능
users.sort(
Comparator.comparingInt(User::getAge)
.thenComparing(User::getName)
);
체이닝이 가능한 이유는 Comparator 인터페이스 (람다나 3단계 이거랑 관계 없이 Comparator객체라서 가능)
Comparator에는
default Comparator<T> thenComparing(...)
가 정의되어 있음.
7. 핵심 아이디어
람다는:
“동작 자체를 전달”
하는 것.
8. 문법 구조
기본 형태:
(parameters) -> expression
또는:
(parameters) -> {
statements
}
9. 예시
x -> x * 2
의미:
입력 x 받아서 2배 반환
10. 여러 파라미터
(a, b) -> a + b
11. 코드 블록도 가능
(a, b) -> {
int sum = a + b;
return sum;
}
12. 매개변수 타입 생략 가능
(String s) -> s.length()
↓
s -> s.length()
가능.
13. 왜 타입 생략 가능할까?
컴파일러가:
타입 추론(Type Inference)
수행하기 때문.
14. 그런데 람다는 “아무 데나” 못 쓴다
매우 중요합니다.
람다는 반드시:
함수형 인터페이스(Function Interface)
필요.
15. 함수형 인터페이스란?
Functional interface 는:
추상 메서드가 하나만 있는 인터페이스
입니다.
16. 예시
@FunctionalInterface
interface MyFunction {
void run();
}
17. 왜 하나만 가능할까?
람다는:
“어떤 메서드 구현인지”
명확해야 하기 때문.
18. 사용 예시
MyFunction f =
() -> System.out.println("Hello");
19. 실제로는 무엇인가?
많은 초보자가 오해하는 부분.
람다는:
진짜 함수(function) 아님
입니다.
Java는 여전히:
객체지향 언어
입니다.
20. 실제론 인터페이스 구현 객체
즉 내부적으로는:
함수형 인터페이스 구현 객체
생성.
21. 익명 클래스와 비교
예전 방식:
new Runnable() {
@Override
public void run() {
}
}
람다 방식:
() -> {
}
22. 핵심 차이
람다는:
불필요한 문법 제거
한 것.
23. JVM 내부 동작
매우 중요합니다.
람다는 내부적으로:
invokedynamic
사용.
24. 왜 중요한가?
예전 익명 클래스보다:
- 메모리 효율
- 성능
- 클래스 생성 비용
개선 가능.
25. 익명 클래스 문제
익명 클래스는 실제로:
별도 .class 생성
가능.
26. 람다는 더 최적화 가능
JVM이:
런타임 최적화
하기 더 유리.
27. Closure(클로저) 개념
람다는 외부 변수 접근 가능.
예:
int x = 10;
Runnable r =
() -> System.out.println(x);
28. 왜 가능할까?
람다가:
외부 변수 capture
하기 때문.
29. 그런데 제약 존재
매우 중요.
effectively final
이어야 함.
30. effectively final이란?
명시적 final 없어도:
값 변경 안 되는 변수
입니다.
31. 왜 제한할까?
람다 실행 시점과:
지역 변수 생명주기 차이
문제 때문.
32. 대표 함수형 인터페이스들
Java는 기본 제공 많이 함.
33. Function<T,R>
Function<String, Integer>
의미:
String 입력
Integer 반환
34. Predicate<T>
Predicate<User>
의미:
boolean 반환 조건 함수
35. Consumer<T>
Consumer<String>
의미:
입력만 소비
반환 없음
36. Supplier<T>
Supplier<User>
의미:
값 생성 공급
37. Stream API와 람다
람다가 폭발적으로 중요해진 이유.
예:
list.stream()
.filter(x -> x > 10)
.map(x -> x * 2)
38. Comparator와 람다
실무에서 엄청 많이 사용.
users.sort(
(a, b) -> a.getAge() - b.getAge()
);
39. Method Reference
람다 축약 문법.
예:
System.out::println
40. 의미
x -> System.out.println(x)
축약.
41. 람다 장점
| 장점 | 설명 |
| 코드 간결 | O |
| 함수형 스타일 | O |
| Stream 궁합 좋음 | O |
| 가독성 향상 | O |
42. 람다 단점
| 단점 | 설명 |
| 디버깅 어려움 | O |
| 과도 사용 시 가독성 저하 | O |
| 복잡 로직엔 부적합 | O |
43. 실무에서 자주 하는 실수
1) 람다 안에 복잡 로직 넣음
가독성 폭망 가능.
2) Stream + 람다 과용
디버깅 어려움.
3) effectively final 이해 부족
컴파일 오류 발생.
4) 람다 = 함수형 언어라고 착각
Java는 여전히 OOP 기반.
44. 람다 vs 익명 클래스 차이
| 항목 | 익명 클래스 | 람다 |
| 문법 | 장황 | 간결 |
| this 의미 | 익명 객체 | 바깥 객체 |
| 클래스 생성 | O | 최적화 가능 |
| JVM 처리 | 일반 클래스 | invokedynamic |
45. 핵심 흐름 요약
함수형 인터페이스
↓
람다 표현식 전달
↓
JVM이 구현 객체 생성
↓
실행
46. 가장 중요한 핵심 한 줄
람다는
“함수형 인터페이스 구현을 간결하게 표현하는 Java 문법”
입니다.
47. 정리
람다 표현식은 단순 축약 문법이 아닙니다.
실제로는:
- 함수형 프로그래밍
- Stream API
- Comparator
- invokedynamic
- Closure
- JVM 최적화
전체와 연결되는 Java 8 이후 핵심 기능입니다.
특히 실무에서는:
- 함수형 인터페이스
- effectively final
- Method Reference
- Stream API
- 람다 가독성 관리
를 정확히 이해하는 것이 매우 중요합니다.
다음 글에서는:
익명 클래스 vs 람다
를 this 차이, capture 방식, bytecode 차이, JVM 내부 생성 구조까지 포함해서 깊게 정리해보겠습니다.
'language > java' 카테고리의 다른 글
| Java 함수형 인터페이스(Function, Predicate, Consumer 등) 완벽 이해하기 (0) | 2026.05.27 |
|---|---|
| Java 익명 클래스 vs 람다 완벽 이해하기 (1) | 2026.05.27 |
| Java ConcurrentHashMap 기초 완벽 이해하기 (0) | 2026.05.27 |
| Java Immutable Collection 만들기 완벽 이해하기 (0) | 2026.05.27 |
| Java Wildcard (<? extends T>, <? super T>) 완벽 이해하기 (0) | 2026.05.27 |
댓글