람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이다.
->) : 파라미터와 바디를 구분() -> {}
() -> "Real"
() -> { return "Mario"; }
return은 단일 표현식일 경우 생략 가능.{}을 사용할 경우 반드시 return을 명시해야 한다.람다는 함수형 인터페이스라는 문맥에서만 사용할 수 있다.
@FunctionalInterface 어노테이션을 붙이면 함수형 인터페이스임을 명시할 수 있으며, 규칙 위반 시 컴파일 오류가 발생한다.자원 처리(예: 파일 입출력, DB 연결)는
1단계 – 동작 파라미터화 기억
2단계 – 함수형 인터페이스 활용
3단계 – 동작 실행
4단계 – 람다 전달
자바 8은 java.util.function 패키지에 다양한 함수형 인터페이스를 제공한다.
T를 받아 불리언을 반환.@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public <T> List<T> filter(List<T> list, Predicate<T> p) { ... }
T를 받아 소비하고 반환 값 없음.@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
public <T> void forEach(List<T> list, Consumer<T> c) { ... }
T를 받아 R을 반환.@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
public <T,R> List<R> map(List<T> list, Function<T,R> f) { ... }
IntPredicate, DoubleFunction 등 기본형 특화 인터페이스 제공.람다는 바깥 스코프에 정의된 변수를 자유 변수(free variable)로 참조할 수 있다.
이 과정을 람다 캡처링이라고 한다.
하지만, 자바에서는 지역 변수 캡처에 제약이 있다.
지역 변수는 final 또는 사실상 final(값이 변하지 않는)이어야 한다.
이는 메모리 안전성 때문이다.
public class LambdaCaptureExample {
public static void main(String[] args) {
String greeting = "Hello"; // 사실상 final (값을 변경하지 않음)
Runnable r = () -> System.out.println(greeting + ", Lambda!");
r.run(); // 출력: Hello, Lambda!
}
}
greeting은 한 번만 초기화되고 이후 변경되지 않으므로 사실상 final이다.public class LambdaCaptureExample {
public static void main(String[] args) {
String message = "Hi";
Runnable r = () -> System.out.println(message);
// message = "Hello"; // 컴파일 오류: 변수는 final 또는 사실상 final이어야 함
r.run();
}
}
message를 람다에서 사용한 후 값을 바꾸려고 하면 컴파일 오류가 발생한다.public class LambdaCaptureExample {
private String instanceVar = "Instance";
private static String staticVar = "Static";
public void test() {
Runnable r = () -> {
System.out.println(instanceVar); // 인스턴스 변수 캡처 가능
System.out.println(staticVar); // 정적 변수도 캡처 가능
};
r.run();
}
public static void main(String[] args) {
new LambdaCaptureExample().test();
}
}
instanceVar)와 정적 변수(staticVar)는 힙/메서드 영역에 저장되므로 자유롭게 변경 가능하다.클로저(Closure)란 함수가 선언될 당시의 환경(변수 값 포함)을 함께 저장하는 개념이다.
람다는 클로저의 성격을 가지며, 실행 시점에도 선언 당시의 변수를 사용할 수 있다.
import java.util.function.Function;
public class ClosureExample {
public static void main(String[] args) {
int factor = 2;
Function<Integer, Integer> multiplier = (x) -> x * factor;
System.out.println(multiplier.apply(5)); // 출력: 10
}
}
factor는 람다 외부에서 정의된 지역 변수지만, 람다 안에서 참조 가능하다.multiplier는 factor를 캡처하여 클로저 역할을 한다.import java.util.function.Supplier;
public class ClosureExample {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");
Supplier<String> supplier = () -> sb.toString();
sb.append(" World"); // 변경 가능 (참조형 객체)
System.out.println(supplier.get()); // 출력: Hello World
}
}
StringBuilder)는 final로 선언된 것이 아니라 객체 내부 상태를 바꾼 것이므로 허용된다.final 혹은 사실상 final이어야 한다.