익명 클래스로 동작을 구현할 수 있었지만, 기대한만큼 깔끔한 코드가 나오진 못했다.
조금 더 깔끔하고 단순한 코드로 동작을 구현할 수 있는 자바 8의 새로운 기능 람다 표현식을 설명한다.
람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라 말할 수 있다.
람다를 사용함으로써 얻는 가장 큰 이득 중 하나는
익명 클래스 등에 형식상 들어가야만 했던 코드들을 더 이상 사용하지 않아도 된다는 점이다.
// lambda 적용 전
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight()) ;
}
};
// lambda 적용 후
Comparator<Apple> byWeight =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) ;
어디에, 어떻게 람다를 사용할까?
람다는 함수형 인터페이스 문맥에서 사용할 수 있다.
함수형 인터페이스는 하나의 추상메서드를 가진 인터페이스를 말한다. 2장의 Predicate이나 위 Comparator가 그 예이다.
이전에는 인터페이스의 추상메소드 구현을 전달하기 위해 실제 구현 Class를 만들거나
위처럼 익명클래스를 통한 구현을 사용하였는데, 자바 8부턴 이 대신 람다표현식을 적용할 수 있다.
람다표현식으로 전달된 코드 또한 함수형 인터페이스의 인스턴스로 취급된다.
Runnable r1 = () -> System.out.println("Hello World 1");
Runnable r2 = new Runnable() {
public void run() {
System.out.println("Hello World 2");
}
};
r1.run(); // Hello World 1 출력
r2.run(); // Hello World 2 출력
함수형 인터페이스의 추상 메서드 시그니처는 람다 표현식의 시그니처를 가리킨다.
Runnable 인터페이스의 유일한 추상메서드 run은 인수를 받지 않고 반환값 또한 void 이므로
Runnable 인터페이스는 인수와 반환값이 없는 시그니처로 생각할 수 있다.
람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터(function descriptor) 라고 부른다
* @FunctionalInterface 는 함수형 인터페이스를 나타내는 어노테이션으로 , 추상메서드가 한 개 이상일 경우 에러를 발생시킨다.
함수형 인터페이스 사용
자바 API는 이미 Comparable, Runnable, Callable과 같은 다양한 함수 인터페이스를 포함하고 있다.
자바 8에서는 java.util.function 패키지로 여러 새로운 함수형 인터페이스를 추가로 제공한다.
새로운 함수형 인터페이스인 Predicate, Consumer, Function 인터페이스를 살펴보기로 한다.
Predicate
Predicate은 test 라는 추상메서드를 가지며, test는 T의 객체를 인수로 받아 boolean을 반환한다.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
// 예제
public static <T> List<T> filter(List<T> list, Predicate<T> p){
List<T> result = new ArrayList<>();
for (T t : list) {
if(p.test(t)){
result.add(t);
}
}
return result;
}
Predicate<String> nonEmptyString = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyString);
Consumer
Consumer는 accept라는 추상메서드를 가지며, accept는 T 객체를 인수로 받아 void를 반환한다.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
// 예제
public static <T> void forEach(List<T> list, Consumer<T> c){
for (T t : list) {
c.accept(t);
}
}
forEach(
Arrays.asList(1, 2, 3, 4, 5),
(Integer i) -> System.out.println(i)
);
Function
Function은 apply라는 추상메서드를 가지며, apply는 T 객체를 인수로 받아 R 객체를 반환한다.
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
// 예제
public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
List<R> result = new ArrayList<>();
for (T t : list) {
result.add(f.apply(t));
}
return result;
}
// [7, 2, 6]
List<Integer> lengthList = map(
Arrays.asList("lambdas", "in", "action"),
(String s) -> s.length() // String을 받아 Integer를 반환
);
함수형 인터페이스에서 쓰이는 제네릭(T, R..) 의 경우 참조형 타입(Integer, Object, ..)만 사용 가능하다.
따라서, 기본형(int, char..) 에 활용하기 위해서는 기본형-참조형 간 변환을 위한 박싱/언박싱 기능을 사용해야 한다.
하지만, 박싱 기능의 경우 기본형을 감싸는 래퍼이고 힙에 저장되기 때문에 메모리를 더 소비하게 된다.
또한, 기본형을 가져올 때에도 메모리를 탐색하는 과정이 필요하다.
이런 단점을 보완하기 위해, 자바 8에서는 기본형에도 적용 가능한 특별한 버전의 함수형 인터페이스를 제공한다.
Predicate의 int 버전인 IntPredicate이 그 예이다.
Predicate<Integer> oddNumbers = (Integer i) -> i % 2 != 0;
oddNumbers.test(1000); // 박싱 됨
IntPredicate oddNumbersInt = (int i) -> i % 2 != 0;
oddNumbersInt.test(1000); // 박싱 없음
아래는 자바 8에 추가된 함수형 인터페이스와, 각 함수형 인터페이스의 함수 디스크립터
그리고 기본형에 특화된 함수형 인터페이스들의 목록이다.
함수형 인터페이스 | 함수 디스크립터 | 기본형 특화 |
Predicate<T> | T -> boolean | IntPredicate, LongPredicate, DoublePredicate |
Consumer<T> | T -> void | IntConsumer, LongConsumer, DoubleConsumer |
Function<T, R> | T -> R | IntFunction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, DoubleToIntFunction, DoubleToLongFunction, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T> |
Supplier<T> | () -> T | BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier |
UnaryOperator<T> | T -> T | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator<T> | (T, T) -> T | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate<L, R> | (T, U) -> boolean | |
BiConsumer<T, U> | (T, U) -> void | ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T> |
BiFunction<T, U, R> | (T, U) -> R | ToIntBiFunction<T, U>, ToLongBiFuntion<T, U>, ToDoubleBiFunction<T, U> |
'Book > 모던 자바 인 액션' 카테고리의 다른 글
[정리] 모던 자바 인 액션 - 스트림 활용(2) (0) | 2023.01.08 |
---|---|
[정리] 모던 자바 인 액션 - 스트림 활용(1) (2) | 2023.01.07 |
[정리] 모던 자바 인 액션 - 스트림 소개 (0) | 2023.01.06 |
[정리] 모던 자바 인 액션 - 람다 표현식 (Lambda Expression)(2) (0) | 2023.01.05 |
[정리] 모던 자바 인 액션 - 동작 파라미터화 (Behavior Parameterization) (0) | 2023.01.03 |
댓글