본문 바로가기

CS/Java

Java FP 2. Functions are objects

Functional Interface

Interface with a single abstract method (aside from the methods of Object).
@FunctionalInterface
public interface Fun<T, R> {
    R apply(T t);

    static void doNothingStatic() { }
    default void doNothingByDefault() { }
}

 

기본형은 위와 같다. 무조건 하나의 method만을 갖고 있으며, @FunctionalInterface 라고 선언해줄시 method의 개수를 검사해서 1개가 아닐경우 오류를 반환해준다. 이렇게 선언해주는 건 필수는 아니다.

근데 좀 헷갈리는게,, 하나의 method만을 가질 수 있지만 위와 같이 default method와 static method 또한 가질 수 있다. 아 자바 개념을 잘 모르니까 하나하나 배울 때마다 잘 모르겠는 게 너무 많다ㅠㅜ,,

 

자 그리고 이 하나의 method를 위처럼 그냥 선언만 해놓고, 나중에 lambda식을 이용해 재정의해줄 수 있는데, 이 때 해당 method가 public으로 선언이 되어 있어야 한다. 따라서 다음과 같은 interface는 lambda expression으로 replace될 수 없다.

 

final double variable = 10;
Serializable serializable = new Serializable() {
    double applyFun(double x) {
        return x + variable;
    }
};

 

Functional Interface를 제대로 쓴 예시를 보자.

 

@FunctionalInterface
public interface TernaryIntPredicate {
    public boolean test(Integer a, Integer b, Integer c);
}

public static final TernaryIntPredicate allValuesAreDifferentPredicate = 
				(a,b,c) -> {return a!=b&&b!=c&&a!=c? true: false ;};

 

이 Functional Interface에 자주쓰이는 java.util.function package 들이 있다. 다음과 같이 5개의 유형이 있다.

  • Functions      any arguments → any arguments

  • Operators      any arguments → **same type** as arguments

  • Predicates     any arguments → boolean values

  • Suppliers        _ → any value

  • Consumers    any arguments → _

 

사실 내가 제일 헷갈리는 건 Functional Interface의 자료형이 function일 때이다. Function이 곧 object라는 것이 추상적이기만 하고 아 별 거 없겠지 했는데 생각보다 어렵다 ㅠㅜㅜ 엉엉

 

stepik에서 푼 문제를 예시로 들어보자.

 

public static IntPredicate disjunctAll(List<IntPredicate> predicates) {
    IntPredicate res = predicates.get(0);
    for(int i = 1; i < predicates.size(); i++){
        res = res.or(predicates.get(i));
    }
    return res;
}

현재 disjunctAll이라는 method가 있는데, 여기서 list of function이 인자로 들어오고, 반환형이 IntPredicate, 즉 function이다.

이 method의 목적은 말 그대로 리스트에 있는 함수들의 disjunction(죄다 or한 것)을 구하는 것이다. 나는 stream을 안 배워서 이런식으로 짰지만, stream을 배우면 훨씬 더 간단하게 짤 수 있다고 한다. 

하여튼 여기서 중요한 것은 함수 자체가 객체처럼 쓰일 수 있다는 것!

 

 

내가 완전 당황했던 문제를 하나 더 보자.

 

/**
 * It represents a handler and has two methods: one for handling requests and other for combining handlers
 */
@FunctionalInterface
interface RequestHandler {
    public Request handle(Request req);
    // !!! write a method handle that accept request and returns new request here
    // it allows to use lambda expressions for creating handlers below
    
    default RequestHandler combine(RequestHandler requesthandler) {
        return request -> handle(requesthandler.handle(request));
    }
    // !!! write a default method for combining this and other handler single one
    // the order of execution may be any but you need to consider it when composing handlers
    // the method may has any name
}

/**
 * Accepts a request and returns new request with data wrapped in the tag <transaction>...</transaction>
 */
