람다란 무엇인가?
메서드로 전달할 수 있는 익명 함수를 단순화한 것
- 익명 : 보통의 메서드와 달리 이름이 없음
- 함수 : 메서드처럼 특정 클래스에 종속되지 않음
- 전달 : 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있음
- 간결성 : 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요가 없음
함수형 인터페이스란?
오직 하나의 추상 메서드를 지정하는 인터페이스
람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달
@FunctionalInterface 어노테이션으로 정의 가능
❗Comparator 는 FunctionalInterface 가 맞나요?
코드를 살펴보니까 추상 메소드가 하나가 아니라 두 개다!
package java.util; @FunctionalInterface public interface Comparator<T> { // abstract method int compare(T o1, T o2); // abstract method boolean equals(Object obj); // few default and static methods }
결론은 추상 메소드는 compare이다. equals 는 Object 의 equals 를 override 한 것이고 이는 함수형 인터페이스 개수에 카운트되지 않는다.
함수 디스크립터
람다 표현식의 시그니처를 서술하는 메서드 ex) T -> boolean, T -> void
람다 표현식은 변수에 할당하거나 함수형 인터페이스를 인수로 받는 메서드로 전달할 수 있으며 함수형 인터페이스의 추상 메서드와 같은 시그니처를 갖음
public class Main {
public static void main(String[] args) throws IOException {
int[] array = new int[] {3, 2, 1, 2, 3};
List<Integer> lst = new ArrayList<>(List.of(3, 2, 1, 2, 3)) ;
//int[] 에는 lambda를 넣어줄 수 없음
//Arrays.sort(array, (o1, o2) -> o1 - o2);
lst.sort(((o1, o2) -> o1 - o2));
System.out.println(lst);
}
}
int compare(T o1, T o2);
lst.sort() 에 comparator 로 넣어준 (o1, o2) -> o1 - o2 는 Integer 2개를 인수로 받고 int를 반환한다. 함수형 인터페이스 Comparator의 추상 메서드 또한 동일하다. 즉 람다식은 함수형 인터페이스의 추상메서드와 같은 시그니처를 갖는다.
❗Arrays.sort(int[], Comparator<T>) 에 Comparator를 넣어줄 수 없는 이유
제네릭에 대한 이해가 필요하다. 자바는 제네릭을 처리할 때 런타임에 Object 타입으로 다 변환해버리고 변수에 할당하게 되면 해당 클래스 타입으로 다운 캐스팅해준다. 그래서 Object 타입으로 변환이 가능해야하기 때문에 원시 타입(int) 은 제네릭으로 넣어줄 수 없다.
꼭 Comparator를 쓰고 싶다면 원시타입을 boxing 해서 객체 타입으로 만들어주면 된다. (int -> Integer)
형식 검사
람다로 함수형 인터페이스의 인스턴스를 만들 수 있다. 람다가 사용되는 컨텍스트를 이용해서 람다의 형식을 추론할 수 있다. 어떤 콘텍스트 (ex. 람다가 전달될 메서드 파라미터나 람다가 할당되는 변수 등) 에서 기대되는 람다 표현식의 형식을 대상 형식이라고 부른다.
형식 추론
// 형식 있음
List<Apple> greenApples = filter(inventory, (Apple apple) -> apple.getWeight() > 150);
// 형식 생략
List<Apple> greenApples = filter(inventory, (apple) -> apple.getWeight() > 150);
자바 컴파일러는 람다 표현식이 사용된 콘텍스트를 이용해서 람다 표현식과 과련된 함수형 인터페이스를 추론한다. 즉, 대상형식을 이용해서 함수 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그니처도 추론할 수 있다.
filter(inventory, (apple) -> apple.getWeight() > 150); 형식 추론하기
1. filter의 선언부 확인 : filter(List<Apple> inventory, Predicate<Apple> p)
2. 대상 형식 확인 : Predicate<Apple> p
3. 대상형식 인터페이스의 추상 메서드 확인 : Apple -> boolean
4. 추론 : 전하고자 한게 Apple -> boolean 이니까 주어진 람다식도 Apple -> boolean 일 것!
지역 변수의 제약 - final 로 선언되어 있거나 final 로 선언된 변수와 똑같이 사용되어야 한다
public class Main {
public static void main(String[] args) {
int portNumber = 123;
Runnable r = () -> System.out.println(portNumber);
}
}
람다 캡처링
람다 표현식 안에서 자유 변수 (파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수) 를 활용할 수 있다. 예시에서는 자유 변수 portNumber 는 파라미터로 넘겨진 변수가 아니라 외부에서 정의된 변수이다. 이것을 람다에서 받아서 쓰는 것이 람다 캡처링이다.
파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수를 자유 변수라고 부르는 이유?
외부에서 정의된 변수는 람다식이 종료되어도 그대로 있다. 그래서 매서드의 종료 여부와 상관없이 자유로워진 변수라고 불리는 것
public class Main {
public static void main(String[] args) {
int portNumber = 123;
Runnable r = () -> System.out.println(portNumber); // 컴파일 에러
portNumber = 456;
}
}
즉 불변하지 않음을 보장해야한다. 왜 지역변수에만 이런 제약이 있을까?
지역 변수는 인스턴스 변수와 달리 스택에 저장된다. 스택의 특성상 지역 변수는 메모리 해제될 수 있는 변수다. 그렇기 때문에 람다에서 실행될 때는 값을 복사해와서 실행한다. 원본 값과 복사본 값이 불일치하는 일은 없어야하기 때문에 컴파일러가 지역 변수의 값이 바뀌는 것을 막는다.
그렇다면 인스턴스 변수라면 어떨까?
public class Main {
public static void main(String[] args) {
Person person = new Person(12, "John");
Runnable r = () -> System.out.println(person);
person.age = 40; // 컴파일러 에러 발생 x
person = new Person(100, "Jenny"); // 컴파일러 에러 발생 o
}
}
stack 지역 변수 person = 0x3ff (주소값)
heap 인스턴스 변수 person(age=12, name="John")
(더 자세히 가자면 인스턴스 변수의 객체 property (name) 은 주소값을 갖고 실제 값은 constant pool에 있다)
person.age = 40
이걸 실행할 때는 지역 변수 person의 (주소) 값이 안 바껴서 okay
person = new Person(100, "Jenny")
이걸 실행할 때는 지역 변수 person의 (주소) 값이 새로 배정되니 error
람다 클로저?
람다식에서 자유 변수가 참조되는 것을 클로저라고 한다. 람다 표현식에서는 참조는 가능하나 변경해서는 안된다!
메서드 참조
- 정적 메서드 참조 : Integer::parseInt
- 인스턴스 메서드 참조 : (List<Integer> list, element) -> list.contains(element) 를 List::contains
- 기존 객체의 인스턴스 메서드 참조 : (String string) -> this.startsWithNumber(string) 를 this::startsWith
참고
https://stackoverflow.com/questions/2721546/why-dont-java-generics-support-primitive-types
'📗Java' 카테고리의 다른 글
[모던자바인액션] Optional (0) | 2023.10.23 |
---|---|
[Java] Reflection API (5) | 2023.09.24 |
[모던자바인액션] 동작 파라미터화 코드 전달하기 (0) | 2023.09.11 |
[Java] Garbage Collection (0) | 2023.09.07 |
[Java] 자바 가상 머신 (JVM) 과 GC (1) | 2023.09.06 |