본문 바로가기
JAVA

Java 8 Functional Interface에 대해 알아보자.

by 킹명주 2023. 10. 31.

c++로 코딩 테스트를 해왔는데, 이제는 주로 사용하는 java로 바꿔야겠다고 다짐했다..!! 그런데, java로 코딩 테스트를 준비하면서 처음보는 구조의 코드를 보았다.

 

바로 정렬 함수인데

1
2
3
4
List<Integer> testList = new ArrayList<>();
testList.add(3);
//  
testList.sort((e1, e2) -> e2 - e1);
 

위 코드처럼 List를 정렬을 하고 있는 것이다!! 그런데 sort 함수를 자세히보면

1
2
3
4
5
6
7
8
9
default void sort(Comparator<super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}
 
 

Comparator을 파라미터로 받고 있었다! 이처럼 sort 함수에서 람다식을 바로 넣어서 사용 가능한 이유를 찾아보다 Functional Interface에 대해 알게되었고, 이를 소개하고자 한다.


Functional Interface는 무엇일까?

 

오직 하나의 추상 method를 가지는 인터페이스를 함수형 인터페이스라고 한다. @FunctionalInterface 어노테이션을 통해 함수형 인터페이스라고 명시를 해줄 수도 있다.

 

위 예제에서 Comparator을 들어가게 되면

1
2
3
4
5
6
@FunctionalInterface
public interface Comparator<T> {
 
    int compare(T o1, T o2);
    // 생략
}
 
 

요런식으로 Functional Interface로 지정되어 있는 것을 확인할 수 있다. Functional Interface의 장점은 추상 메서드 구현을 할 때, 람다식으로 직접 구현할 수 있다는 점이다. Comparator interface를 예로 들면,  int compare(T o1, T o2) 이 함수만 구현해주면 끝난다. (Comparator에 구현되어있는 default, static, Object method를 제외하면 compare 하나의 함수만 존재한다.)

 

그러면, 이렇게 람다식으로 코드를 구현할 수 있을 것이다.

1
Comparator<Integer> comparator = (e1, e2) -> e2 - e1;
 

얼마나 간단한가!! 보기에도 좋고~~
?! 왜 간단하고 보기좋은지 모르겠다구여??? 원래 구현방식과 비교해보자!!

1
2
3
4
5
6
Comparator<Integer> comparator = new Comparator<>() {
    @Override    
    public int compare(Integer e1, Integer e2) {
        return e2 - e1;
    }
};
 
 

이렇게 비교해보면 람다식을 사용하는 것이 더 간결한 것을 바로 파악할 수 있다. 이런 것이 Functional Interface를 사용하는 이유 중 하나라고 생각한다!!


Functioanl Interface 종류

 

요까지 읽어보셨다면 이제 확실히 Comparator 예제에 대해서는 숙지하셨을 것이다. 이제 더 많은 함수형 인터페이스를 소개하고자 한다.

 

1. Runnable

1
2
3
4
5
6
@FunctionalInterface
public interface Runnable {
 
    public abstract void run();
}
 
 

Runnable interface의 run method를 살펴보면, 입력받는 파라미터 값이 없고 void로 리턴도 하지 않는다.

즉, 파라미터와 리턴 값이 둘 다 없는 함수를 구현할 때 사용하는 인터페이스다. 아래는 Runnable 활용법이다.

1
2
Runnable runnable = () -> System.out.println("파라미터, 리턴 모두 없음~~");
runnable.run();
 
 

 

2. Consumer

1
2
3
4
5
6
7
8
9
10
11
@FunctionalInterface
public interface Consumer<T> {
 
    void accept(T t);
 