final static RequestHandler wrapInTransactionTag =
        (req) -> new Request(String.format("<transaction>%s</transaction>", req.getData()));

/**
 * Accepts a request and returns a new request with calculated digest inside the tag <digest>...</digest>
 */
final static RequestHandler createDigest =
        (req) -> {
            String digest = "";
            try {
                final MessageDigest md5 = MessageDigest.getInstance("MD5");
                final byte[] digestBytes = md5.digest(req.getData().getBytes("UTF-8"));
                digest = new String(Base64.getEncoder().encode(digestBytes));
            } catch (Exception ignored) { }
            return new Request(req.getData() + String.format("<digest>%s</digest>", digest));
        };

/**
 * Accepts a request and returns a new request with data wrapped in the tag <request>...</request>
 */
final static RequestHandler wrapInRequestTag =
        (req) -> new Request(String.format("<request>%s</request>", req.getData()));

/**
 * It should represents a chain of responsibility combined from another handlers.
 * The format: commonRequestHandler = handler1.setSuccessor(handler2.setSuccessor(...))
 * The combining method setSuccessor may has another name
 */
final static RequestHandler commonRequestHandler = // !!! write the combining of existing handlers here
    (req) -> wrapInRequestTag.combine( createDigest).combine( wrapInTransactionTag).handle(req);

/**
 * Immutable class for representing requests.
 * If you need to change the request data then create new request.
 */
static class Request {
    private final String data;

    public Request(String requestData) {
        this.data = requestData;
    }

    public String getData() {
        return data;
    }
}

 

코드에 주석 다 있으니까 무슨 일을 하는 건지 알고 싶으면 알아서 읽길.. (미래의 나에게 하는 말 ㅜ)

RequestHandler라는 interface에서 handle이라는 public method 한 개와, default method를 가진다. handle이라는 method를 lambda expression을 사용해서 재정의해놓았다. 그리고 combine method는 lambda expression을 return하고 있다! 이것은 combine의 반환형 자체가 functional interface라서, 어떠한 함수를 반환해야 하기 때문이다.

결정적으로 commonRequestHandler라는 객체?는 default method인 combine을 사용해 handle method를 정의하고 있다.

 

대충 내가 알아들은 대로 설명하긴 했는데 이게 맞는지도 모르겠다,,,

아 그리고 오늘 forEach도 배웠는데 뭔가 캐싱기,,,

 

IntPredicate isEven = x -> x % 2 == 0;
IntPredicate dividedBy3 = x -> x % 3 == 0;
IntPredicate pred = isEven.negate().or(dividedBy3);

// print all odd values and even values that can be divided by 3.
// numbers = {1,2,3,,,,,30}
numbers.forEach(val -> {
    if (pred.test(val)) System.out.print(val + " ");
});

 

넵 별거 없지만, 현재 numbers의 자식들 중 3으로 나눠지는 것만 출력되고 있다. 

forEach안에서 lambda Expression을 사용하고 있고, input은 elements들을 순회한다. 현재는 val이 그 역할 하는 중.

 

 

 

 

아 갑자기 topic 하나 넘어갔다고 개어려워졌다.

내가 설명 잘 못하고, 내가 아는 거 글로/ 말로 표현 못한다는 거 다시 깨닫는다.

자바 interface 자체를 좀 잘 알고싶다 ㅠㅅㅜ 엉엉

 

 

 

 

출처 : https://stepik.org/course/1595

 

Java. Functional programming

The course introduces elements of functional programming in Java 8. After completing this course, you should have a basic understanding of lambda expressions, functional interfaces, stream API, lazy evaluation, currying and monads.

stepik.org

 

'CS > Java' 카테고리의 다른 글

Java FP 5. Monads  (7) 2020.04.07
Java FP 4. Currying  (5) 2020.04.07
Java FP 3. Streams  (4) 2020.03.30
Java FP 1. Lambda expressions and method references  (14) 2020.03.26