01.기초

동적 파라미터화 코드 전달하기

자주 바뀌는 요구사항에 효과적으로 대응하자.

  • 동적 파라미터 : 아직은 어떻게 실행할지 결정하지 않은 코드 블록

  • 동작(코드)을 메서드 인수로 전달

Before

public static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if ("green".equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}

After

선택 조건을 결정하는 인터페이스를 정의하자. (전략 디자인 패턴)

  • 각 항목에 적용할 동작을 분리

public interface Predicate<T> {
    boolean test(T t);
}

public class weightPredicate implements Predicate {
    @Override
    public boolean test(T t) {
        return t.getWeight() > 150;
    }
}

public class colorPredicate implements Predicate {
    @Override
    public boolean test(T t) {
        return t.getColor() == Color.GREEN;
    }
}

public <T> List<T> filter(List<T> list, Predicate<T> p) {
    List<T> result = new ArrayList<>();
    for (T e : list) {
        if (p.test(e)) {
            result.add(e);
        }
    }
    return result;
}

//
List<Apple> greenApples = filter(inventory, new colorPredicate());

람다 표현식

메서드로 전달할 수 있는 익명 함수를 단순화한 것.

익명 함수의 일종이다 -> 이름은 없지만, 파라미터 리스트, 바디, 반환 형식을 가지고 예외를 던질 수 있다.

  • 람다 표현식의 구성

    • 파라미터 리스트

    • 화살표

    • 람다 바디

    (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
    '----람다 파라미터---화살표-------------람다 바디------------------'
        
    List<Apple> greenApples = filter(inventory, (Apple a) -> Color.GREEN.equals(a.getColor()));

함수형 인터페이스

  • 함수형 인터페이스 : 오직 하나의 추상 메서드만을 정의하는 인터페이스

    • 함수형 인터페이스를 기대하는 곳에서만 람다 표현식 사용 가능

  • 제네릭 파라미터에는 참조형(Byte, Integer, Object, List..)만 사용 가능

Predicate

  • 추상 메서드를 정의하여 제네릭 형식 T의 객체를 인수로 받아 Boolean 반환

    @FunctionalInterface
    public interface Predicate<T> {
        boolean test(T t);
    }
    
    public <T> List<T> filter(List<T> list, Predicate<T> p) {
        List<T> result = new ArrayList<>();
        for (T t : list) {
            if (p.test(t)) {
                result.add(t);
            }
        }
        return result;
    }
    
    Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
    List<String> nonEmpty = filter(listOfString, nonEmptyStringPredicate)

Consumer

  • 추상 메서드를 정의하여 제네릭 형식 T의 객체를 인수로 받아 void 반환 (특정 동작 수행)

    @FunctionalInterface
    public interface Consumer<T> {
        void accept(T t);
    }
    
    public <T> void forEach(List<T> list, Consumer<T> c) {
        for(T t : list) {
            c.accept(t);
        }
    }
    forEach(
        Arrays.asList(1,2,3,4,5),
        (Integer i) -> System.out.println(i);
    )

Function

  • 추상 메서드를 정의하여 제네릭 형식 T의 객체를 인수로 받아 제네릭 형식 R 객체를 반환 (입력을 출력으로 매핑할 경우)

    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
    }
    
    public <T, R> List<R> map(List<T> list, Function<T, R> f) {
        List<R> result = new ArrayList<>();
        for(T t : list) {
            result.add(f.apply(t));
        }
        return result;
    }
    
    // [7, 2, 6]
    List<Integer> l = map(
        Arrays.asList("lambdas", "in", "action"),
        (String s) -> s.length()
    );

람다, 메서드 참조 활용

1단계: 코드 전달

  • 객체 안에 동작을 포함시키는 방식으로 다양한 전략 전달

    • sort 동작은 파라미터화 되었다!

static class AppleComparator implements Comparator<Apple> {
    @Override
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight() - a2.getWeight();
    }
}
inventory.sort(new AppleComparator());

2단계: 익명 클래스 사용

  • 일회성이 있는 경우 익명 클래스를 이용하는 것이 좋다.

inventory.sort(new Comparator<Apple>() {
    @Override
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight() - a2.getWeight();
    }
});

3단계: 람다 표현식 사용

  • 함수형 인터페이스를 기대하는 곳 어디서나 람다 표현식을 사용할 수 있다.

// 1. Comparator 함수 디스크림터는 (T, T) -> int
inventory.sort((Apple a1, Apple a2) ->  a1.getWeight() - a2.getWeight());

//2. 자바 컴파일러는 람다 표현식이 사용된 콘텍스트를 활용해서 람다의 파라미터 형식을 추론
inventory.sort((a1, a2) -> a1.getWeight() - a2.getWeight());

//3. comparing 메서드 사용
import static java.util.Comparator.comparing;
inventory.sort(comparing(apple -> apple.getWeight()));

4단계: 메서드 참조 사용

  • 메서드 참조를 이용하면 람다 표현식의 인수를 더 깔끔하게 전달할 수 있다.

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

람다 표현식 조합 유용 메서드

Comparator

  • 정적 메서드 Comparator.comparing를 이용해서 비교에 사용할 키 추출

// 역정렬
inventory.sort(comparing(Apple::getWeight).reversed());

// Comparator 연결(thenComparing 메서드로 두 번째 비교자 만들기)
inventory.sort(comparing(Apple::getWeight)
               .reversed()
               .thenComparing(Apple::getContry));

Predicate

  • 복잡한 Predicate를만들 수 있도록 negate, and, or 세 가지 메서드 제공

// 기존 Predicate 객체 결과를 반전시킨 객체 생성 (negate)
Predicate<Apple> notRedApple = redApple.negate();

// 두 Predicate 를 연결해 새로운 Predicate 객체 생성
//(and)
Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150);
//(or)
Predicate<Apple> redAndHeavyOrGreen = redApple.and(apple -> apple.getWeight() > 150)
    										.or(apple -> GREEN.equals(a.getColor()));

Function

  • Function 인스턴스를 반환하는 andThen, compose 두 가지 default 메서드 제공

//andThen (주어진 함수 결과를 다른 함수의 입력으로 전달하는 함수 반환)
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1); // g(f(x)) -> 4

//compose (인수로 주어진 함수를 먼저 실행한 후 그 결과를 외부 함수의 인수로 제공)
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1); // f(g(x)) -> 3

Last updated