자바의 람다식에 대해 학습하세요.
//표현식 스타일
(parameters) -> expression
//블록 스타일
(parameters) -> {statements;}
메서드를 람다식으로 바꾸는 과정
출저: 자바의 정석 기본편
{}
사이에 ->
를 추가int max(int a, int b) {
return a > b ? a : b;
}
(int a, int b) -> {
return a > b ? a : b;
}
return
문 대신 '식(expresstion)'으로 대신할 수 있다. 식의 연산결과가 자동으로 반환값이 된다. 이때는 '문장(statements)'이 아닌 '식'이므로 끝에 ;
을 붙이지 않는다(int a, int b) -> a > b ? a : b
(a, b) -> a > b ? a : b
()
생략 가능{}
생략 가능return
문 포함할 경우 괄호{}
생략 불가Q. 어디에서 람다를 사용할 수 있는 건가?
A. 함수형 인터페이스
java.lang.Object
의 메서드를 오버라이딩하는 추상 메서드도 추상 메서드로 카운트하지 않는다//java.util.Comparator
public interface Comparator<T> {
int compare(T o1, T o2);
}
//java.lang.Runnable
public interface Runnable {
void run();
}
@FunctionalInterface
로 인터페이스를 선언했지만 함수형 인터페이스의 조건을 만족하지 않을 경우 컴파일 에러 발생@FunctionalInterface
를 사용하면 함수형 인터페이스를 명시적으로 표현할 수 있고 코드 작성시 컴파일러의 도움을 받을 수 있으므로 꼭 사용이처럼 람다의 시대가 열리면서 익명 클래스는 설 자리가 크게 좁아진 게 사실이다. 하지만 람다로 대체할 수 없는 곳이 있다. 람다는 함수형 인터페이스에서만 쓰인다. 예컨대 추상 클래스의 인스턴스를 만들 때 람다를 쓸 수 없으니, 익명 클래스를 써야 한다. 비슷하게 추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때도 익명 클래스를 쓸 수 있다. 마지막으로, 람다는 자신을 참조할 수 없다. 람다에서의 this 키워드는 바깥 인스턴스를 가리킨다. 반면 익명 클래스에서의 this는 익명 클래스의 인스턴스 자신을 가리킨다. 그래서 함수 객체가 자신을 참조해야 한다면 반드시 익명 클래스를 써야 한다.
출저: 이펙티브 자바 3/E
람다식은 익명 함수처럼 자유 변수(free variable)를 활용할 수 있다. 이 같은 동작을 람다 캡처링(capturing lambda)라고 한다
접근 가능 대상
final
로 선언되거나 final
이 붙은 것처럼 이후 변경이 없어야 한다.int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337; //컴파일 에러
출저: 모던 자바 인 액션
왜 지역 변수에 이런 제약이 필요한지 이애할 수 없는 독자도 있을 것이다. 우선 내부적으로 인스턴스 변수와 지역 변수는 태생부터 다르다. 인스턴스 변수는 힙에 저장되는 반면 지역 변수는 스택에 위치한다. 람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다. 따라서 자바 구현에서는 원래 변수에 접근을 혀용하는 것이 아니라 자유 지역 변수의 복사본을 제공한다. 따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴 것이다.
출저: 모던 자바 인 액션
종류 | 람다 | 메서드 참조 |
---|---|---|
정적 메서드 참조 | (args) -> ClassName.staticMethod(args) | ClassName::staticMethod |
다양한 형식의 인스턴스 메서드 참조 | (arg0, rest) -> arg0.instanceMethod(rest) | ClassName::instanceMethod |
기존 객체의 인스턴스 메서드 참조 | (args) -> expr.instanceMethod(args) | expr::instanceMethod |
new
키워드를 통해 기존 생성자의 참조를 만들 수 있다Supplier<MyClass> s1 = () -> new MyClass(); //람다식
Supplier<MyClass> s2 = MyClass::new; // 생성자 참조
MyClass m1 = s1.get();
MyClass m2 = s2.get();
Supplier
를 통해 파라미터가 없는 생성자를 생성자 참조를 통해 람다 축약 + 사용 코드Function
, BiFunction
를 통해 비슷하게 구현 가능Function<Integer num> c2 = MyClass::new;
Function<Integer, T[] = T[]::new
같은 식으로 하면 된다public class Sorting {
public static void main(String... args) {
List<Apple> inventory = new ArrayList<>();
inventory.addAll(Arrays.asList(
new Apple(80, Color.GREEN),
new Apple(155, Color.GREEN),
new Apple(120, Color.RED)
));
// 1단계: 코드 전달
// [Apple{color=GREEN, weight=80}, Apple{color=RED, weight=120}, Apple{color=GREEN, weight=155}]
inventory.sort(new AppleComparator());
System.out.println(inventory);
// reshuffling things a little
inventory.set(1, new Apple(30, Color.GREEN));
// 2단계: 익명 클래스 사용
// [Apple{color=GREEN, weight=30}, Apple{color=GREEN, weight=80}, Apple{color=GREEN, weight=155}]
inventory.sort(new Comparator<Apple>() {
@Override
public int compare(Apple a1, Apple a2) {
return a1.getWeight() - a2.getWeight();
}
});
System.out.println(inventory);
// reshuffling things a little
inventory.set(1, new Apple(20, Color.RED));
// 3단계: 람다 표현식 사용
// [Apple{color=RED, weight=20}, Apple{color=GREEN, weight=30}, Apple{color=GREEN, weight=155}]
inventory.sort((a1, a2) -> a1.getWeight() - a2.getWeight());
System.out.println(inventory);
// reshuffling things a little
inventory.set(1, new Apple(10, Color.RED));
// 4단계: 메서드 참조 사용
// [Apple{color=RED, weight=10}, Apple{color=RED, weight=20}, Apple{color=GREEN, weight=155}]
inventory.sort(comparing(Apple::getWeight));
System.out.println(inventory);
}
static class AppleComparator implements Comparator<Apple> {
@Override
public int compare(Apple a1, Apple a2) {
return a1.getWeight() - a2.getWeight();
}
}
}
출저: 모던 자바 인 액션
java.util.function
에 이러한 인터페이스가 정의돼 있다함수형 인터페이스 | 메서드 | 함수 디스크립터 |
---|---|---|
Supplier<T> | T get() | ( ) -> T |
Consumer<T> | void accept(T t) | T -> void |
Function<T, R> | R apply(T t) | T -> R |
Predicate<T> | boolean test(T t) | T -> boolean |
BiConsumer<T, U> | void accept(T t, U u) | (T, U) -> void |
BiFunction<T, U, R> | R apply(T t, U u) | (T, U) -> R |
BiPredicate<T, U> | boolean test(T t, U u) | (T, U) -> boolean |
UnaryOperator<T> | T apply(T t) | T -> T |
BinaryOperator<T> | T apply(T t, T t) | (T, T) -> T |
자바에서 제네릭 파라미터에는 참조형만 가능하다. 따라서 제네릭을 통해 정의된 함수형 인터페이스에서는 기본형에 따른 래퍼 클래스를 사용해야 하며, 이때 박싱과 언박싱에 따른 비용이 발생한다. 자바8에서는 기본형을 입출력으로 사용할때 오토박싱을 피할 수 있도록 각 기본형에 특화된 함수형 인터페이스를 제공한다.
negate
, and
, or
andThen
, compose