본문 바로가기
Java

토비의 봄을 담은 제네릭 2편 람다와 인터섹션

by Jordy-torvalds 2020. 3. 26.
반응형

시작하는 글

1편에서 설명했던 제네릭의 개념을 바탕으로 하여 람다와 인터섹션에 대해 학습해보자.


람다란?

람다 표현식은 익명 클래스와 익명 메소드, 함수형 인터페이스를 단순화하여 표현한 것이다.

public class LambdaAndIntersection { 

    public static void main(String[] args) { 
        hello(o -> o); 

        hello(o -> { 
            return o; 
        }); 

        hello( 
            new Function(){ 

                @Override 
                public Object apply(Object o) { 
                    return o; 
                } 
        }); 
    } 

    private static void hello(Function o) { 
    } 
} 

위 예제를 보면 hello 란 정적 메소드에 할당된 파라미터 들은 람다에 익숙하지 않은 분들께는 다소 이상해 보일 수 있다.

놀라운 것은 메인 메소드 내 hello 메소드의 파라미터는 외형만 다른 모두 같은 메소드 이다.

1번째 메소드와 3번째 메소드를 비교했을 때 코드가 정말 간견해진 것을 알 수 있다.

참고로 람다를 쓰게 되면 관련 라이브러리에 의해 클래스가 내부적으로 생성되게 된다.

이러한 특성을 가진 Function과 같은 인터페이스를 함수형 인터페이스라고 한다.


함수형 인터페이스

함수형 인터페이스는 default 메소드, 정적 메소드가 아닌 메소드가 하나 뿐인 인터페이스. @FunctionalInterface란 어노테이션을 붙이지 않아도 괜찮다. 아래는 가장 대표적인 함수형 인터페이스인 Function 이다.

 @FunctionalInterface 
public interface Function<T, R> { 

    R apply(T t); 

    default  Function<V, R> compose(Function<? super V, ? extends T> before) { 
        Objects.requireNonNull(before); 
        return (V v) -> apply(before.apply(v)); 
    } 


    default  Function<T, V> andThen(Function<? super R, ? extends V> after) { 
        Objects.requireNonNull(after); 
        return (T t) -> after.apply(apply(t)); 
    } 

    static  Function<T, T> identity() { 
        return t -> t; 
    } 
} 

위 인터페이스에는 4개의 메소드가 있지만, non-static, non-default 메소드는 하나 뿐이므로 함수형 인터페이스 인 것이다.


인터섹션(Intersection)

람다 사용시 여러 인터페이스 교차시킴으로써 쉽고 빠르게 함수를 구현하는 방법이다.

public class LambdaAndIntersection { 

    public static void main(String[] args) { 
        hello((Function & Serializable & Cloneable) o -> o); 
    } 

    private static void hello(Function o) { 
    } 
} 

위 코드 대로면 Fucntion과 Serializable 그리고 Cloneable의 기능이 교차된 함수가 탄생하게 되며, 여러 인터페이스를 교차시킬 때 non-static, non-default 메소드가 한 개 이기만 하면 된다.

public class LambdaAndIntersection { 

    public interface Hello { 
        default void hello() { 
            System.out.println("hello"); 
        } 
    } 

    public interface Hi { 
        default void hi() { 
            System.out.println("hi"); 
        } 
    } 

    public static void main(String[] args) { 
        run((Function & Hello & Hi) o -> o, s -> { 
            s.hello(); 
            s.hi(); 
        }); 
    } 

    private static  void run(T t, Consumer c) { 
        c.accept(t); 
    } 
} 

이 예제는 default 메소드를 가지는 Hello와 Hi 인터페이스를 Function과 인터섹션 한 예제다.

대략적인 흐름을 보면 Function, Hello, Hi 인터페이스의 모든 기능이 혼합된 Function o가 Consumer에 accept 되고, 그래서 컨슈머 내에서 그 기능을 활용한다.

앞서 말했 듯이 인터섹션은 인덕션이라는 과정을 거치는데 그 과정에서 non-static, non-default 메소드가 한 개면 된다.

public class LambdaAndIntersection { 

    public interface BestNinizMember  { 
        T who(); 
    } 

    public interface CallBestNinizMember extends BestNinizMember { 
        default void call() { 
            System.out.println("Best Niniz Member is : " + who()); 
        } 
    } 

    public static void main(String[] args) { 
        run((BestNinizMember & CallBestNinizMember) () -> "죠르디", s -> { 
            s.call(); 
        }); 
    } 

    private static  void run(T t, Consumer c) { 
        c.accept(t); 
    } 
} 

위와 같은 것도 가능하다.

끝판왕

아래 예제와 같은 인터섹션을 구현할 수 있으면 마스터했다고 봐도 무방하다.

한번 유사하게 구현 해보자.

public class LambdaAndIntersection { 

    public interface JordyProfileTemplate  { 
        T getName(); 
        void setName(T t); 
        T getRace(); 
        void setRace(T s); 
    } 
    static class JordyProfile implements JordyProfileTemplate { 
        String name; 
        String race; 

        @Override 
        public String getName() { 
            return name; 
        } 

        @Override 
        public void setName(String name) { 
            this.name = name; 
        } 

        @Override 
        public String getRace() { 
            return race; 
        } 

        @Override 
        public void setRace(String race) { 
            this.race = race; 
        } 

        public JordyProfile(String name, String race) { 
            this.name = name; 
            this.race = race; 
        } 
    } 

    public interface DelegateTo { 
        T delegate(); 
    } 

    public interface ForwardingJordyProfileTemplate  extends DelegateTo<JordyProfileTemplate>, JordyProfileTemplate { 
        default T getName(){ 
            return delegate().getName(); 
        } 
        default void setName(T t){ 
            delegate().setName(t); 
        } 
        default T getRace(){ 
            return delegate().getRace(); 
        } 
        default void setRace(T t){ 
            delegate().setRace(t); 
        } 

    } 

    interface Printable  extends DelegateTo<JordyProfileTemplate> { 
        default void print() { 
            System.out.println(delegate().getName() + "  " + delegate().getRace()); 
        } 

    } 

    interface Convertable  extends DelegateTo<JordyProfileTemplate> { 
        default void convert(Function<T,T> mapper) { 
            JordyProfileTemplate jordyProfileTemplate = delegate(); 

            delegate().setName(mapper.apply(jordyProfileTemplate.getName())); 
            delegate().setRace(mapper.apply(jordyProfileTemplate.getRace())); 
        } 
    } 

    public static void main(String[] args) { 
        JordyProfile jordyProfile  = new JordyProfile("jordy","dino"); 
        run((ForwardingJordyProfileTemplate & Printable & Convertable) ()->jordyProfile , o -> { 
            o.print(); 
            o.convert(s -> s.toUpperCase()); 
            o.print(); 
            o.convert(s->s.substring(0,2)); 
            o.print(); 
        }); 
    } 
    public static ,S> void run(T t, Consumer c){ 
        c.accept(t); 
    } 
}
반응형