    default Consumer<T> andThen(Consumer<super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
 
 
 

Consumer Interface의 accept method를 살펴보면, 입력 받는 파라미터가 하나 있고 리턴 값은 void로 없다.
즉, 파라미터가 하나있고 리턴 값은 없는 함수를 구현할 때 사용하는 인터페이스다. 아래는 Consumer 활용법이다.

1
2
Consumer<String> consumer = t -> System.out.println(t + "를 입력 하셨습니다. 리턴 값은 없습니다.");
consumer.accept("킹명주의 백엔드 일지");
 
cs

그리고 default method인 andThen이 존재하는데, 필자는 많이 사용하지 않지만 사용법을 한 번 다루고자 한다. 일단, andThen의 뜻을 살펴보면 '그리고나서' 라는 뜻을 가지고 있다. 즉, 첫번째 Consumer를 실행하고 이후에 두번째 Consumer를 실행하는 함수이다. (해당 함수의 경우 리턴 값이 Consumer인 것 참고!!) 아래는 andThen의 활용법이다.

1
2
3
4
Consumer<String> consumer1 = t -> System.out.println(t + "나는 첫번째 Consumer");
Consumer<String> consumer2 = t -> System.out.println(t + "나는 두번째 Consumer");
Consumer<String> andThen = consumer1.andThen(consumer2);
andThen.accept("안녕, ");
 
 

 

3. Supplier

1
2
3
4
5
@FunctionalInterface
public interface Supplier<T> {
 
    T get();
}
 

이제, 딱 보면 알 것이다. 우선 Supplier Interface의 get method를 살펴보면, 파라미터는 받고 있지 않고, 리턴만 지정해주고 있다.

즉, 파라미터 값은 없지만 리턴 값은 존재하는 함수를 구현할 때 사용하는 인터페이스다. 아래는 Supplier 활용법이다.

1
2
3
Supplier<String> supplier = () -> "킹명주는 매우 강해";
String kingMj = supplier.get();
System.out.println("kingMj = " + kingMj);
 

 

4. Function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@FunctionalInterface
public interface Function<T, R> {
 
    R apply(T t);
 
    default <V> Function<V, R> compose(Function<super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
 
    default <V> Function<T, V> andThen(Function<super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
 
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
 
 
 

Funtion의 apply method를 살펴보자!! 입력 값 T와 리턴 값 R을 확인할 수 있다.

즉, 파라미터가 하나있고 리턴 값이 존재하는 함수를 구현할 때 사용하는 인터페이스다. 아래는 Function 활용법이다.

1
2
3
Function<Integer, String> function = String::valueOf;
String result = function.apply(919);
System.out.println("result = " + result);
 

 

그리고 compose, andThen, identity methods이 존재하는데, compose / andThen 활용법을 소개하고 넘어가겠다~~

 

compose를 살펴보면 apply(before.apply(v)) 이런식으로 return을 해주고 있다.

즉, 파라미터로 받은 Function을 먼저 실행하고 나머지 Function을 실행하는 함수라고 파악할 수 있다. 

 

andThen은 Consumer에서 살펴본 것과 같이 첫번째 Function을 실행하고 파라미터로 받은 Function을 나중에 처리하는 함수다. 아래는 두 가지 method의 차이를 구별할 수 있는 예제로, 한 눈에 차이점을 파악할 수 있을 것이다.

1
2
3
4
5
6
7
Function<Integer, Integer> functionFirst = x -> x * 2;
Function<Integer, Integer> functionSecond = x -> x + 10;
System.out.println("Compose Result = " + functionFirst.compose(functionSecond)
                                                              .apply(10));
 
System.out.println("andThen Result = " + functionFirst.andThen(functionSecond)
                                                              .apply(10));
 

 

5. BiFunction / BiConsumer

마지막으로 소개할 Functional Interface는 BiFunction과 BiConsumer다!! Binary Function, Binary Consumer을 줄인 말로, 두 개의 파라미터를 입력 값으로 사용할 수 있다. Function, Consumer 활용법과 차이점은 없으니 대표적으로 BiFunction 활용법만 소개하고 넘어가겠다-아~

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface BiFunction<T, U, R> {
 
    R apply(T t, U u);
 
    default <V> BiFunction<T, U, V> andThen(Function<super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}
 
 
1
2
BiFunction<Integer, Integer, String> biFunction = (n1, n2) -> String.valueOf(n1 + n2);
System.out.println("BiFunction Result = " + biFunction.apply(1030));
 


마무~의리

마무~의리를 해보고자 한다. 이번 시간에는 Functional Interface에 대해서 알아보았다. 실무에 빨리 적용하고 싶어서 미치게따..

Java를 계속 사용할 거라면 엄청 엄청 좋은 개념이라고 생각한다!!

 

끝으로, 세상에 나쁜 지식은 없는 것 같다 그럼 20000

 

 

 

 

 

 

* 밑에 선배님께서 많은 도움을 주셨습니다. 

https://github.com/mozylee

 

mozylee - Overview

mozylee has 22 repositories available. Follow their code on GitHub.

github.com