본문 바로가기

자바

[Java] Functional Interface

2021-03-06글

함수형 인터페이스

자바 8부터 자바는 객체지향 언어일 뿐만 아니라 함수형 언어도 지원할 수 있게되었다.

자바에서는 숫자, 문자 같은 원시 타입, 클래스를 통해 정의하는 참조 타입 객체들이
일급 객체가 될 수 있지만,
(함수형 인터페이스가 등장하기 전까지는) 함수에 해당하는 메서드는 일급 객체가 아니었다.

덕분에 오늘 뜯어 볼 Functional Interface의 등장으로,
객체 안에만 존재했던 함수(메서드)를 일급 객체로 다룰 수 있게되었다.

🤔 일급 객체...?
어떤 개체가 다음 3가지 조건을 만족하는 경우, 이를 일급 객체라고 말한다.

  • 파라미터로 전달할 수 있다.
  • 반환값으로 사용할 수 있다.
  • 변수나 데이터 구조 안에 담을 수 있다.
  • 할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.

자세한 내용은 이 글을 참고하면 좋을 것 같다.

함수형 인터페이스 정의하기

인터페이스에 추상 메소드가 딱 하나만 존재하는 것을 함수형 인터페이스라고 한다.
자바의 람다식은 함수형 인터페이스를 통해서만 다뤄진다.
함수형 인터페이스는 람다식과 인터페이스 메소드가 1:1로 연결되기 위해,
추상 메소드가 단 하나뿐인 인터페이스이다.
(static, defualt 메서드는 제외다.)

자바에서는 이를 @FunctionalInterface 어노테이션을 사용하여 나타낸다.
참고로 어노테이션을 붙일 경우 컴파일러에서 어노테이션이 붙은 엔티티가
단일 추상메서드를 갖춘 인터페이스인지 검사하니 붙이도록 하자.

@FunctionalInterface
interface Calculate {
    int cal(int a, int b);
}

미리 정의되어 있는 함수형 인터페이스

java.util.function 패키지

제네릭 메서드로 정의한다면, 매개변수나 반환 타입이 달라도 문제가 되지 않는다.
때문에 자바 패키지에서는 자주 쓰이는 형식의 메서드를 함수형 인터페이스로
미리 정의해 놓았다.
사실 매번 람다식을 위해 함수형 인터페이스를 정의하는 것은 귀찮은 일이니
가급적 이 패키지의 함수형 인터페이스를 사용하자!

표준으로 정의된 대표적 함수형 인터페이스와 그 안에 있는 추상 메소드

함수형 인터페이스 추상 메서드
Predicate<T> boolean test(T t)
Supplier<T> T get()
Consumer<T> void accept(T t)
Funtion<T, R> R apply(T t)

Predicate<T>

매개변수 타입 T, 반환타입 Boolean

boolean test(T t); 
  • 매개변수가 2개인 경우 BiPredicate<T, U>

전달된 인자를 대상으로 true, false를 반환해야하는 상황에 유용하게 사용된다.

class PredicateDemo {
    public static int sum(Predicate<Integer> p, List<Integer> lst) {
        int s = 0;

        for(int n : lst) {
            if(p.test(n))
                s += n;
        }       

        return s;
    }

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 5, 7, 9, 11, 12);

        int s;
        s = sum(n -> n%2 == 0, list);
        System.out.println("짝수 합: " + s);

        s = sum(n -> n%2 != 0, list);
        System.out.println("홀수 합: " + s);

    }
}

sum(Predicate<Integer> p, List<Integer> lst)에는
boolean test(Integer t) 메소드 정의에 해당하는 람다식을 작성해서 전달해야한다.

Supplier<T>

매개변수 없음, 반환 타입 T

T get();

단순히 무언가를 반환해야 할 때 사용된다.

class SupplierDemo {
    public static List<Integer> makeIntList(Supplier<Integer> s, int n) {
        List<Integer> list = new ArrayList<>();    
        for(int i = 0; i < n; i++)
            list.add(s.get());
        return list;
    }

    public static void main(String[] args) {
        Supplier<Integer> spr = () -> {
            Random rand = new Random();
            return rand.nextInt(50);
        };

        List<Integer> list = makeIntList(spr, 5);
        System.out.println(list);

        list = makeIntList(spr, 10);
        System.out.println(list);
    }
}

makeIntList(Supplier<Integer> s, int n) 는 정수를 담고 있는 컬렉션 인스턴스를 반환한다.
첫 번째 인자에서 컬렌션 인스턴스에 담을 정수의 생성 방법을 결정할 수 있다.

Consumer<T>

매개변수 타입 T, 반환 타입 없음

void accept(T t);
  • 매개변수가 2개일 경우 BiConsumer<T, U>

전달 인자를 소비하는 형태로 매개변수와 반환형이 선언되어 있다.
전달된 인자를 가지고 어떤 결과를 보여야 할 때 유용하게 사용된다.

class ConsumerDemo {
    public static void main(String[] args) {
        Consumer<String> c = s -> System.out.println(s);

        c.accept("Pineapple");    // 출력이라는 결과를 보인다.
        c.accept("Strawberry");
    }
}

Funtion<T, R>

매개변수 타입 T, 반환타입 R

R apply(T t);
  • 매개변수가 2개인 경우 BiFunction<T, U, R>

전달한 인자와 반환 값이 모두 존재하는 가장 보편적인 형태이다.

class FunctionDemo {
    public static void main(String[] args) {

    Function<String, Integer> toInt = value -> Integer.parseInt(value);

    Integer number = toInt.apply("100");
    }
}

함수형 인터페이스의 장점?

이 부분은 여러 자료를 찾아보면서 스스로 생각해본 부분이다.

간결한 표현과 가독성

행위도 값(value)처럼 쓰일 수 있다

1~100까지의 짝수 합을 구하는 로직을 함수형 인터페이스를 사용하지 않고,
함수형 인터페이스를 사용하여 두 가지 코드를 확인해보자.

public static int evenSum() {  
   int sum = 0;
   for (int i = 1; i <= 100; i++) {
      if ( i % 2 == 0)
         sum += i;
   }
   return sum;
}
public static int evenSum() {  
   return IntStream.rangeClosed(1, 100)
         .filter(i -> i % 2 ==0)
         .reduce(0, (l, r) -> l + r);
}

filter()의 선언부를 보면 다음과 같다.

Predicate<> 함수형 인터페이스를 받기 때문에 조건에 대한 함수를 매개변수로 받아,
행위 자체를 값으로 받고 있다.
또한 이 덕분에 filter()에 매개변수로 행위가 어떤 것인지 람다식으로 명시적으로 드러내며
넘겨주기 때문에 직관성을 높인다고도 생각한다. (맞는지는 모르겠으나 개인적 생각🥲)

함수형 인터페이스의 명확한 장점을 말하고 싶은데,
잘 정리가 안된다.
이에 대해 함수형 프로그램의 장점을 읽어 보는 것이 좋을 것 같다(❓)

참고 자료