본문 바로가기

함수형 자바

[JAVA] 함수형 인터페이스 변형 - Chapter 03

함수형 자바 프로그래밍 3장을 읽었다.

주로 여기서는 함수형 인터페이스의 변형 혹은 합성을 통한 확장성을 다룬 것 같다.

 

먼저 대표적인 4가지 함수형 인터페이스를 살펴보자.

 

1) Function

Function<T, R> 로 나타낼 수 있으며, T타입의 입력을 받아 R타입의 결과를 return하는 인터페이스이다.

 

2) Consumer

Consumer<T> 로 나타낼 수 있으며, T타입의 입력을 받지만, return하는 것 없이, 말 그대로 소비하는 인터페이스이다.

 

 

3) Supplier

Supplier<T> 로 나타낼 수 있으며, 아무 입력을 받지 않고 T타입의 결과를 return하는 인터페이스이다.

Consumer와 반대 개념이라 생각된다.

지연 실행에 Supplier로 래핑한 후 get 메소드를 통해 호출 하는 식으로 사용된다 한다.

 

4) Predicate

predicate<T> 로 나타낼 수 있으며, T타입의 입력을 받아 boolean 타입의 결과 (true or false)를 return하는 인터페이스이다.

필터에 주로 쓰인다.

 

 

함수형 인터페이스 변형

위의 네가지 인터페이스를 통해 다양한 사례를 다룰 수 있지만, 특화된 변형이 존재한다 한다.

하휘 호환성을 유지하기 위해 특화된 변형이 존재한다고 한다.

 

 

1) 아리티 (Arity)

함수의 인수의 개수를 뜻한다.

인수가 1개인 경우의 인터페이스의 예시는, Function, Consumer, Predicate가 있고,

2개인 경우는 BiFunction, Biconsumer, BiPredicate가 있다.

 

자바의 기본적인 인터페이스는 최대 2개의 인수를 지원한다.

더 높은 아리티를 추가하기 위해서는 아래와 같이 추가 구현이 필요하다.

@FunctionalInterface
public interface TriFunction<T, U, V, R> {
	R accept(T t, U u, V v);
}

 

T, U, V 세가지 인수를 받아 R을 return 하는 TriFunction 이다.

 

동일한 타입의 인수를 받는 경우 간소화 하여 나타내는 인터페이스도 있다.

 

Function<T, T> -> UnaryOperator<T>

BiFunction<T, T, T> -> BinaryOperator<T>

 

이런 식으로 간결한 코드를 작성 할 수 있다.

 

다만, 간소화 한 인터페이스 (ex UnaryOperator)와 상위 인터페이스 (ex Function)는 상호 교환 할 수 없다.

간소화 한 인터페이스 타입을 인수로 요구하는 메소드에 상위 인터페이스를 넣으면 컴파일 오류가 발생한다. (반대는 오류 발생 X)

 

특화된 간소화 한 인터페이스를 사용하면 코드가 간결해질 수 있지만, 일반적인 인터페이스 타입을 사용하는 것이 호환성을 높일 수 있다고 생각하자.

 

2) 원시 타입 (Primitive type)

원시 타입은 제네릭 타입으로 사용 될 수 없다.

다만, 래퍼 타입에 대해서는 어느 인터페이스든 사용이 가능하지만, 오토박싱으로 인해 성능에 영향을 끼칠 수 있다.

 

따라서 오토박싱을 피하기 위한 원시 타입을 인수를 받을 수 있는 특수한 인터페이스들이 존재한다.

아래는 원시타입 중 하나인 int 타입의 인수를 받을 수 있는 특수한 함수형 인터페이스들이다.

 

3) 함수형 인터페이스 브리징

두 인터페이스에 동일한 인수를 받고, 동일한 값을 Return 하는 SAM (Single Abstract Method)가 있떠라도,
자바의 타입 시스템에는 이를 연결 할 수 없기 때문에 형변환이 불가능 하다고 한다.

 

따라서 동일하지만 호환되지 않는 함수형 인터페이스를 형변환 대신 메서드 참조를 사용할 수 있다.

interface LikePredicate<T> {
	boolean test(T value);
}

LikePredicate<String> isNull = str -> str == null;

//컴파일 오류
Predicate<String> wontCompile = isNull;

