Search
Duplicate

람다 캡처링(Capturing Lambda)

개요

람다 표현식(Lambda Expression)에서는 익명 함수와 같이 자유 변수(free variable) 를 활용할 수 있다. 자유 변수란 파라미터로 넘겨진 변수가 아니라 외부에서 정의된 변수인데, 아래의 코드에서 delimiter 변수를 자유 변수라 할 수 있다. 이와 같은 동작을 람다 캡처링(capturing lambda)라 부른다.
public String composeNames(List<Book> books) { String seperator = ","; return books.stream().map(book-> book.getAuthor()).collect(Collectors.joining(seperator)); }
Java
복사
상당히 유용해보이지만, 이러한 자유 변수에 대한 람다 캡처링도 제약이 있다.
람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처해서 자신의 바디부분에서 참조할 수 있는데, 이 경우 지역 변수는 effectivly final variable 이어야 한다. 즉 명시적으로 final 키워드가 붙거나 final처럼 변경없이 사용해야 한다는 것이다. 그렇기에 다음과 같은 코드는 컴파일할 수 없는 코드이다.
public String composeNames(List<Book> books) { String seperator = ","; return books.stream().map(book-> book.getAuthor()).collect(Collectors.joining(seperator)); seperator = ":";//final 키워드가 없더라도 final처럼 사용하지 않으면 문제가 된다. }
Java
복사

어째서 이런 제약이 있는 것일까?

어째서 지역 변수에 이런 제약이 있는 것일까?
우선, 내부적으로 인스턴스 변수와 지역 변수는 저장되는 위치부터가 다르다.
인스턴스 변수 → Heap 에 저장
지역 변수 - Stack 에 저장
그렇기에 람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다.
그렇기에 자바 구현에선 원래 변수에 접근을 허용하는 것이 아닌 자유 지역 변수의 복사본을 제공하기에 복사본의 값이 바뀌지 않아야 하고 그렇게 지역 변수는 한 번만 값을 할당해야 한다는 제약이 생기게 된 것이다.

참고: 클로저(closure)

람다는 클로저의 정의에 부합할까? 원칙적으로 클로저란 함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스를 가리킨다. 예를 들어 클로저를 다른 함수의 인수로 전달할 수 있다.
클로저는 클로저 외부에 정의된 변수의 값에 접근하고, 값을 바꿀 수 있는데, 자바 8의 람다와 익명클래스가 클로저와 비슷한 동작을 수행한다.
하지만, 람다와 익명클래스는 람다가 정의된 메서드의 지역 변수의 값은 바꿀 수 없다. 이 지역 변수는 final(혹은 Effectively final)변수여야 한다. 지역 변수는 스택에 존재하기에 자신을 정의한 스레드와 생존을 같이 해야 하기에 final이어야 한다. 가변 지역 변수를 새로운 스레드에서 캡쳐할 수 있다면 안전하지 않은 동작을 수행할 가능성이 생긴다.
물론, 인스턴스 변수는 스레드가 공유하는 힙에 존재하기에 특별한 제약이 없다.