본문 바로가기
개발 언어/Java

[Java] 람다표현식의 조합과 Comparator, Predicate, Function 디폴트 메서드(Default method)

by 정권이 내 2021. 12. 16.

[Java8] 람다 표현식 조합, 디폴트 메서드

 

Java8 의 함수형 인터페이스들중 몇몇은 람다표현식을 조합한 유틸리티 메서드를 제공합니다.

람다 표현식의 조합이란 간단한 여러개의 람다 표현식을 조합하여 복잡한 람다표현식을 만드는 것입니다.

 

하지만 우리는 함수형 인터페이스는 단일 추상메서드(Single Abstract Method) 만을 가지는 인터페이스라고

배웠는데 별도의 유틸리티 메서드를 가진다고 하니 이해가 되지 않을수 있습니다.

 

여기서 말하는 유틸리티 메서드는 디폴트 메서드(Default Method) 입니다.

 

디폴트 메서드(Default Method)

Java8 에서 새로 추가된 기능중 하나인 디폴트 메서드는 default 키워드를 이용하여 생성합니다. 클래스의

인스턴스로 접근할수 있으며 인터페이스는 기본적으로 메서드의 내용을 구현할수 없지만 디폴트 메서드는

인터페이스 내부에 있는 메서드임에도 내용을 구현할수 있습니다.

 

디폴트 메서드가 생긴 이유는 하위 호환성 때문입니다. 어떤 인터페이스를 만들어서 사용하고 있는데

해당 인터페이스에 특정 동작을 수행해야 하는 메서드를 추가해야 될때 디폴트 메서드를 추가하면

단일 추상메서드 규칙에 걸리지 않으면서 메서드를 생성할수 있기 때문입니다.

 

1. Comparator 조합

지난번 포스팅에서 메서드 참조를 이용해서 정적메서드 Comparator.comparingsort 함수의 정렬

규칙으로 전달했었습니다.

Comparator<Apple> comparator = Comparator.comparing(Apple::getWeight);

 

역 정렬

기본적으로 Comparator.comparing 메서드는 오름차순으로 정렬이 되는데 반대인 내림차순으로 하려면

디폴트 메서드인 reverse 메서드를 사용하면 됩니다.

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class FunctionalTest {

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100));
            inventory.add(randApple);
        }

        inventory.sort(Comparator.comparing(Apple::getWeight).reversed());

        for (Apple apple : inventory)
            System.out.printf("Apple Weight : %d\n", apple.getWeight());

    }
}

 

[실행 결과]

Apple Weight : 233
Apple Weight : 213
Apple Weight : 196
Apple Weight : 196
Apple Weight : 169
Apple Weight : 163
Apple Weight : 156
Apple Weight : 132
Apple Weight : 114
Apple Weight : 106

 

Comparator 연결

사과를 무게순서대로 정렬할때 무게가 같은 사과들이 존재한다면 새로운 정렬 필터를 추가해야 합니다.

Comparator 를 연속해서 사용하면 되는데 두번째 부터의 정렬 조건은 thenComparing 메서드를 사용합니다.

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class FunctionalTest {

    private static final String[] AppleColors = new String[]{"RED", "GREEN", "YELLOW","BLACK","WHITE"};

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 5 + 100),
                    AppleColors[(int) (Math.random() * 5)]);

            inventory.add(randApple);
        }
		// weight로 정렬후 무게가 같다면 color로 다시한번 정렬
        inventory.sort(Comparator.comparing(Apple::getWeight).reversed().
                thenComparing(Apple::getColor));

        for (Apple apple : inventory)
            System.out.printf("Apple Order   %d %s\n", apple.getWeight(), apple.getColor());

    }
}

 

[실행 결과]

Apple Order   104 RED
Apple Order   102 GREEN
Apple Order   102 RED
Apple Order   102 RED
Apple Order   102 RED
Apple Order   102 WHITE
Apple Order   101 WHITE
Apple Order   100 GREEN
Apple Order   100 WHITE
Apple Order   100 YELLOW

 

2. Predicate 조합

Predicate는 특정 조건에 따라 boolean 값을 반환해주는 인터페이스 입니다. 특정 조건으로 만들어놓은

Predicate 객체에 대하여 negate, and, or 메서드를 사용할수 있습니다.

 

negate

default Predicate<T> negate() {
    return (t) -> !test(t);
}

negate는 만들어놓은 Predicate 객체가 가지고 있는 조건과 반대되는 조건을 필터링할때 사용합니다.

예제로 빨간색사과만 가지는 redApplePredicate 객체와 해당 객체에 반대되는 조건을 가지고있는