//메서드 참조를 통하여 호환 가능
Predicate<String> canCompile = isNull::test;

 

 

함수 합성

작은 함수들을 결합하여 크고 복잡한 처리를 하기 위해 사용된다.

 

1) 글루 메서드 (Glue Method)

기본적인 함수형 인터페이스 자체에 직접 구현되어 있다.

이를 통해 4가지 함수형 인터페이스 (Function, Consumer, Suplier, Predicate) 들은 쉽게 합성 할 수 있다.

 

아래는 Function의 기본 메서드로 구현되어 있는 두가지 글루 메서드이다.

<V> Function<V, R> compose(Function<V, T> before)
<V> Function<T, V> andThen(Function<R, V> after)

 

Function <V, T> before;
Function <T, R> after;
V input;

Function<V, R> composeFunction = after.compose(before);

compose 메소드는 V input을 받아 before 함수가 T를 Return하고, 이 Return한 T를 after 함수가 받아 R을 Return 한다.

따라서 composeFunction은 결국 V를 인수로 받아 R을 Return하여, <V, R> 로 표현이 가능하다.

 

함수 흐름은 before -> after 이다.

 

Function <R, V> after;
Function <T, R> before;
T input;

Function<T, V> andThenFunction = before.andThen(after);

andThen 메소드는 T input을 받아 before함수가 R을 Return하고, 이 Return한 R을 after 함수가 받아 V를 Return 한다.

따라서 andThenFunction은 결국 T를 인수로 받아 V를 Return하여 <T, V>로 표현이 가능하다.

 

함수 흐름은 compose와 달리 앞에 있는 함수부터 실행하여 before -> after 이다.

 

2) 기본 메서드 추가 (Default Method)

자바 8 이후로 이넡페이스에 추상 메서드 외에 기본 메서드를 추가 할 수 있게 되었다.

인터페이스에 기본 메서드를 추가한 후, 필요에 따라 인터페이스를 구현하는 클래스에 자체적으로 더 적합한 구현을 생성 할 수 있다.

기본 메서드를 추가함으로써 기존의 구현을 깨뜨리지 않고 변형을 제공 할 수 있다.

 

3) 함수형 인터페이스 명시적으로 구현하기

함수형 인터페이스는 람다나 메서드 차조를 통해 묵시적으로 구현 될 수 있지만, 더 높은 차수의 함수에서 사용할 수 있도록 명시적으로 구현할 수 있다.

예를 들어 A를 extends한 B 클래스의 경우, B를 인수로 받는 고차함수에 쉽게 전달 할 수 있다.

 

또한 함수형 인터페이스를 직접 구현하는 것은 비함수형 이었던 타입을 함수형 코드에서 쉽게 사용 할 수 있도록 한다.

(객체 지향 명령 디자인 패턴)

 

+ 다만, 여러 인터페이스를 다중 상속 받는 경우, 각각 인터페이스에 동일한 이름, 매개변수, 반환 타입을 가지는 기본 메서드가 존재하는 경우, 상속 받는 곳에서 기본 메서드를 직접 구현해야 한다.

 

4) 정적 헬퍼 생성

타입을 직접 제어할 수 없는 경우, 헬퍼 타입을 만들 수 있다.

다른 타입도 받아들일 수 있는 헬퍼 타입 메서드를 정의 하여 사용 할 수 있다.

package chapter3;

import java.util.function.Function;

@FunctionalInterface
public interface Compositor<T, R>{

    R apply(T s);

    default <V> Function<V, R> compose(Function<V, T> before) {
        return (V s) -> {
            T result = before.apply(s);
            return apply(result);
        };
    }

}

 

원래 T타입을 받아 R타입을 반환하는 함수형 인터페이스가, compose메서드를 통해, 새로운 V타입의 값을 받아 R타입의 값을 리턴할 수 있다. (V -> T -> R)

 

<Reference>

https://developer-talk.tistory.com/463

 

[Java]기본 메서드 또는 디폴트 메서드(Default Method)

기본 메서드(Default Method) Java 8에 도입된 기본 메서드는 default 키워드를 사용하여 인터페이스(interface)에서 메서드를 구현하는 것입니다. 기본 메서드 도입 이전에는 인터페이스에 추상 메서드만

developer-talk.tistory.com