람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화 한 것.
파라미터 + 화살표 + 바디
로 이루어진다(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// 람다 파라미터
// 화살표
// 람다 바디
(parameters) -> expression
또는 블록스타일
(parameters) -> { statements; }
람다 표현식은 함수형 인터페이스 문맥에서 사용할 수 있다.
함수형 인터페이스는 오직 하나의 추상메서드만 지정하는 인터페이스이다.
전체 표현식을 함수형 인터페이스를
구현한 클래스의 인스턴스
라고 취급
할 수 있다.interface Runnable {
void run();
}
public class Main {
public static void process(Runnable r){
r.run();
}
public static void main(String[] args)
{
Runnable r1 = () -> System.out.println("Hello, r1");
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("Hello, r2");
}
};
r1.run(); // hello, r1
r2.run(); // hello, r2
process(() -> System.out.println("Hello, r3")); // hello, r3
}
}
함수 디스크립터
라고 부른다.(int, int) → int
이다.함수형 인터페이스
를 인수로 받는 메서드에만 람다 표현식을 사용할 수 있다.순환 패턴(recurrent pattern)
은 자원을 열고, 처리한 다음에, 자원을 닫는 순서로 이루어 진다.실행 어라운드 패턴
이라고 한다.public String processFile() throws IOException {
try ( BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return br.readLine(); // 실제 작업 코드
}
}
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
public String processFile(BufferedReaderProcessor p) throws IOException {
// ...
}
public String processFile(BufferedReaderProcessor p) throws IOException {
try(BufferedREader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
이제 람다를 이용해서 다양한 동작을 processFile 메서드로 전달할 수 있다.
String oneLine = processFile((BufferedReaer br) -> br.readLine());
String twoLine = processFile((BufferedReaer br) -> br.readLine() + br.readLine());
자바8 라이브러리 설계자들은 java.util.function 패키지로 여러 가지 새로운 함수형 인터페이스를 제공
Predicate<T>
Supplier<T>
Consumer<T>
Function<T, R>
자바의 모든 형식은 참조형 혹은 기본형
하지만 제네릭은 내부 구현상 어쩔 수 없이 참조형만 사용 가능하다.
그래서 박싱(기본형 -> 참조형)과 언박싱(참조형->기본형) 제공한다.
박싱한 값은 기본형을 감싸는 래퍼이며 힙에 저장된다.
따라서 박싱한 값은 메모리를 더 소비하며 기본형을 가져올 때도 메모리를 탐색하는 과정이 필요하다.
람다 표현식 자체에는 람다가 어떤 함수형 인터페이스를 구현하는지의 정보가 포함되어 있지 않다.
따라서 람다 표현식을 더 제대로 이해하려면 람다의 실제 형식을 파악해야 한다.
콘텍스트(context)
를 이용해서 람다의 형식(type)
을 추론할 수 있다.List<Apple> heavierThan150g = filter(apples, (Apple apple) -> apple.getWeight() > 150**);
1. 람다가 사용된 콘텍스트는 무엇이지? filter의 정의 확인
-> filter(List<Apple> apples, Predicate<Apple> p)
2. 대상 형식은 Predicate<Apple>이다.
3. Predicate<Apple> 인터페이스의 추상메서드는 무엇이지?
-> boolean test(Apple apple)
4. test 메소드의 Apple -> boolean 함수 디스크립터 묘사
5. 찾은 함수의 디스크립터가 전달된 람다 표현식과 일치하는지 확인
6. 형식 검사 성공적으로 완료
// 형식 추론 하지 않음
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// 형식 추론 함
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
자유 변수
(파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용할 수 있다.람다 캡처링
이라고 한다. int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
인스턴스 변수
와 정적 변수
를 자유롭게 캡처(자신의 바디에서 참조할 수 있도록) 할 수 있다.지역 변수
는 명시적으로 final
로 선언되어 있어야 하거나, 실질적으로 final로 선언된 변수와 똑같이 사용(effectively final)
되어야 한다.한번만 할당할 수 있는 지역 변수
를 캡처 가능 int portNumber = 1337;
// error
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337;
class CapturingTest {
private int a = 1;
public void test() {
final int b = 2;
int c = 3;
int d = 4;
final Runnable r = () -> {
// 인스턴스 변수 a는 final로 선언돼있을 필요도, final처럼 재할당하면 안된다는 제약조건도 적용되지 않는다.
a = 123;
System.out.println(a);
};
r.run();
// 지역변수 b는 final로 선언돼있기 때문에 OK
final Runnable r2 = () -> System.out.println(b);
r2.run();
// 지역변수 c는 변수에 값을 재할당하지 않았으므로 OK
final Runnable r3 = () -> System.out.println(c + " " + b);
r3.run();
// 지역변수 d는 final이 아니고 effectively final도 아니다.
d = 12;
final Runnable r4 = () -> System.out.println(d);
}
}
final
또는 effectively final
상태여야 한다.즉 가변 지역 변수를 새로운 스레드에서 캡쳐할 수 있다면 안전하지 않은 동작이 수행될 가능성이 생기기 때문!!!
메서드 참조는 특정 람다 표현식을 축약한 것이라고 생각하면 좋다.
// (Apple apple) -> apple.getWeight()
Apple::getWeight
// () -> Thread.currentThread().dumpStack()
Thread.currentThread()::dumpStack()
// (str, i) -> str.substring(i)
String::substring
// (String s) -> System.out.println(s)
System.out::println
// (String s) -> this.isValidName(s)
this::isValidName
메서드 참조는 세 가지 유형으로 구분할 수 있다.
Integer::parseInt
String::length
MyClass obj = new MyClass();
(x) -> obj.equals(x);
obj::equals
- 람다 : (x) → obj.method(x)
- 메서드 참조 : obj::method
생성자를 호출하는 람다식도 메서드 참조로 변환할 수 있다.
Supplier<MyClass> s = () -> new MyClass();
Supplier<MyClass> s = MyClass::new;
MyClass obj1 = s.get();
Function<Integer, MyClass> f = (i) -> new MyClass(i);
Function<Integer, MyClass> f2 = MyClass::new;
Function<Integer, int[]> ff = x -> new int[x];
Function<Integer, int[]> ff2 = int[]::new;