📍개요
김영한님의 실전 자바 - 고급 3편 강의가 드디어 공개되었다.
이번 편은 코딩 테스트나 실무에서도 빈번하게 활용되는 람다(Lambda)와 스트림(Stream)을 집중적으로 다루는 강의다.
이번 기회에 강의를 따라가며 배운 개념과 인사이트를 정리하고, 내 코드에 적용하고, 기록으로 남겨두려고 한다.
📍자바에서 람다가 필요한 이유
코드의 본질을 이해해보자.
public static void helloJava() {
System.out.println("시작");
System.out.println("Hello Java");
System.out.println("끝");
}
public static void helloSpring() {
System.out.println("시작");
System.out.println("Hello Spring");
System.out.println("끝");
}
- 중복되는 값(시작, 끝)을 매개변수화 해보면.
public static void hello(String message) {
System.out.println("시작");
System.out.println(message);
System.out.println("끝");
}
- 이런 방식으로 표현할 수 있다. 변화하는 값을 외부에서 주입해서 로직의 범용성을 높이는 것 → 전략 패턴의 시작점.
행동을 넘겨라
public static void helloDice() {
long start = System.nanoTime();
System.out.println("주사위 = " + new Random().nextInt(6) + 1);
long end = System.nanoTime();
System.out.println("소요 시간: " + (end - start));
}
- 이런 코드가 수십개가 된다면? 시작/종료 타이머가 중복된다.
@FunctionalInterface
public interface Procedure {
void run();
}
public class Dice implements Procedure {
public void run() {
System.out.println("주사위 = " + new Random().nextInt(6) + 1);
}
}
public static void hello(Procedure procedure) {
long start = System.nanoTime();
procedure.run();
long end = System.nanoTime();
System.out.println("소요 시간: " + (end - start));
}
- 인터페이스 정의, 전달하고 싶은 행동을 구현체로 만들기, 행동을 매개변수로 넘겨 실행 방식으로 해결할 수 있다.
람다의 도입: 행동을 객체화하지 않고 전달
hello(() -> {
System.out.println("주사위 = " + new Random().nextInt(6) + 1);
});
- 기존 방법의 한계
- 클래스 생성 → 구현 → 오버라이딩 → 인스턴스 생성 → 전달 = 너무 번거로움.
- 익명 클래스 → 길고 복잡, 가독성 떨어짐.
- 해결 방법
- 코드 블록을 값처럼 넘긴다. 이것이 함수형 프로그래밍의 본질이다.
‼️함수형 인터페이스: 왜 꼭 SAM이어야 하나?
람다는 함수다. 이름 없는 함수.
함수를 저장하려면 형(타입)이 필요 → 자바에선 인터페이스가 그 역할을 함.
그런데 인터페이스에 메서드가 2개 이상이면 어떤 걸 구현한 건지 모호함.
그래서 자바는 단 하나의 추상 메서드만 가지는 함수형 인터페이스에만 람다를 할당 가능하게 제한한다.
📍람다의 시그니처와 생략 규칙
람다는 함수형 인터페이스의 추상 메서드에 바인딩되며,
그 메서드의 형태(= 시그니처)에 맞춰 작성해야 한다.
함수형 인터페이스의 시그니처란?
- 메서드의 이름은 중요하지 않음 ❌
- 매개변수의 개수 / 타입 / 순서 ✔
- 반환 타입 ✔
📌 즉, 람다는 이름 없는 함수이기 때문에 메서드 이름은 무시하고, 나머지 시그니처가 정확히 일치해야 함.
람다의 시그니처
@FunctionalInterface
public interface MyFunction {
int apply(int a, int b); // 시그니처: (int, int) -> int
}
MyFunction f = (int a, int b) -> { return a + b; };
- MyFunction 인터페이스의 apply 메서드에 맞는 람다식은 똑같은 int형 매개변수 2개와 int형 반환값이어야 한다.
기본 생략 규칙
// 1. 풀 버전
MyFunction f1 = (int a, int b) -> { return a + b; };
// 2. 타입 생략
MyFunction f2 = (a, b) -> { return a + b; };
// 3. 중괄호 + return 생략 (단일 표현식)
MyFunction f3 = (a, b) -> a + b;
- 이런식으로 자바에서 람다는 생략할 수 있는 로직은 생략하는 걸 선호한다.
‼️예외적인 생략 규칙
매개변수가 하나라면 ()도 생략 가능 (매개변수가 없을 경우 ()생략은 불가능)
📍람다의 고차함수
고차함수는 함수를 인자로 받는 경우, 함수를 반환하는 경우가 있다. 람다는 모두 가능하다.
public static void calculate(MyFunction function, int x, int y) {
System.out.println("계산 결과: " + function.apply(x, y));
}
public static MyFunction getOperation(String op) {
switch (op) {
case "add": return (a, b) -> a + b;
case "mul": return (a, b) -> a * b;
default: return (a, b) -> 0;
}
}
- 람다는 자기 혼자서는 의미가 없다. 사용되는 문맥 속에서 어떤 하뭇형 인터페이스에 대입되느냐에 따라 의미가 결정된다. 지금은 반환 값이 MyFunction이기 때문에 자동으로 MyFunction에 매핑되는 것.
참고 : 김영한의 실전 자바 - 고급 3편
'java' 카테고리의 다른 글
[Java] 람다의 활용 (0) | 2025.05.05 |
---|---|
[Java] 함수형 인터페이스 (0) | 2025.04.11 |
[Java] 튜닝의 마지막 단계 GC 알아보기 (0) | 2025.02.05 |
[Java] Java 용어들을 알아보자. (JRE, JDK, JVM) (1) | 2025.02.01 |