depark's note
Blog
Blog
  • Book / Lecture

    • 토비의 스프링 3.1

      • CH6. AOP
    • Spring Security 6 완전 정복

      • 초기화 과정
      • 인증 프로세스
      • 인증 아키텍처
      • 인증 상태 영속성
      • 세션 관리
  • Java

    • JVM, Java Virtual Machine

      • Class Loader
      • Runtime Data Area
      • Garbage Collection Algorithm
      • Garbage Collector
    • Generics
    • Collections

      • Hashing
      • Map
    • Thread Programming

      • Thread, Runnable
      • Fork, Join, RecursiveTask
      • Callable, Future, Executor
      • CompletableFuture
    • Reactive Programming
    • 함수형 인터페이스와 람다
  • Spring

    • AOP
  • Database

    • Transaction
    • Index

함수형 인터페이스와 람다

Functional Interface, 함수형 인터페이스

  • 추상 메소드를 하나만 가지는 인터페이스
@FunctionalInterface
public interface RunSomething {
    // 추상 메소드 딱 하나만 가지고 있는 인터페이스
    int doIt(int n);

    static void printName() {
        System.out.println("Hello World");
    }

    default void printAge() {
        System.out.println("40");
    }
}
  • @FunctionalInterface 어노테이션을 사용하면 추상 메서드를 하나만 가지지 않는 경우 컴파일 오류를 발생시키다.
  • static, default 메소드는 있어도 상관 없다.

사용

RunSomething runSomething = (i) -> {
    System.out.println(i);
    return i;
};
System.out.println(runSomething.doIt(1));
// static method
RunSomething.printName();
// default method
runSomething.printAge();

왜 필요한가

동작 파라미터화

함수형 인터페이스를 메서드의 매개변수로 사용하면 특정 동작을 인수로 전달할 수 있다.

public static List<Apple> filterApples(List<Apple> apples, Predicate<Apple> p) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}

filterApples 메서드는 함수형 인터페이스 p를 받아서 함수형 인터페이스의 test 메서드 (T -> boolean)을 수행한다.

다음은 filterApples 메서드를 사용하는 코드이다.

List<Apple> heavyApples = filterApples(apples, new Predicate<Apple>() {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight() > 300;
    }
});

다음과 같이 함수형 인터페이스를 통해 수행할 동작을 익명 클래스로 구현하여 파라미터로 념겨줄 수 있다. 그런데 메서드 하나를 구현하는데도 코드가 장황하다. 한 눈에 안읽힌다.

List<Apple> redApples = filterApples(apples, (Apple apple) -> RED.equals(apple.getColor()));

List<Apple> heavyApples = filterApples(apples, (Apple apple) -> apple.getWeight() > 300));

다음과 같이 람다식과 결합하면 코드의 간결성, 가독성을 높일 수 있다.

[장점]

  • 변경에 닫혀있고 확장에 유연한 코드를 만들 수 있다. (OCP)
    • filterApples() 메서드의 내부 구현을 바꾸지 않고 필터 조건을 바꿀 수 있어 확장에 유연하다.
  • 하나의 메서드가 다른 동작을 수행할 수 있도록 재활용할 수 있다.
    • 하나의 메서드로 색깔을 필터링, 무게로 필터링하는 동작을 다 수행할 수 있다.

Lambda Expression, 람다식

익명 함수를 표현하는 간결한 문법이다. 람다식은 함수를 일급 객체(first class object)로 취급하여 다른 함수의 인자로 전달하거나 함수에서 반환값으로 사용할 수 있다.

특징

  • 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부른다.
  • 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다.
  • 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요 없다.

java.util.function

  • 자바에서 기본으로 제공하는 함수형 인터페이스 패키지
함수형 인터페이스메서드 형태API 활용
java.lang.Runnablevoid run()스레드의 매개 변수로 이용
Consumer<T>void accept(T t)객체 T를 소비
Supplier<T>T get()객체 T를 반환
Function<T,R>R apply(T t)객체 T를 받아 R을 반환
Predicate<T>boolean test(T t)객체 T를 받아 boolean을 반환
OperatorT applyAs(T t)매개 타입, 반환 타입이 동일

