우리는 람다가 왜 쓰이는 지에 대해서 알게 되었고, 람다를 사용하기 위해서는 함수형 인터페이스를 제작해야한다는 것을 알게 되었습니다. 




하지만 대부분 함수형에서 사용할 법한 함수 디스크립터는 비슷할 것이며, 그렇기 때문에 JAVA8 라이브러리 설계자들은 java.util.function에 미리 사용할 법한 함수 인터페이스를 정의했습니다. 

(Observer 패턴을 많이 사용한다고 Observerable 을 정의한 것과 같은 맥락이겠죠. ㅡㅡ^)


오늘 포스팅은 자주 쓰일 법한 JAVA8의 함수형 인터페이스를 알아보려 합니다. (인터페이스에 대한 표기는 함수 디스크럽터만 정의합니다. 디폴트 메소드는 생략하겠습니다. ㅜㅜ)


1. Predicate

boolean 형식이 필요한 상황에서 별다른 구현 없이 람다식을 사용할 수 있습니다.


1
2
3
4
5
6
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
 
Predicate<Apple> predicate = (Apple a) -> "green".equals(a.getColor());
cs


2. Consumer

interface의 이름대로, 소비자입니다. 즉 프로시저로써, void 형의 함수를 만든다고 생각하면 됩니다. 


1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
 
People doohyun = obj.createObject(27"남두현");
 
Consumer<People> cosumer = (People people) -> System.out.println(people.toString());
cosumer.accept(doohyun);
cs


3. Function

Function 인터페이스의 경우 제네릭 형식의 T를 받아 R 객체를 반환하는 apply 라는 추상 메소드를 가지고 있습니다.


1
2
3
4
5
6
7
@FunctionalInterface
public interface Function<T, R> {
    R accept(T t);
}
 
Function<String, Integer> function = (String name) -> name.length();
Integer blogNameLength = function.apply("남두현 블로그 입니다.");
cs


4. Supplier

Consumer와 반대로 제네릭 형식의 T를 출력해주는 인터페이스입니다.  


1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Supplier<T> {
    T accept();
}
 
Supplier<People> supplier = () -> {
    People ret = new People();
    ret.setName("기본 이름 지정");
    return ret;
};
cs


5. UnaryOperator

제네릭 형식의 T 타입을 파라미터로 받아 T를 return 하는 인터페이스다. 생각보다 쓸모는 없어보이네요다. 굳이 쓰자면 이런데에 쓸만하지 않을까요? (Clone 만들기)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FunctionalInterface
public interface UnaryOperator<T> {
    T createObject(T t);
}
 
UnaryOperator<People> unaryOperator = (People people) -> {
    People clone = new People();
    clone.setAge(people.getAge());
    clone.setName(people.getName());
 
    return clone;
};
 
People doohyun = obj.createObject(27"남두현");
People cloneDoohyun = unaryOperator.apply(doohyun);
cs


6. BinaryOperator

제네릭 형식의 T 타입 두개를 받아, 조합하는 용도로 사용할 수 있다. 


1
2
3
4
5
6
7
@FunctionalInterface
public interface BinaryOperator<T> {
    T createObject(T t1, T t2);
}
 
BinaryOperator<Integer> binaryOperator = (Integer a, Integer b) -> a + b;
System.out.println(binaryOperator.apply(54));
cs


여기서 한가지 봐야할 부분은 UnaryOperator 와 BinayOperator 인터페이스는 compose 와 andThen 등의 디폴트 메소드를 가지고 있습니다. 해당 메소드를 통하여 다른 함수형 인터페이스를 만들수 있으니, 참고하세요.^^


1
2
3
4
5
6
7
UnaryOperator<String> unaryOperator = (String name) -> {
    return name;
};
Function<People, String> func = unaryOperator.compose((People people) -> people.getName());
 
People doohyun = obj.createObject(27"남두현");
System.out.println(func.apply(doohyun));
cs


Default method 로 등장장한 compose, andThen 에 대한 설명은 이 곳을 참고.



7. BiPredicate, BiConsumber, BiFunction

서로 다른 제네릭 형식 T, R 에 대한 비교를 수행할 수 있습니다. 

Bi가 붙은 인터페이스들은 서로 다른 제네릭에 대하여 수행을 합니다.


Bi 가 붙은 인터페이스는 함수디스크립터만 명시하겠습니다.


BiPredicate<L, R>   -->  (L, R)   --> boolean

BiConsumbe<T, U>   --> (T, U)   --> void

BiFunction<T, U, R>    --> (T, U)   --> R


1
2
3
4
5
6
7
@FunctionalInterface
public interface BiPredicate<T, R> {
    boolean test(T t, R r);
}
 
BiPredicate<Apple, String> biPredicate = (Apple apple, String type) -> type.equal(apple.getColor());
biPredicate.test(apple, "green");
cs



위와 같이, 이미 정의된 함수형 interface로 인해, 많은 기능을 사용할 수 있을 것 처럼 보입니다. 그러나 가만보니, 인터페이스에 붙어있는 제네릭 T에 의해 원사타입의 value 들(이를 테면 int, boolean 등)은 사용할 수 없는 것 처럼 보이네요.


그것에 대한 해결은 이 곳에서 확인 바랍니다.



자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기




반응형
Posted by N'