notRedApplePredicate 객체로 확인해보겠습니다.

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class FunctionalTest {

    private static final String[] AppleColors = new String[]{"RED", "GREEN", "YELLOW"};

    public static <T> List<T> forEach(List<T> list, Predicate<T> predicate){
        List<T> result = new ArrayList<>();
        for (T t : list){
            if(predicate.test(t))
                result.add(t);
        }

        return result;
    }

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100),
                    AppleColors[(int) (Math.random() * 3)]);

            inventory.add(randApple);
        }
        
        // redApplePredicate : 빨간색 사과만 필터링
        Predicate<Apple> redApplePredicate = (Apple apple) -> apple.getColor().equals("RED");
        // notRedApplePredicate : 빨간색 사과에 반대되는 필터링
        Predicate<Apple> notRedApplePredicate = redApplePredicate.negate();

        List<Apple> redApples = forEach(inventory, redApplePredicate);
        List<Apple> notRedApples = forEach(inventory, notRedApplePredicate);

        System.out.println("\n=============== RED APPLE ===============");

        for (Apple apple : redApples)
            System.out.printf("Apple Color %s\n", apple.getColor());

        System.out.println("\n=============== NOT RED APPLE ===============");

        for (Apple apple : notRedApples)
            System.out.printf("Apple Color %s\n", apple.getColor());

    }
}

 

[실행 결과]

=============== RED APPLE ===============
Apple Color RED
Apple Color RED
Apple Color RED

=============== NOT RED APPLE ===============
Apple Color YELLOW
Apple Color GREEN
Apple Color YELLOW
Apple Color GREEN
Apple Color GREEN
Apple Color YELLOW
Apple Color YELLOW

 

and

default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) && other.test(t);
}

and는 두가지 조건을 동시에 만족하는 경우에 사용할수 있습니다.

빨간색사과만 가지는 redApplePredicate 객체에 and 메서드를 사용해 무게가 200 이상인 사과를 필터링하는

예제 입니다.

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class FunctionalTest {

    private static final String[] AppleColors = new String[]{"RED", "GREEN", "YELLOW"};

    public static <T> List<T> forEach(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T t : list) {
            if (predicate.test(t))
                result.add(t);
        }

        return result;
    }

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100),
                    AppleColors[(int) (Math.random() * 3)]);

            inventory.add(randApple);
        }

        // redApplePredicate : 빨간색 사과만 필터링
        Predicate<Apple> redApplePredicate = (Apple apple) -> apple.getColor().equals("RED");
        // redHeavyApplePredicate : 빨간색 사과이면서 무게가 200이상
        Predicate<Apple> redHeavyApplePredicate = redApplePredicate.and((Apple apple) -> apple.getWeight() > 200);

        List<Apple> redHeavyApples = forEach(inventory, redHeavyApplePredicate);

        for (Apple apple : redHeavyApples)
            System.out.printf("Apple %s %d\n", apple.getColor(), apple.getWeight());


    }
}

 

or

default Predicate<T> or(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) || other.test(t);
}

or는 두가지 조건중 하나만 만족할때 사용할수 있습니다.

빨간색사과만 필터링하는 redApplePredicate 객체에 or 메서드를 사용해 빨간색 사과이거나 무게가 200 이상인

사과도 필터링하는 예제 입니다.

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;

public class FunctionalTest {

    private static final String[] AppleColors = new String[]{"RED", "GREEN", "YELLOW"};

    public static <T> List<T> forEach(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T t : list) {
            if (predicate.test(t))
                result.add(t);
        }

        return result;
    }

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100),
                    AppleColors[(int) (Math.random() * 3)]);

            inventory.add(randApple);
        }

        // redApplePredicate : 빨간색 사과만 필터링
        Predicate<Apple> redApplePredicate = (Apple apple) -> apple.getColor().equals("RED");
        // redHeavyApplePredicate : 빨간색 사과이면서 무게가 200이상
        Predicate<Apple> redHeavyApplePredicate = redApplePredicate.or((Apple apple) -> apple.getWeight() > 200);

        List<Apple> redHeavyApples = forEach(inventory, redHeavyApplePredicate);
        // Color 정렬
        redHeavyApples.sort(Comparator.comparing(Apple::getColor));

        for (Apple apple : redHeavyApples)
            System.out.printf("Apple %s %d\n", apple.getColor(), apple.getWeight());


    }
}

 

[실행 결과]

