람다식이란 메서드로 전달할 수 있는 익명 함수를 단순화한 것으로 함수형 인터페이스를 구현한 익명 클래스의 인스턴스라 할 수 있다.
먼저 인터페이스와 추상 메소드를 만들어 예제를 확인해보자
@FunctionalInterface
interface ExampleInterface {
int sum (int x, int y);
}
public class Lambda {
public static void main(String[] args) {
ExampleInterface exampleInterface = new ExampleInterface() {
@Override
public int sum(int x, int y) {
return x + y;
}
};
}
}
위 코드를 람다식을 이용하여 더 간결하게 만들 수 있다.
@FunctionalInterface
interface ExampleInterface {
int sum (int x, int y);
}
public class Lambda {
public static void main(String[] args) {
ExampleInterface exampleInterface = (x, y) -> {
return x+y;
};
}
}
확실히 더 짧아진 것을 확인할 수 있을 것이다.
물론 이해까지 더해진다면 짧은 코드로 가독성있게 확인할 수 있을 것이다.
이 경우에는 위 코드보다 더욱 간결하게 만들 수 있다.
@FunctionalInterface
interface ExampleInterface {
int sum (int x, int y);
}
public class Lambda {
public static void main(String[] args) {
ExampleInterface exampleInterface = (x, y) -> x+y;
}
}
위 코드를 보면 그 길었던 코드가 한 줄로 짧아진 것을 볼 수 있다. 솔직히 놀랍다.
하지만, 람다식을 너무 남발하면 코드가 오히려 지저분해지고 해석하기 어려울 수 있으며 디버깅 또한 힘들어진다. 그러므로 람다를 적절하게 사용하는 것이 중요하다.
람다의 모든 매개변수 타입은 생략 가능하다.
-> 컴파일러가 타입을 추론할 수 없다고 할 때는 제외한다.
매개변수가 하나일 때 괄호 () 생략 가능하다.
-> 매개변수의 타입까지 작성해야할 때는 생략 불가하다.
람다 바디 문장이 하나일 때 대괄호 {} 생략 가능하다.
람다의 직렬화는 피하자
-> 람다도 익명 클래스처럼 직렬화 형태가 구현하는 가상머신에 따라 다를 수 있다.
람다식을 다루기 위한 인터페이스로 함수형 인터페이스는 조건이 있다.
@FunctionalInterface : 함수형 인터페이스임을 표시하는 어노테이션
람다는 함수형 인터페이스에서만 쓰인다.
추상 클래스의 인스턴스를 만들 때 람다를 쓸 수 없으니, 익명 클래스를 써야한다.
추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때도 익명 클래스를 쓸 수 있다.
람다는 자신을 참조할 수 없다. -> 람다에서의 this 키워드는 바깥 인스턴스를 가리킨다. 반면 익명 클래스에서의 this는 익명 클래스의 인스턴스 자신을 가리킨다. 그래서 함수 객체가 자신을 참조해야 한다면 반드시 익명 클래스를 써야 한다.
람다식은 익명 함수처럼 자유 변수( free variable )를 활용할 수 있다.
이 같은 동작을 람다 캡처링이라고 한다.
접근 가능 대상 :
인스턴스 변수는 힙에 저장되는 반면 지역 변수는 스택에 위치한다. 람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다. 따라서 자바 구현에서는 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공한다. 따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴 것이다.
출처: 모던 자바 인 액션
public class Lambda {
int a = 100;
public void print1() {
int b = 200;
final ExampleInterface exampleInterface = () -> {
return System.out.println(a);
};
}
}

public class Lambda {
public static void main(String[] args) {
ExampleInterface exampleInterface = (x, y) -> x+y;
}
}
위 코드를 더어ㅓㅓㅓㅓㅓ 생략해 파라미터까지 제거할 수 있다.
@FunctionalInterface
interface ExampleInterface {
int sum (int x, int y);
}
public class Lambda {
public static void main(String[] args) {
ExampleInterface exampleInterface = Integer::sum;
}
}
@FunctionalInterface
interface ExampleInterface {
Member example(String name, int age);
}
class Member {
private String name;
private int age;
Member() {
}
Member(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Lambda {
public static void main(String[] args) {
ExampleInterface exampleInterface = new ExampleInterface() {
@Override
public Member example(String name, int age) {
return new Member(name, age);
}
};
}
}
위 코드의 lambda 클래스를 람다식으로 바꾸면 아래와 같아진다.
public class Lambda {
public static void main(String[] args) {
ExampleInterface exampleInterface = ((name, age) -> new Member(name, age));
}
}
여기서 생성자 참조를 하게 되면 아래와 같아진다.
public class Lambda {
public static void main(String[] args) {
ExampleInterface exampleInterface = Member::new;
}
}
정말 간결해졌다.. 하지만 이 코드만 보고 이게 무엇을 의미하는지는 알기 아직까지는 힘든 것 같다..