매개변수가 두 개인 인터페이스

함수형 인터페이스메서드 형태설명
BiConsumer<T,U>void accept(T t, U u)두 개의 매개변수만 있고, 반환 값이 없음
BiPredicate<T,U>boolean test(T t, U u)매개변수는 둘, 반환값은 boolean
BiFunction<T,U,R>R apply(T t, U u)두 매개변수를 받아 하나의 결과를 반환
  • 두 개 이상의 매개변수를 갖는 함수형 인터페이스가 필요하다면 직접 만들어서 써야 한다. 만일 3개 매개변수를 갖는 함수형 인터페이스를 선언한다면 다음과 같을 것이다.
@FunctionalInterface
interface TriFunction<T, U, V, R> {
	R apply(T t, U u, V v);
}

UnaryOperator와 BinaryOperator

Function의 또 다른 변형으로 UnaryOperator와 BinaryOperator가 있다. 매개변수의 타입과 반환 타입이 모두 일치한다는 점이 Function과 다르다.

함수형 인터페이스메서드 형태설명
UnaryOperator<T>T apply(T t)Function<T,R>의 하위 인터페이스
BinaryOperator<T>T apply(T t, T t)BiFunction<T,U,R>의 하위 인터페이스

컬렉션 프레임워크와 함수형 인터페이스

컬렉션 프레임워크의 인터페이스에 추가된 다수의 디폴트 메서드 중 일부는 함수형 인터페이스다.

인터페이스메서드설명
Collectionboolean removeIf(Predicate<E> filter)조건에 맞는 요소를 삭제
Listvoid replaceAll(UnaryOperator<E> operator)모든 요소를 변환하여 대체
Iterablevoid forEach(Consumer<T> action)모든 요소에 작업 action을 수행
MapV compute(K key, BiFunction<K,V,V> f)지정된 키의 값에 작업 f를 수행
V computeIfAbsent(K key, Function<K,V> f)키가 없으면 작업 f 수행 후 추가
V computeIfPresent(K key, BiFunction<K,V,V> f)지정된 키가 있을 때 작업 f를 수행
V merge(K key, V value, BiFunction<V,V,V> f)모든 요소에 병합작업 f를 수행
void forEach(BiConsumer<K,V> action)모든 요소에 작업 action을 수행
void replaceAll(BiFunction<K,V,V> f)모든 요소에 치환작업 f를 수행
  • Map 인터페이스 'compute'로 시작하는 메서드는 맵의 value를 변환하고
  • merge()는 Map을 병합한다.

기본형을 사용하는 인터페이스

지금까지 기술한 인터페이스는 매개변수와 반환 타입이 Generic이므로 기본 타입이 전달되면 Boxing이 일어난다. 따라서 기본형 타입을 처리할 때 Wrapper 클래스를 사용하는 것은 비효율적이다. 보다 효율적으로 처리할 수 있도록 기본형을 사용하는 함수형 인터페이스가 제공된다.

  • Consumer
함수형 인터페이스메서드설명
IntConsumervoid accept(int value)int 값을 받아 소비
LongConsumervoid accept(long value)long 값을 받아 소비
DoubleConsumervoid accept(double value)double 값을 받아 소비
ObjIntConsumer<T>void accept(T t, int value)객체 T와 int 값을 받아 소비
ObjDoubleConsumer<T>void accept(T t, double value)객체 T와 double 값을 받아 소비
ObjLongConsumer<T>void accept(T t, long value)객체 T와 long 값을 받아 소비
  • Supplier
함수형 인터페이스메서드설명
BooleanSupplierboolean getAsBoolean()boolean 값을 리턴
DoubleSupplierdouble getAsDouble()double 값을 리턴
IntSupplierint getAsInt()int 값을 리턴
LongSupplierlong getAsLong()long 값을 리턴
  • Function
