[Java] Lambda - Functional Interface

반응형

이번 글에서는 functional interface에 대해 알아보도록 하겠습니다.

1. Functional Interface

functional interface란 lambda식에 사용될 수 있는 interface를 의미합니다.

이전 글에서 살펴 보았듯이, lambda에선 interface를 사용해 type을 정의할 수 있습니다.

이때 interface type으로 사용되는 interface는 반드시 한개의 'abstract' method만 선언되어 있어야 합니다.

// interface for lambda
interface MyLambda {
    void perform( );
}

java8부터는 interface 내부에 default & static 메서드를 직접 구현할 수 있도록 변경되었으므로.. functional interface는 반드시 1개의 'abstract' method로만 구성되어 있는지 확인해야합니다. 😅
++ java7까지는 모든 interface의 method는 abstract 메서드였으므로 굳이 해당사항을 확인할 필요는 없었습니다.

만약 기존에 functional interface로 사용하던 interface에 다른 사용자가 abstract method를 추가하면, 이는 더이상 functional interface의 기능을 수행할 수 없게됩니다.

// cannot use for lambda
interface MyLambda {
    void perform( );
    boolean check();
}

그 결과, 기존에 해당 interface를 사용한 lambda는 에러가 발생하게됩니다.

이를 방지하기위해 사용하는 @Annotation이 '@FunctionalInterface'입니다.

// alerting this interface is for lambda expression
@FunctionalInterface
interface MyLambda {
    void perform( );
}

'@FunctionalInterface'을 선언한 interface에 1개 이상의 abstract method를 정의할 경우 에러가 발생하게됩니다. 이를 통해 다른 사용자가 의도치않게 functional interface를 깨트려버리는 실수를 방지할 수 있습니다.

2. Exercise

lambda의 기본적인 사용법은 어느정도 이해된 것 같습니다. 😁

이제 Java에서 정의해 놓은 'Functional Interface'를 사용해 조금 더 간단하게 lambda 식을 작성하는 방법에대해 살펴보겠습니다.

2-1) hard coding

먼저 아래와 같은 예제가 있다고 가정하겠습니다.

public class Person {
    private String firstName;
    private String lastName;
    private int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public int getAge() {
        return this.age;
    }

    @Override
    public String toString() {
        return "Person [firstName= " + firstName + " , lastName=" + lastName + " , age=" + age + "]";
    }
}

Person 클래스를 정의하고

public class Exercise {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Charles", "Dickens", 60),
                new Person("Lewis", "Carroll", 42),
                new Person("Thomas", "Carlyle", 51),
                new Person("Charlotte", "Bronte", 45),
                new Person("Matthew", "Arnold", 39)
        );

        // step 1 : Sort list by last name
        Collections.sort(people, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getLastName().compareTo(o2.getLastName());
            }
        });

        // step 2 : Create a method that prints all elements in the list
        printAll(people);

        // step 3 : Create a method that prints all people that have last name beginning with C
        printLastNameBeginningWithC(people);

    }

    private static void printLastNameBeginningWithC(List<Person> people){
        for(Person p  : people){
            if(p.getLastName().startsWith("C")){
                System.out.println(p);
            }
        }
    }

    private static void printAll(List<Person> people){
        for(Person p  : people){
            System.out.println(p);
        }
    }
}

Person 들로 구성된 List를 정렬 & 출력 하는 예제입니다.

2-2) interface implements

위의 예제에서 가장 먼저 개선해야 될 부분은 hard coding된 메서드를 interface로 변경하는 것 입니다.

위의 함수에서 조건이 맞는지 확인하는 것과, 출력을 하는 부분은 해당 메서드에서 hard coding 하는 것이 아니라 해당 '기능'을 메서드의 파라미터로 전달받아 단순히 실행시키기만 하도록 변경하는 것이 바람직합니다.

이를 통해 '기능'이 변경되더라도 해당 메서드는 수정없이 사용할 수 있게됩니다.

다음과 같이 변경할 수 있습니다.

public class Exercise {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Charles", "Dickens", 60),
                new Person("Lewis", "Carroll", 42),
                new Person("Thomas", "Carlyle", 51),
                new Person("Charlotte", "Bronte", 45),
                new Person("Matthew", "Arnold", 39)
        );

        // step 1 : Sort list by last name
        Collections.sort(people, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getLastName().compareTo(o2.getLastName());
            }
        });

        // step 2 : Create a method that prints all elements in the list
        printConditionally(people, new Condition() {
            @Override
            public boolean test(Person p) {
                return true;
            }
        }, new Action() {
            @Override
            public void perform(Person p) {
                System.out.println(p);
            }
        });
        ;

        // step 3 : Create a method that prints all people that have last name beginning with C
        printConditionally(people, new Condition() {
            @Override
            public boolean test(Person p) {
                return p.getLastName().startsWith("C");
            }
        }, new Action() {
            @Override
            public void perform(Person p) {
                System.out.println(p);
            }
        });

    }

    private static void printConditionally(List<Person> people, Condition condition, Action action) {
        for (Person p : people) {
            if (condition.test(p)) {
                action.perform(p);
            }
        }
    }
}

