본문 바로가기

코딩/JAVA 공부

[Modern Java] Functional Interface

Functional Interface : abstract method가 1개만 존재하는 Interface

사용할 때 Anonymous Class Object 생성 필요 없이 람다 expression으로 대체 가능.

(람다 expresstion의 타입이 Functional Interface 이기 때문에 가능함.)

Java 8 에서 총 4가지의 Functional Interface 가 추가됐음.

(Function, Comsumer, Predicate, Supplier)

 

* Function type

파라미터를 받아 특정 타입으로 리턴

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;}
}

identity : input 값과 return 값이 완전히 동일할 때 (타입, 내용 전부)

람다 없이 Anonymous Class를 사용한 경우

public classFunctionalInterfaceExample {
    public static void main(String[] args) {
        Function<String, Integer> toInt = new Function<String, Integer>() {
            @Override
            public Integer apply(final String value) {
                return Integer.parseInt(value);
            }
        };
        
        final Integer number = toInt.apply("100");
        System.out.println(number);
    }
}

Function 선언 시 Anonymous Cass Object를 사용해 코드가 아주 길어졌다.

 

람다 Expression 사용시

public classFunctionalInterfaceExample {
    public static void main(String[] args) {
        final Function<String, Integer> toInt = value -> Integer.parseInt(value);
        
        final Integer number = toInt.apply("100");
        System.out.println(number);
    }
}

 

<identity> 

final Function<Integer, Integer> identity = Function.identity();
// Function 클래스 안에 static 메서드 identity() 는 람다 메서드를 반환 (t -> t) 
// 그래서 위처럼 사용 가능

* Consumer

파라미터를 받아 아무것도 리턴하지 않음

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.accrpt(t); };
    }
}

 

람다 없이 Anonymous Class를 사용한 경우

final Consumer<String> print = new Consumer<String>() {
    @Override
    public void accept(final String value) {
        System.out.println(value);
    };
    
print.accept("Hello");

 

람다 expression을 사용한 경우

final Consumer<String> print = value -> System.out.print("value");

print.accept("Hello");

*Predicate

결괏값이 항상 boolean인 Functional Interface.

Function <T, Boolean>과 역할은 같음. 추가적인 기능이 있어서 Predicate로 분리했을까?

public interface Predicate<T> {
    boolean teest(T t);
    
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
    
    static <T> Predicate<T> not(Predicate<? super T> target) {
        Objects.requireNonNull(target);
        return (Predicate<T>)target.negate();
    }
}

 

Predicate<Integer> isPositive = value -> value > 0;

System.out.println(isPositive.test(1));

 


*Supplyer

Lazy evaluation 가능!

public interface Supplier<T> {
    
    T get();
}
final Supplier<String> helloSupplier = () -> "Hello ";

System.out.println(helloSupplier.get() + "world");

이런 사용법이면 왜 Supplyer Function Interface를 사용할까? 그냥 Hello를 넣으면 될 텐데

바로 Lazy evaluation을 위해!

private String getVeryExpensiveValue() {
    try {
        TimeUnit.SECONDS.sleep(3);
    }catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "Value";
}   // 이 메서드는 매우 많은 시간이 걸려 값을 반환한다 가정! (sleep으로 강제 가정)

---
private static void printIfValidIndex1(int num, String value) {
    if(num >= 0) {
        System.out.println("The value is " + value + ".");
    }else {
        System.out.println("Invalid");
    }
}

printIfValidIndex(0, getVeryExpensiveValue());
printIfValidIndex(-1, getVeryExpensiveValue());
printIfValidIndex(-2, getVeryExpensiveValue());
// -1, -2 일 땐 getVeryExpensiveValue 실행 필요 없지만, 전체 프로그래밍 시간은 9초로 계산을 진행함

---
private static void printIfValidIndex2(int num, Supplier<String> valueSupplier) {
    if(num >= 0) {
        System.out.println("The value is " + valueSupplier.get() + ".");
    }else {
        System.out.println("Invalid");
    }
}

printIfValidIndex(0, () -> getVeryExpensiveValue());
printIfValidIndex(-1, () -> getVeryExpensiveValue());
printIfValidIndex(-2, () -> getVeryExpensiveValue());
// 0 일때만 getVeryExpensiveValue 가 실행되 총 3초만 소요