함수형 인터페이스메서드설명
DoubleFunction<R>R apply(double value)double을 객체 R로 매핑
IntFunction<R>R apply(int value)int를 객체 R로 매핑
IntToDoubleFunctiondouble applyAsDouble(int value)int를 double로 매핑
IntToLongFunctionlong applyAsLong(int value)int를 long으로 매핑
LongToDoubleFunctiondouble applyAsDouble(long value)long을 double로 매핑
LongToIntFunctionint applyAsInt(long value)long을 int로 매핑
ToDoubleBiFunction<T,U>double applyAsDouble(T t, U u)객체 T, U를 double로 매핑
ToDoubleFunction<T>double applyAsDouble(T t)객체 T를 double로 매핑
ToIntBiFunction<T,U>int applyAsInt(T t, U u)객체 T, U를 int로 매핑
ToIntFunction<T>int applyAsInt(T t)객체 T를 int로 매핑
ToLongBiFunction<T,U>long applyAsLong(T t, U u)객체 T, U를 long으로 매핑
ToLongFunction<T>long applyAsLong(T t)객체 T를 long으로 매핑
  • Operator
함수형 인터페이스메서드설명
DoubleBinaryOperatordouble applyAsDouble(double, double)두 개의 double을 연산
DoubleUnaryOperatordouble applyAsDouble(double)한 개의 double을 연산
IntBinaryOperatorint applyAsInt(int, int)두 개의 int를 연산
IntUnaryOperatorint applyAsInt(int)한 개의 int를 연산
LongBinaryOperatorlong applyAsLong(long, long)두 개의 long을 연산
LongUnaryOperatorlong applyAsLong(long)한 개의 long을 연산
  • Predicate
함수형 인터페이스메서드설명
DoublePredicateboolean test(double)double 값을 조사
IntPredicateboolean test(int)int 값을 조사
LongPredicateboolean test(long)long 값을 조사

Function의 합성과 Predicate의 결합

// Function
default <V> Function<T,V> andThen(Function<? super R, ? extends V> after)
default <V> Function<T,R> compose(Function<? super V, ? extends T> before)
static <T> Function<T,T> identity()

// Predicate
default Predicate<T> and(Predicate<? super T> other)
default Predicate<T> or(Predicate<? super T> other)
default Predicate<T> negate()
static <T> Predicate<T> isEqual(Object targetRef)

Function의 합성

두 람다식을 합성해서 새로운 람다식을 만들 수 있다.

  1. f.andThen(g)
  • 함수 f, g가 있을 때 함수 f를 먼저 적용하고, 그 다음에 함수 g를 적용한다.
Function<String, Integer> f = (s) -> Integer.parseInt(s, 16);
Function<Integer, String> g = (i) -> Integer.toBinaryString(i);
Function<String, String> h = f.andThen(g);
  1. f.compose(g)
  • 반대로 g를 먼저 적용하고 f를 적용한다.
Function<Integer, String> g = (i) -> Integer.toBinaryString(i);
Function<String, Integer> f = (s) -> Integer.parseInt(s, 16);
Function<Integer, Integer> h = f.compose(g);
  1. identity()
  • 함수를 적용하기 전과 후가 동일한 항등 함수가 필요할 때 사용한다.
Function<String, String> f = x -> x;
// 위의 문장과 동일
Function<String, String> f = Function.identity();
  • 항등 함수는 잘 사용되지 않는 편이며, map() 으로 변환 작업할 때 변환 없이 그대로 처리하고자 할 때 사용한다.

Predicate의 결합

  • 여러 Predicate를 and(), or(), negate()로 연결해서 하나의 새로운 Predicate로 결합할 수 있다.
Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i % 2 == 0;
Predicate<Integer> notP = p.negate();  // i >= 100

// 100 <= i && (i < 200 || i % 2 == 0)
Predicate<Integer> all = notP.and(q.or(r));
System.out.println(all.test(150));  // true

isEqual()은 두 대상을 비교하는 Predicate를 만들 때 사용한다. 먼저 isEqual()의 매개변수로 비교대상을 하나 지정하고, 또 다른 비교대상은 test()의 매개변수로 지정한다.

// str1과 str2가 같은지 비교
boolean result = Predicate.isEqual(str1).test(str2);

References

  • Java의 정석
  • gpt4o
Last Updated:
Contributors: depark
Prev
Reactive Programming