들어가며

객체지향을 추구하던 Java는 8버전 부터 큰 변화를 시도하였습니다.
Java8 버전에 함수형 패러다임이 추가되었는데, 그 부분에서 가장 큰 역할을 하는 것이 람다와 함수형 인터페이스가 아닐까라고 생각합니다.
이번 포스팅에선 함수형 인터페이스의 선언과 자주 사용하는 인터페이스 몇가지를 소개드릴까 합니다.

다만, 이번 포스팅을 읽기전에 람다와 익명클래스에 관해 간단히 읽어 보시면 좋을거 같습니다!!
JAVACOFFEE :: Java Java8 - 람다 표현식 사용하기 | Lambda | 익명클래스


함수형 인터페이스 선언

함수형 인터페이스란 한개의 추상 메서드로 이루어진 인터페이스를 말합니다.
간단한 예제를 통해 알아보겠습니다.

@FunctionalInterface
public interface FunctionalInterfaceExample {
    void printMsg(String msg);
    //void printName(String name);

    default void defaultMethod() {
        System.out.println("defalut Method");
    }

    static void staticMethod() {
        System.out.println("static Method");
    }
}
  • @ FunctionalInterface를 선언하여 줍니다
  • 추상메서드는 하나만 선언할 수 있습니다
  • 두 개 이상시 컴파일 에러가 발생합니다
  • default, static 메서드는 사용 가능합니다

함수형 인터페이스 사용

생성한 함수형 인터페이스를 사용해보도록 하겠습니다.
먼저 Yunho.class

public class Yunho implements FunctionalInterfaceExample {
    @Override
    public void printMsg(String msg) {
        System.out.println(msg);
    }
}
public class FunctionMain {
    public static void main(String[] args) {
        FunctionalInterfaceExample yunhoInterface = new Yunho();
        yunhoInterface.printMsg("안녕하세요");
        yunhoInterface.defaultMethod();
        FunctionalInterfaceExample.staticMethod();
    }
}

위의 main()을 실행하게 되면
추상메서드, default 메서드, static 메서드가 아래와 같이 실행됩니다.

안녕하세요
defalut Method
static Method

자주 사용하는 함수형 인터페이스

자바에서 기본적으로 제공하는 함수형 인터페이스는 다음과 같은 것들이 있습니다.

Function<T,R> : <T> -> <R>
Consumer<T> : <T> -> Void
Predicate<T> : <T> -> Boolean
Supplier<T> : lazy evaluation

이외에도 다양한 인터페이스를 제공합니다. java.util.function (Java Platform SE 8 )

Predicate

T타입 인자를 받고 결과로 Boolean을 리턴합니다.

@FunctionalInterface
public interface Predicate<T> {
    boolean test(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);
    }
    @SuppressWarnings("unchecked")
    static <T> Predicate<T> not(Predicate<? super T> target) {
        Objects.requireNonNull(target);
        return (Predicate<T>)target.negate();
    }
}

주요 메서드로 test가 있고, and, or 등의 메서드를 제공합니다

public class PredicateExample {

    public static void main(String[] args) {
        Predicate<Integer> integerPredicate = num ->  num > 10;
        Predicate<Integer> integerPredicate1 = num -> num < 20;

        System.out.println(integerPredicate.test(5));
        System.out.println(integerPredicate.and(integerPredicate1).test(15));
    }
}

and는 Predicate를 파라미터로 받기 때문에 위와 같은 연산을 수행할 수 있습니다.

Consumer

T 타입의 객체를 인자를 받고 내부적으로 값을 연산합니다. 리턴값은 없습니다.

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

Accept, andThen 메서드를 제공합니다

public class ConsumerExample {

    public static void main(String[] args) {
        Consumer<List<Integer>> numberConsumer = list -> {
            for (int i = 0; i < list.size(); i++) {
                list.set(i, list.get(i) * list.get(i));
            }
        };
        Consumer<List<Integer>> printConsumer = list -> {
            for (Integer num : list) {
                System.out.println(num);
            }
        };
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
        numberConsumer.andThen(printConsumer).accept(numbers);
    }
}

andThen은 Consumer를 Parameter로 받기 때문에 복합 연산을 수행할 수 있습니다.

Supplier

인자를 받지 않고 T 타입을 리턴합니다.

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

get 메서드를 제공합니다

public class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }
}

public class SupplierExmaple {

    public static void main(String[] args) {
        Supplier<String> supplier= () -> "hello world";
        System.out.println(supplier.get());

        Supplier<Student> studentSupplier = () -> new Student("황윤호", 20);
        System.out.println(studentSupplier.get());
    }
}

Function

T타입의 인자를 받고, R타입의 객체를 리턴합니다.

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

apply 이외에 compose, andThen 등의 메서드를 제공합니다

public class FunctionExample {

    public static void main(String[] args) {
        Function<Integer, Integer> multiplyFunction = number -> number * number;
        System.out.println(multiplyFunction.apply(10));

        Function<Integer, Integer> minusFunction = number -> number - 10;
        Integer result = multiplyFunction.andThen(minusFunction).apply(10);
        System.out.println(result);
    }
}

마찬가지로 andThen을 이용하면 복합 연산을 이용할 수 있습니다


마치며

이번 포스팅에서는 함수형 인터페이스에 대하여 알아보았는데요, 개인적으로는 Java8에서 꽃은 람다, 스트림, 함수형인터페이스 라고 생각합니다. 그만큼 중요하고, 많이 쓰이고 이해가 어려운 부분이라, 이번 포스팅에서 간단한 이해를 하시고 실제로 많은 연습을 통해 익히시는 걸 권해드립니다 @.@


참고

모던 자바 인 액션 - YES24
Java8 - 함수형 인터페이스(Functional Interface) 이해하기
java-self-study/src/main/java/fi at master · yhmane/java-self-study · GitHub

블로그 이미지

yhmane

댓글을 달아 주세요