본문 바로가기

JAVA

[JAVA 8] 형식 검사, 형식 추론, 제약

728x90
반응형


람다로 함수형 인터페이스의 인스턴스를 만들 수 있으며 람다 표현식에는 람다가 어떤 함수형 인터페이스를 구현하는지 정보를 가지고 있다. 따라서 람다 표현식을 더 제대로 이해하려면 람다의 실제 형식을 파악해야 한다.

1. 형식 검사
- 람다에 사용되는 내용을 바탕으로 람다의 형식을 추론할 수 있다.
- 대상 형식 ? 람다가 전달될 파라미터나 람다가 할당되는 변수 등에서 기대되는 람다 표현식의 형식을 대상 형식이라 부른다. (예: Predicate<T>)
- filter() 함수의 정의를 보면 파라미터로 Predicate<T>로 정의 되어 있다.
- 즉, Predicate<T>라는 대상 형식에 만족하는 람다 함수를 기대하는 것이다.

1-1.같은 람다, 다른 함수형 인터페이스
- 대상 형식이라는 특징 때문에 같은 람다 표현식이더라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다.
- Callable과 PrivilegedAction 인터페이스는 인수를 받지 않고 제네릭 형식 T를 반환하는 함수를 정의 한다.
- Callable<Integer> c = () -> 42;
- PrivilegedAction<Integer> p = () -> 42;
- 위에서 보는 바와 같이 하나의 람다 표현식을 다양한 함수형 인터페이스에 사용할 수 있다.

1-2. 다이아몬드 연산자
- 자바 7에서 지원된 기능으로 다이아몬드 연산자(<>)로 콘텍스트에 따른 제네렉 형식을 추론할 수 있다.
- 즉, 주어진 클래스 인스턴스 표현식을 두 개 이상의 다양한 콘텍스트에 사용할 수 있다.
- List<String> strList = new ArrayList<>();
- List<Integer> intList = new ArrayList<>();

1-3. void 호환 규칙
- 람다의 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환된다.
- 예를 들어 boolean값을 return하는 함수가 있다면 Consumer 콘텍스트가 기대하는 void 대신 이 함수를 사용할 수 있다.

2. 형식 추론

코드를 좀 더 단순화할 수 있는 방법을 생각해 보자.
코드의 단순화는 가독성이 올라감으로 개발자에게 큰 도움이 된다.
자바 컴파일러는 람다 표현식이 사용된 대상 형식을 이용하여 람다 표현식과 관련된 함수형 인터페이스를 추론할 수 있다. 결국 컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서 이를 생략할 수 있다. 이와 같은 특징은 여러 파라미터를 포함하는 람다 표현식일 때 코드 가독성 향상이 일어날 경우 더욱 유용하게 사용된다.

Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

무조건 형식을 배제하는 것이 좋은것은 아니다. 때에 따라 형식을 작성해주는 것이 가독성에 좋을 수도 있다. 이것은 상황에 따라 개발자의 센스로 남겨두겠다.

2-1. 지역 변수 사용
람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수(파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용 할 수 있다. 이와 같은 동작이 람다 캡처링이다.
하지만 자유 변수에도 제약이 있으니 꼭 알고 사용해 봅시다.
람다는 인스턴스 변수와 정적 변수를 자유롭게 사용할 수 있다. 하지만 그러기 위해서는 지역 변수는 명시적으로 final로 선언되어 있거나 실질적으로 final로 선언된 변수와 같이 사용되어야 한다. 즉, 람다 표현식은 한 번만 할당할 수 있는 지역 변수만 사용할 수 있다.

아래와 같은 코드는 사용할 수 없는 것이다.

int count = 1;
Runnable r = () -> System.out.println(count);
count = 2;

2-2. 지역 변수의 제약
지금부터 위에서 이야기한 지역변수의 제약에 대해 알아보자.
람다에서 지역 변수를 사용하는 것은 직접 접근하는 것이 아니라 지역변수의 복사본을 사용한다. 그 이유는 아래와 같다.
인스턴스 변수는 힙에 저장되며 지역 변수는 스택에 위치한다. 이때 람다에서 지역변수에 바로 접근하게 될 경우를 생각해 보면 람다가 스레드에서 실행될 때 지역 변수의 수명이 다해 할당이 해제되는 경우가 발생하게 된다. 이와 같은 이슈를 막기 위해 복사본을 사용하고 복사본을 사용하기 위해서는 값이 바뀌지 않아야 한다는 제약 사항이 발생한 것이다.

2-3. 클로저 알아보기
클로저란 함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스를 가르킨다.
클로저는 다른 함수의 인수로 전달 할 수 있다.
클로저는 클로저 외부에 정의된 변수의 값을 접근하고 값을 바꿀 수 있다.
자바 8의 람다와 익명 클래스는 클로저와 비슷한 동작을 수행한다. 람다와 익명 클래스 모두 함수의 인자로 전달 될 수 있다. 자신의 외부 영역에 변수에 접근 할 수 있다.
단, 람다와 익명클래스는 람다가 정의된 메서드의 지역 변수의 값을 바꿀 수 없다.
람다가 정의된 메서드의 지역 변수값은 final 변수여야 한다.
지역 변수값은 스택에 존재하므로 자신을 정의한 스레드와 생존을 같이 해야 하며 따라서 지역 변수는 final로 되어야 하는 것이다. 가변 지역 변수를 새로운 스레드에서 캡처할 수 있다면 안전하게 동작하지 않을 것이다.








[출처 : JAVA8 in Action]

728x90
반응형