/////
Search
Duplicate
5️⃣

형식 검사, 형식 추론, 제약

: 람다 표현식을 처음 설명할 때 람다로 함수형 인터페이스의 인스턴스를 만들 수 있다고 언급했다.
: 람다 표현식 자체에는 람다가 어떤 함수형 인터페이스를 구현하는지의 정보가 포함되어있지 않으므로 람다 표현식을 더 제대로 이해하려면 람다의 실제 형식을 파악해야 한다.

1. 형식 검사

: 람다가 사용되는 컨텍스트를 이용해서 람다의 형식을 추론할 수 있다. 어떤 컨텍스트에서 기대되는 람다 표현식의 형식을 대상 형식이라고 부른다.
: 람다 표현식을 사용할 때, 실제 어떤 일이 일어나는지 보여주는 예제를 확인하자.
List<Apple> heavierThan150g = filter(inventory, (Apple apple) -> apple.getWeight() > 150);
Java
복사
: 위 코드의 형식 확인 과정은 다음과 같다.
1.
filter 메서드의 선언을 확인한다.
2.
filter 메서드는 두 번째 파라미터로 Predicate<Apple> 형식을 기대한다.
3.
Predicate<Apple>은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스이다.
4.
test 메서드는 Apple을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
5.
filter 메서드로 전달된 함수는 이와 같은 요구사항을 충족한다.
: 위 예제에서 람다 표현식은 Apple을 인수로 받아 boolean을 반환하므로 올바른 코드다. 람다 표현식이 예외를 던질 수 있다면 추상 메서드도 예외를 던질 수 있어야 한다.

2. 같은 람다, 다른 함수형 인터페이스

: 대상 형식이라는 특징 때문에 같은 람다 표현식이더라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다.
: 예를 들어 이전의 Callable과 PrivilegedAction 인터페이스는 인수를 받지 않고 제네릭 형식 T를 반환하는 함수를 정의한다. 따라서 다음 두 할당문은 모두 유효하다.
Callabe<Integer> c = () -> 42; PrivilegedAction<Integer> p = () -> 42;
Java
복사
: 위 코드의 첫 번째 할당문의 대상 형식은 Callable<Integer>이고 두 번째 할당문의 대상 형식은 PrivilegedAction<Integer>이다.

3. 형식 추론

: 코드를 좀 더 단순화할 수 있는 방법이 있다. 자바 컴파일러는 람다 표현식이 사용된 컨텍스트를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.
: 즉 대상 형식을 이용해서 함수 디스크립터를 확인할 수 있으므로 컴파일러는 람다의 시그니처로 추론할 수 있다.
: 결과적으로 컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서 이를 생략할 수 있다. 자바 컴파일러는 다음처럼 람다 파라미터 형식을 추론한다.
List<Apple> greenApples = filter(inventory, apple -> GREEN.equals(apple.getColor())); // 파라미터 a에는 형식을 명시적으로 지정하지 않았다.
Java
복사
: 여러 파라미터를 포함하는 람다 표현식은 코드 가독성 향상이 더 두드러진다. 예를 들어 다음은 Comparator 객체를 만드는 코드다.
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); // 형식 추론 하지 않음, 난 이게 더 나아보이는데,, 흠.. Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()); // 형식 추론 함
Java
복사

4. 지역 변수 사용

: 지금까지 살펴본 모든 람다 표현식은 인수를 자신의 바디 안에서만 사용한다. 하지만 람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수를 활용할 수 있다.
: 이와 같은 동작을 람다 캡처링이라고 부른다. 다음은 portNumber 변수를 캡처하는 람다 예제다.
int portNumber = 1337; Runnable r = () -> System.out.println(portNumber);
Java
복사
: 하지만 이런 자유변수에도 약간의 제약이 존재한다. 람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처할 수 있다. 하지만 그러려면 지역 변수는 명시적으로 final 선언되어있거나 실질적으로 final로 선언된 변수와 똑같이 사용되어야 한다.
지역 변수의 제약
: 왜 지역 변수에 이런 제약이 필요한지 이해하기 힘들 수 있다. 우선 내부적으로 인스턴스 변수와 지역 변수는 태생부터 다르다.
: 인스턴스 변수는 힙에 저장되는 반면 지역 변수는 스택에 위치한다.
: 람다에서 지역 변수에 바로 접근할 수 있다는 가정 하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다. 따라서 자바 구현에서는 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공한다. 따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴것이다.
: 또한 지역 변수의 제약 때문에 외부 변수를 변화시키는 일반적인 명령형 프로그래밍 패턴에 제동을 걸 수 있다.