들어가기
지난 포스트에서는 람다(lambda)가 왜 사용되면 편리한지 또 어떻게 사용될 수 있을지에 대해 아주 간단하게 알아보았습니다.
이번 포스트에서는 람다(lambda)를 다시 한 번 정리하고 조금 더 심화된 내용을 알아보려고 합니다.
람다란 무엇인가
람다 표현식은 메서드로 전달할 수 있는 익명 함수를 간단하게 표현한 것입니다. 람다 표현식은 익명 함수와 비슷하게 이름은 없지만, 파라미터, 바디, 반환 형식, 예외 리스트를 가질 수 있습니다.
즉, 보통의 메서드와는 달리 이름이 없으므로 익명이라 표현하고 특정 메서드에 종속되지 않은 독립적인 것이므로 메서드가 아닌 함수라고 부릅니다.
하지만 람다가 기술적으로 자바8 이전의 자바로 할 수 없었던 일을 제공하는 것은 아닙니다. 다만 람다는 동작 파라미터 형식의 코드를 더 쉽게 구현하고 보기 쉬운 코드를 작성하는데 큰 도움이 됩니다.
1 | Comparator<Apple> byWeight = new Comparator<apple>() { |
위와 같은 코드를 람다를 이용하면 훨씬 간단한 코드로 작성할 수 있습니다.
1 | Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); |
즉, compare 메서드의 바디를 직접 전달하는 것처럼 코드를 전달할 수 있으면 이전 코드에 비해 훨씬 가독성 또한 높아졌습니다.
확인한 것처럼 람다 표현식의 기본 문법은 간단합니다.
1 | // 유효 |
(parameters) -> expression 또는 (parameters) -> {statement;} 형식으로 람다 표현식을 작성할 수 있습니다.
어디에, 어떻게 람다를 사용할까
함수형 인터페이스
함수형 인터페이스는 오직 하나의 추상 메서드를 지정하는 인터페이스 입니다.
1 | // java.util.Comparator |
과연 이런 함수형 인터페이스로 무엇을 할 수 있을까요? 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로 전체 표현식을 함수형 인터페이스의 인스턴스로 취급할 수 있습니다.
또, 함수형 인터페이스보다는 덜 깔끔하지만 익명 내부 클래스로도 같은 기능을 구현할 수 있습니다.
다음과 같은 코드는 모두 올바른 코드입니다.
1 | // 람다 사용 |
함수 디스크립터
람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터라고 부릅니다. 예를 들어 Runnable 인터페이스의 유일한 추상 메서드 run은 인수와 반환값이 없으므로 Runnable 인터페이스는 인수와 반환값이 없는 시그니처로 생각할 수 있습니다.
즉, Runnable 인터페이스의 run은 () -> void과 같이 표현될 수 있습니다. 그리고 이에 대응하는 람다의 함수 디스크립터 역시 () -> void로 표현되어야 합니다.
함수형 인터페이스 사용
Predicate
java.util.function.Predicate< T > 인터페이스는 test라는 추상메서드를 정의하며 test는 제네릭 형식의 t의 객체를 인수로 받아 Boolean을 반환합니다. 즉, 따로 Predicate를 만들 필요없이 필요에 따라 바로 Predicate 인터페이스를 사용할 수 있습니다.
1 |
|
Consumer
java.util.function.Consumer< T > 인터페이스는 제네릭 형식 T 객체를 받아서 void를 반환하는 accept라는 추상 메서드를 정의합니다. T 형식의 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 Consumer 인터페이스를 사용할 수 있습니다.
1 |
|
Function
java.util.function.Function< T, R > 인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R 객체를 반환하는 apply라는 추상 메서드를 정의하며 이를 활용하여 입력을 출력으로 매핑하는 형식으로 사용할 수 있습니다.
1 |
|
기본형 특화
자바에는 기본형과 참조형이 존재하지만 제네릭 파라미터에는 참조형만 사용할 수 있습니다. 이에 따라 자바에서는 기본형을 참조형으로 변환할 수 있는 기능을 제공하고 이 기능을 박싱이라고 부릅니다.
또 이와 반대로 참조형을 기본형으로 변환하는 작업을 언박싱이라고 부릅니다. 더 나아가 자바에서는 박싱과 언박싱이 자동으로 이루어지는 오토 박싱이라는 기능도 제공합니다.
1 | List<Integer> list = new ArrayList<>(); |
예시와 같은 코드는 동작하는데는 무리가 없지만 기본형이 참조형으로 변환하는 과정에서 어쩔수 없는 비용이 소모됩니다. 박싱한 값은 기본형을 감싸는 래퍼이며 힙에 저장됩니다. 따라서 박싱한 값은 메모리를 더 소비하며 기본형을 가져올 때도 메모리를 탐색하는 과정이 필요합니다.
자바 8에서는 오토박싱 동작을 피할 수 있도록 특별한 버전의 함수형 인터페이스를 제공합니다. 예를 들어 아래 예제에서 IntPredicate는 1000이라는 값을 박싱하지 않지만, Predicate< Integer >는 1000이라는 값을 Integer로 박싱합니다.
1 | public interface IntPredicate { |
마치며
자바 8에서 중요한 요소인 람다의 정리를 마치겠습니다. 질문은 언제든지 자유롭게 달아주시면 찾아서라도 답 달아보도록 노력할게요~