모던 자바 인 액션 4 : 람다 & 메서드 참조

minseok·2023년 4월 26일
0

메서드 참조란?

한 마디로 더 깔끔하게 행동를 전달하는 것

  • 기존 메서드 정의를 재활용 하여 람다처럼 전달할 수 있습니다.
  • 메서드 참조를 이용하면 때로는 람다를 사용하는 것보다 더 가독성이 좋을 수 있습니다.



같은 결과지만 다른 코드폼

Lambda Style
inventory.sort((Apple a1, Apple a2) -> a.getWeight().compareTo(a2.getWeight()));

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

📗 메서드 참조를 새로운 기능이 아니라 하나의 메서드를 참조하는 람다를 편리하게 표현할 수 있는 문법이다.



몇가지 예제를 추가적으로 확인합니다.(MR = Method Reference)

lambda : (Apple apple) -> apple.getWeight();
MR : Apple:getWeight

lambda : () -> Thread.currentThread().dumpStack()
MR : Thread.currentThread()::dumpStack

lambda : (str, i) -> str.substring(i)
MR : sString::substring

lambda : (String s) -> System.out.prlintln(s) (String s) -> this.isValidName(s)
MR : this::isValidName



여러 종류의 메서드 참조
1. 정적 메서드의 경우 String.length(), Integer.parseInt() 같은 것들이 존재
2. 인스턴스 메서드 (참조하고자 하는 메서드가 non static)
4. 생성자





여러 가지 상황에 메서드 참조를 사용해보기

생성자 참조 : 메서드 참조를 사용한 인스턴스 생성방식

public class Empty {
    public Empty() {} // 기본 생성자
    public Empty(Integer i) {} // 2번 생성자
    public Empty(String s1, String s2, String s3) {} // 3번 생성자
}

1. Empty Class의 기본 생성자를 참조
Supplier<Empty> emptySupplier = Empty::new;
Empty empty = emptySupplier.get();

2. Empty Class의 2번 생성자 참조
Function<Integer, Empty> emptyFunc = Empty::new;
Empty empty2 = emptyFunc.apply(100);

3. Empty Class의 2번 생성자를 참조, 2번 생성자의 디스크립터와 일치한다.
List<Empty> empties = numbers.stream()
                .map(Empty::new)
                .collect(Collectors.toList());
                
4. 3번 생성자를 사용하기 위한 Functional Interface 정의
@FunctionalInterface
public interface TriFunction<T, U, V, R> { 
	R apply(T t, U u, V v);
}

TriFunction<String, String, String, Empty> emptyTriFunc = Empty::new;
Empty empty1 = emptyTriFunc.apply("t1","u1","v1");

람다와 메서드 참조를 사용해서 사과를 정렬하는 코드를 개선하기

java 8 이전의 방식
직접 행동을 상속받은 클래스를 작성하며, 인스턴스를 생성하여 넘기는 방식입니다.

--- set up ---
사과 클래스
public class Apple {
    public Color color;
    public Apple(Color color) {
        this.color = color;
    }
    public Color getColor() { return color; }
    @Override
    public String toString() {
        return "Apple{" +
                "color=" + color +
                '}';
    }
}
 
사과 Comparator 클래스
public class AppleComparator implements Comparator<Apple> {
    @Override
    public int compare(Apple o1, Apple o2) {
        return o1.color.compareTo(o2.color);
    }
}

--- execute ---
inventory.sort(new AppleComparator());

익명 클래스 사용
이전 방식과 차이는 Comparator 구현위치의 차이입니다.
해당 방식도 8 이전의 방식이며 가독성이 떨어집니다.

inventory.sort(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.color.compareTo(o2.color);
            }
        });

람다 표현식 사용
람다 표현식을 사용하여 라인수가 크게 줄었습니다.
익명 클래스 보다는 람다 표현식이 훨씬 간결하여 추천합니다.

inventory.sort((Apple a1, Apple a2) -> a1.color.compareTo(a2.color));

메서드 참조 사용
람다 표현식 대신 사용할 수 있다면 좋은 선택지입니다.
더욱 간결해집니다.

inventory.sort(Comparator.comparing(Apple::getColor));






람다 표현식을 조합할 수 있는 유용한 메서드

Comparator 조합

정적 메서드 Comparator.comparing()를 사용하여 키를 추출하는 Function기반 Comparator 반환
Comparator.comparing(Object::geState).reversed()

thenComparing() 을 체이닝을 통해 행위를 조합할 수 있습니다.
Comparator.comparing(Object::getState).reversed().thenComparing(Object::getState)




Predicate 조합

Predicate 인터페이스는 복잡한 프레디케이트를 만들 수 있도록 negate, and, or 3가지 method를 제공한다.

predicate의 조건을 반전
Predicate notRedApple = redApple.negate();

predicate에 새로운 predicate를 조합
Predicate heavyAndRedApple = redApple.and(apple -> apple.getWeight > 150);




Function 조합

Function 인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메서드를 제공합니다.

andThen은 주어진 함수를 먼저 적용한 결과를 다른 합수의 입력으로 전달하는 함수를 반환한다.

수학으로 write(g(f(x)))로 표현
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);

compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외 부함수의 인수로 제공한다.

수학으로 f(g(x))로 표현
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);

유틸리티 메서드를 조합해서 다양한 변환 파이프라인을 만들어보겠습니다.

public static String addHeader(String text){ return "From Raoul, Mario and Alan" + text; }
public static String addFooter(String text){ return text + "From Raoul, Mario and Alan"; }
public static String checkSpelling(String text){ return text + "From Raoul, Mario and Alan"; }

Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader
	.andThen(Letter::checkSpelling)
	.andThen(Letter::addFooter);
transformationPipeline.apply("hi, Java");
profile
즐겁게 개발하기

0개의 댓글