interface Condition {
    boolean test(Person p);
}

interface Action {
    void perform(Person p);
}

printAll과 printLastNameBeginningWithC는 결국, Condition에 따라서 Person을 출력하는 기능이므로 'printConditionally' 함수로 합칠 수 있습니다.

또한, 메서드의 파라미터 값으로 Condition Interface와 Action Interface를 전달받고, 해당 메서드는 전달받은 '기능'을 실행하도록 변경할 수 있습니다.

2-3) lambda

이번에는 위의 코드를 lambda 로 변경해보겠습니다.

lambda를 사용하면 아래와 같이 더욱 간결하게 코드를 작성할 수 있습니다.

public class Exercise {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Charles", "Dickens", 60),
                new Person("Lewis", "Carroll", 42),
                new Person("Thomas", "Carlyle", 51),
                new Person("Charlotte", "Bronte", 45),
                new Person("Matthew", "Arnold", 39)
        );

        // step 1 : Sort list by last name
        Collections.sort(people, (o1, o2) -> o1.getLastName().compareTo(o2.getLastName()));

        // step 2 : Create a method that prints all elements in the list
        printConditionally(people, p -> true, (p)-> System.out.println(p));

        // step 3 : Create a method that prints all people that have last name beginning with C
        printConditionally(people, p -> p.getLastName().startsWith("C"), (p)-> System.out.println(p));

    }

    private static void printConditionally(List<Person> people, Condition condition, Action action) {
        for (Person p : people) {
            if (condition.test(p)) {
                action.perform(p);
            }
        }
    }
}

interface Condition {
    boolean test(Person p);
}

interface Action {
    void perform(Person p);
}

2-4) java's functional interface

마지막으로 java의 functional interface를 사용해 위의 코드를 개선할 수 있습니다.

java8은 개발자들이 functional interface를 직접 정의하는 번거로움을 덜어주기위해 java.util.function 패키지에 자주 사용되는 functional interface를 작성해놓았습니다.

위의 functional interface들은 generic으로 작성되었기 때문에, custom class에도 적용해 사용할 수 있습니다.

이를 사용하면 위에서 직접 정의한 Condition & Action interface를 대체해 사용할 수 있습니다.

링크는 이곳 😎

우리가 사용할 functional interface는 다음과 같습니다.

Predicate<T> : 1개의 input 값을 전달받고 boolean 값을 return 한다.

Predicate interface의 test 함수를 사용하면 Condition interface를 대체할 수 있습니다.

Consumer<T> : 1개의 input 값을 전달받고 아무것도 return 하지 않는다.

Consumer interface의 accpet 함수를 사용하면 Action interface를 대체할 수 있습니다.

최종적으로 작성한 코드는 다음과 같습니다.

public class Exercise {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Charles", "Dickens", 60),
                new Person("Lewis", "Carroll", 42),
                new Person("Thomas", "Carlyle", 51),
                new Person("Charlotte", "Bronte", 45),
                new Person("Matthew", "Arnold", 39)
        );

        // step 1 : Sort list by last name
        Collections.sort(people, (o1, o2) -> o1.getLastName().compareTo(o2.getLastName()));

        // step 2 : Create a method that prints all elements in the list
        printConditionally(people, (p) -> true, p-> System.out.println(p));

        // step 3 : Create a method that prints all people that have last name beginning with C
        printConditionally(people, p -> p.getLastName().startsWith("C"), (p)-> System.out.println(p));

    }

    private static void printConditionally(List<Person> people, Predicate<Person> condition, Consumer<Person> action) {
        for (Person p : people) {
            if (condition.test(p)) {
                action.accept(p);
            }
        }
    }
}

처음보다.. 훨씬~ 간결해졌네요 👏👏👏

이외에도 정의된 functional interface를 사용하면 손쉽게 lambda 를 사용할 수 있습니다. 물론.. customize한 interface도 반드시 필요할 것 입니다.


추천서적

 

이것이 자바다:신용권의 Java 프로그래밍 정복

COUPANG

www.coupang.com

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음


반응형

'Java' 카테고리의 다른 글

[Java] Lambda - Closure  (0) 2020.08.16
[Java] Lambda - Exception Handling  (0) 2020.08.16
[Java] Lambda - Using Lambda  (0) 2020.08.16
[Java] Lambda - Understanding Lambda  (0) 2020.08.16
[Java] Multithreading - Thread Pool & Executor Service  (0) 2020.08.16

댓글

Designed by JB FACTORY