ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [모던자바인액션] chap 03 람다 표현식
    JAVA 2021. 2. 27. 22:47
    반응형

    람다 표현식

    람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것.

    람다의 특징

    • 익명
      보통의 메서드와 달리 이름이 없으므로 익명이라 표현. 구현해야 할 코드에 대한 걱정거리가 줄어듬.

    • 함수
      람다는 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부름. 하지만 메서드처럼 파라미터 리스트, 바디, 반환 형식, 가능한 예외 리스트를 포함.

    • 전달
      람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있음.

    • 간결성
      익명 클래스처럼 많은 자질구레한 코드를 구현할 필요가 없음.


    람다 표현식의 형태

    (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())

    • 파라미터 리스트
      Comparator의 compare 메서드 파라미터 (사과 두 개)

    • 화살표
      화살표(→)는 람다의 파라미터 리스트와 바디를 구분함.

    • 람다 바디
      두 사과의 무게를 비교함. 람다의 반환값에 해당하는 표현식.

    람다 예제

    불리언 표현식     (List<String> list) -> list.isEmpty()
    객체 생성         () -> new Apple(10)
    객체에서 소비     (Apple a) -> {
                System.out.println(a.getWeight());
              }
    객체에서 선택/추출 (String s) -> s.length()
    두 값을 조합       (int a, int b) -> a * b
    두 객체 비교       (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())

    함수형 인터페이스

    함수형 인터페이스는 정확히 하나의 추상 메서드를 지정하는 인터페이스.
    많은 디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스.
    함수형 인터페이스라는 문맥에서 람다 표현식을 사용할 수 있음.

    • @FunctionalInterface
      함수형 인터페이스임을 가리키는 어노테이션.
      @FunctionalInterface로 인터페이스를 선언했지만 실제로 함수형 인터페이스가 아니면 컴파일러가 에러를 발생시킴.

    함수 디스크립터 - 람다 표현식의 시그니처를 서술하는 메서드.


    다양한 함수형 인터페이스

    Predicate

    java.util.function.Predicate 인터페이스는 test라는 추상 메서드를 정의하며
    test는 제네릭 형식 T의 객체를 인수로 받아 불리언을 반환한다.

    Consumer

    java.util.function.Consumer 인터페이스는 제네릭 형식 T 객체를 받아서
    void를 반환하는 accept라는 추상 메서드를 정의함.

    Function

    java.util.function.Function<T, R> 인터페이스는 제네릭 형식 T를 인수로 받아서
    제네릭 형식 R 객체를 반환하는 추상 메서드 apply를 정의함.

    기본형 특화

    자바에는 기본형을 참조형으로 변환하는 기능인 박싱(boxing)을 제공함.
    참조형을 기본형으로 변환하는 반대 동작을 언박싱(unboxing)이라고 함.
    프로그래머가 편리하게 코드를 구현할 수 있도록 박싱과 언박싱이 자동으로 이루어지는 오토박싱(autoboxing)이라는 기능도 제공함.
    자바 8에서는 기본형을 입출력으로 사용하는 상황에서 오토박싱 동작을 피할 수 있도록 특별한 버전의 함수형 인터페이스를 제공함.

    IntPredicate, DoublePredicate, IntConsumer 등등


    형식 검사

    람다가 사용되는 콘텍스트를 이용해서 람다의 형식을 추론할 수 있다.
    어떤 콘텍스트에서 기대되는 람다 표현식의 형식을 대상 형식이라고 부름.

    대상 형식이라는 특징 때문에 같은 람다 표현식이더라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다.

    대상 형식으로 람다의 파라미터 형식도 추론할 수 있음.
    대상 형식을 이용해서 함수 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그니처도 추론할 수 있다.

    결과적으로 컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서 이를 생략할 수 있음.

    Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
    Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

    명시적으로 형식을 포함하는 것이 좋을 때도 있고 형식을 배제하는 것이 가독성을 향상시킬 때도 있음. 정해진 규칙은 없다!


    지역 변수 사용

    람다 표현식에서는 자유 변수(파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용할 수 있음.
    이와 같은 동작을 람다 캡쳐링이라고 부름.

    , 약간의 제약이 있음.

    람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡쳐할 수 있음.

    왜 제약이 있을까?

    지역 변수는 스택에 위치하기 때문.
    람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다.
    따라서 자바 구현에서는 원래 변수가 아닌 자유 지역 변수의 복사본을 제공하는데 이 값은 바뀌지 않아야 하므로 한 번만 값을 할당해야 한다는 제약이 생긴 것.


    메서드 참조

    기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있음.
    메서드명 앞에 구분자(::)를 붙이는 방식.

    • 정적 메서드 참조
      Integer::parseInt
    • 다양한 형식의 인스턴스 메서드 참조
      String::length
    • 기존 객체의 인스턴스 메서드 참조
      expensiveTransaction::getValue

    생성자 참조

    ClassName::new 처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 참조를 만들 수 있다.


    람다, 메서드 참조 활용하기

    • 1단계 : 코드 전달
    public class AppleComparator implements Comparator<Apple> {
        @Override
        public int compare(Apple a1, Apple a2) {
          return a1.getWeight().compareTo(a2.getWeight());
        }
      }
    
    invenotry.sort(new AppleComarator());

    sort의 동작은 파라미터화되었다.
    즉, sort에 전달된 정렬 전략에 따라 sort의 동작이 달라질 것이다.

    • 2단계 : 익명 클래스 사용
    inventory.sort(new Comparator<Apple>() {
          @Override
          public int compare(Apple a1, Apple a2) {
            return a1.getWeight().compareTo(a2.getWeight());
          }
        });
    • 3단계 : 람다 표현식 사용
    inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
    inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));

    자바 컴파일러는 람다 표현식이 사용된 콘텍스트를 활용해서 람다의 파라미터 형식을 추론한다.

    • 4단계 : 메서드 참조 사용
    inventory.sort(comparing(Apple::getWeight));

    단지 코드만 짧아진 것이 아니라 코드의 의미도 명확해짐.
    즉, 코드 자체로 'Apple을 weight별로 비교해서 inventroy를 sort하라'는 의미를 전달할 수 있음.


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

    함수형 인터페이스는 람다 표현식을 조합할 수 있도록 유틸리티 메서드를 제공함.
    여기서 등장하는 것이 바로 디폴트 메서드(추상 메서드가 아니므로 함수형 인터페이스의 정의를 벗어나지 않음)

    Comparator 조합

    inventory.sort(comparing(Apple::getWeight))
          .reversed() //무게를 내림차순으로 정렬
          .thenComparing(Apple::getCountry)); //두 사과의 무게가 같으면 국가별로 정렬

    Predicate 조합

    복잡한 프레디케이트를 만들 수 있도록 negate, and, or 세 가지 메서드 제공.

    Predicate<Apple> notRedApple = redApple.negate();
    //기존 프레디케이트 객체 redApple의 결과를 반전시킨 객체를 만든다.
    
    Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150);
    //두 프레디케이트를 연결해서 새로운 프레디케이트 객체를 만든다.
    
    Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(apple -> apple.getWeight() > 150)
    .or(apple -> GREEN.equals(a.getColor)));
    //프레디케이트 메서드를 연결해서 더 복잡한 프레디케이트 객체를 만든다.

    Function 조합

    andThen, compose 디폴트 메서드 제공.

    Function<Integer, Integer> f = x -> x + 1;
    Function<Integer, Integer> g = x -> x * 2;
    Function<Integer, Integer> h = f.andThen(g); //g(f(x))
    int result = h.apply(1); //4를 반환
    
    Function<Integer, Integer> f = x -> x + 1;
    Function<Integer, Integer> g = x -> x * 2;
    Function<Integer, Integer> h = f.compose(g); //f(g(x))
    int result = h.apply(1); //3을 반환

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

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

    728x90

    댓글

Designed by Tistory.