Apple GREEN 237
Apple RED 114
Apple RED 188
Apple RED 134
Apple RED 153
Apple YELLOW 209
Apple YELLOW 238

 

negete, and, or 조합

위 3가지 메서드는 서로 조합해서도 사용할수 있는데 계산은 왼쪽부터 순서대로 진행됩니다.

예제로 netege -> or -> and 순서대로 빨간색사과가 아니거나 무게가 200이상인 사과를 필터링하고

노란색 사과만 필터링 해보겠습니다.

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;

public class FunctionalTest {

    private static final String[] AppleColors = new String[]{"RED", "GREEN", "YELLOW"};

    public static <T> List<T> forEach(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T t : list) {
            if (predicate.test(t))
                result.add(t);
        }

        return result;
    }

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 15; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100),
                    AppleColors[(int) (Math.random() * 3)]);

            inventory.add(randApple);
        }

        System.out.println("=============== ORIGINAL APPLE ===============");
        inventory.sort(Comparator.comparing(Apple::getColor));
        for (Apple apple : inventory)
            System.out.printf("Apple %s %d\n", apple.getColor(), apple.getWeight());


        System.out.println("=============== FILTER APPLE ===============");

        Predicate<Apple> predicate1 = (Apple apple) -> apple.getColor().equals("RED");
        // ((not 빨간사과) || 무게 200이상) && 노란색
        Predicate<Apple> predicate2 = predicate1.negate().
                or((Apple apple) -> apple.getWeight() > 200).
                and((Apple apple) -> apple.getColor().equals("YELLOW"));

        List<Apple> filterApples = forEach(inventory, predicate2);
        // Color 정렬
        filterApples.sort(Comparator.comparing(Apple::getColor));

        for (Apple apple : filterApples)
            System.out.printf("Apple %s %d\n", apple.getColor(), apple.getWeight());


    }
}

 

[실행 결과]

=============== ORIGINAL APPLE ===============
Apple GREEN 219
Apple GREEN 151
Apple GREEN 144
Apple GREEN 153
Apple RED 231
Apple RED 201
Apple RED 142
Apple RED 225
Apple YELLOW 164
Apple YELLOW 112
Apple YELLOW 136
Apple YELLOW 138
Apple YELLOW 248
Apple YELLOW 138
Apple YELLOW 160
=============== FILTER APPLE ===============
Apple YELLOW 164
Apple YELLOW 112
Apple YELLOW 136
Apple YELLOW 138
Apple YELLOW 248
Apple YELLOW 138
Apple YELLOW 160

 

3. Function 조합

Function 인터페이스는 Function 인스턴스를 반환하는 andThen, compose 두개의 디폴트 메서드가 있습니다.

 

andThen

주어진 함수를 먼저 계산한 결과를 다른함수의 입력으로 전달하는 Function 인스턴스를 반환합니다.

 

세가지 Function f, g, h가 있고 f.andThen(g)Function f의 결과를 Function g의 입력으로 전달하는

의미입니다. 수학적인 수식으로 표현하면 아래와 같습니다.

 

[FunctionalTest.java]

Function f의 x+1 수식은 Function g의 x에 대입이 되고 최종적으로 Function h는 (x + 1) * 2 라는 수식을

계산하게 됩니다.

import java.util.function.Function;

public class FunctionalTest {

    public static void main(String[] args) {

        Function<Integer, Integer> f = x -> x + 1;
        Function<Integer, Integer> g = x -> x * 2;
        Function<Integer, Integer> h = f.andThen(g);

        Integer result = h.apply(1);
        System.out.printf("result : %d", result);

    }
}

 

[실행 결과]

result : 4

 

compose

andThen 메서드와는 반대로 생각하면 쉬운데 f.compose(g) 라고 했을때 Function g 의 결과값을

Function f 에 전달하게 됩니다.

 

[FunctionalTest.java]

Function g의 x * 2 수식은 Function f의 x에 대입이 되고 최종적으로 Function h는 (x * 1) + 1 라는 수식을

계산하게 됩니다.

import java.util.function.Function;

public class FunctionalTest {

    public static void main(String[] args) {

        Function<Integer, Integer> f = x -> x + 1;
        Function<Integer, Integer> g = x -> x * 2;
        Function<Integer, Integer> h = f.andThen(g);

        Integer result = h.apply(1);
        System.out.printf("result : %d", result);

    }
}

 

[실행 결과]

result : 3

 

 

참조 : http://www.kyobobook.co.kr/product/detailViewEng.laf?ejkGb=BNT&mallGb=ENG&barcode=9781617293566&orderClick=LAG&Kc=

반응형

댓글