[ch3] 람다 표현식 (2)

    3.5 형식 검사, 형식 추론, 제약

    3.5.1 형식 검사

    람다가 사용되는 context를 이용해 람다의 형식을 추론할 수 있음

    대상 형식: 어떤 context에서 기대되는 람다 표현식의 형식

    List<Apple> heavierThan150g = filter(inventory, (Apple apple)->apple.getWeight() > 150);

     

    형식 확인 과정

    1. filter 메서드의 선언을 확인한다.
    2. filter 메서드는 두 번째 파라미터로 Predicate<Apple> 형식을 기대한다. 여기서 Predicate<Apple>이 대상 형식이 된다.
    3. Predicate<Apple>은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스이다.
    4. test 메서드는 Apple을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
    5. filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다.

     

     

    3.5.2 같은 람다, 다른 함수형 인터페이스

    호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있음

    // 두 인터페이스 모두 제네릭 형식 T를 반환하는 함수를 정의하기 때문에
    // 람다 표현식 ()->42이 두 식에 모두 사용가능하다.
    Callable<Integer> c = ()->42;
    PrivilegedAction<Integer> p = ()->42;

     

    특별한 void 호환 규칙

    람다의 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환됨

    // Pedicate는 불리언의 반환값을 가짐
    Predicate<String> p = s->list.add(s);
    // Consumer는 void 반환값을 가짐
    Consumer<String> b = s->list.add(s);

     

     

    3.5.3 형식 추론

    자바 컴파일러는 람다 표현식이 사용된 context를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론함

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

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

    // 파라미터의 형식이 명시적으로 지정되지 않았으므로 형식 추론 ㅇ
    List<Apple> greenApples = filter(inventory, apple->GREEN.equals(apple.getColor())); 
    
    Comparator<Apple> c = (Apple a1, Apple a2)->a1.getWeight().compareTo(a2.getWeight()); // 형식 추론 X
    Comparator<Apple> c = (a1, a2)->a1.getWeight().compareTo(a2.getWeight()); // 형식 추론 ㅇ

     

     

    3.5.4 지역 변수 사용

    람다 캡쳐링: 외부에서 정의된 변수(자유변수) 사용 가능

    인스턴스 변수와 정적 변수를 자유롭게 캡쳐 가능

    람다에서 사용되는 지역 변수는 final로 선언되거나, 실질적으로 final처럼 취급되어야 함

    int portNumber = 1237;
    Runnable r = ()->System.out.println(portNumber); // ERROR: 사용된 변수가 final 취급되지 않음
    portNumber = 31337;

     

    지역 변수의 제약

    인스턴스 변수는 힙에 저장되고, 정적 변수는 스택에 저장됨

    람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행되면, 변수를 할당한 스레드가 사라져서 변수 할당이 사라졌는데도 람다를 실행하는 스레드에서 해당 변수에 접근하려 할 수 있음

    따라서 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공함. 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야한다는 제약이 생겼음

     

    클로저

    함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스

    클로저는 클러저 외부에 정의된 변수의 값에 접근하고, 값을 바꿀 수 있음

    Java8의 람다와 익명클래스는 클로저와 비슷한 동작을 수행함

     

     

    3.6 메서드 참조

    3.6.1 요약

    기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있음

    특정 메서드만을 호출하는 람다의 축약형

    가독성을 높일 수 있음

    // 기존 코드
    inventory.sort((Apple a1, Apple a2)->a1.getWegiht().compareTo(a2.getWeight()));
    
    // 메서드 참조 & java.util.Comparator.comparing
    inventory.sort(comparing(Apple::getWeight)); //Apple 클래스에 정의된 getWeight의 메서드 참조

     

    실제 메서드를 호출하는 것이 아님.

    Apple::getWeight → (Apple a) -> a.getWeight()를 축약한 것

    람다 메서드 참조 단축 표현
    (Apple apple)->apple.getWeight() Apple::getWeight
    ()->Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
    (str, i)->str.substring(i) String::substring
    (String s)->System.out.println(s) System.out::println
    (String s)->this.isValidName(s) this::isValidName

     

    메서드 참조를 만드는 방법

    ClassName::MethodName

    • 1) 정적 메서드 참조
      • ex) Integer::parseInt
    • 2) 다양한 형식의 인스턴스 메서드 참조
      • ex) String::length
    • 3) 기존 객체의 인스턴스 메서드 참조
      • ex) Transaction 객체를 할당받은 expensiveTransaction 지역 변수가 있고, Transaction 객체에는 getValue 메서드가 있다면, 이를 expensiveTransaction::getValue라고 표현할 수 있음

     

     

    3.6.2 생성자 참조

    ClassName::new

    클래스명과 new 키워드를 이용해서 기존 생성자의 참조를 만들 수 있음

    만들어진 생성자의 참조는 이와 일치하는 시그니처를 갖는 함수형 인터페이스 부분에서 사용됨

    // 두 식은 같음
    Supplier<Apple> c1 = Apple::new;
    Supplier<Apple> c1 = ()->new Apple();
    List<Integer> weights = Arrays.asList(7,3,4,10);
    List<Apple> apples = map(weight, Apple::new);
    public List<Apple> map(List<Integer> list, Function<Integer, Apple> f){
    	List<Apple> result = new ArrayList<>();
        for(Integer i:list){
        	result.add(f.apply(i));
        }
    	return result;
    }
    static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
    static{
    	map.put("Apple", Apple::new);
        map.put("Orange", Orange::new);
        // 등등
    }
    
    public static Fruit giveMeFruit(String fruit, Integer weight){
    	return map.get(fruit.toLowerCase()).apply(weight);
    }

     

     

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

    문제: 사과 리스트를 정렬

     

    1단계) 코드 전달

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

     

    2단계) 익명 클래스

    inventory.sort(new Comparator<Apple>() {
    	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()));
    import static java.util.Comparator.comparing;
    inventory.sort(comparing(apple -> apple.getWeight()));

     

    4단계) 메서드 참조 사용

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

     

     

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

    여러 개의 람다 표현식을 조합해서 복잡한 람다 표현식을 만들 수 있음

    이를 위해 함수형 인터페이스에서 디폴트 메서드를 제공함

    디폴트 메서드는 추상 메서드가 아니라서 함수형 인터페이스 정의에서 벗어나지 않음

     

    3.8.1 Comparator 조합

    역정렬

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

     

    Comparator 연결

    무게가 같은 두 사과가 존재한다면 어떤 사과를 먼저 나열할까?

    이를 위해  thenComparing 메서드를 사용해서 Comparator를 연결할 수 있음

    inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));

     

    3.8.2 Predicate 조합

    Predicate 인터페이스에서는 negate, and, or 세가지 메서드를 제공함

    // 빨간색이 아닌 사과
    Predicate<Apple> notRedApple = redApple.negate();
    
    // 빨간색이면서 무거운 사과
    Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150);
    
    // 빨간색이면서 무거운 사과이거나 녹색 사과
    Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(apple->apple.getWeight() > 150).or(apple -> GREEN.equals(apple.getColor()));

     

     

    3.8.3 Function 조합

    • Function 인터페이스에서는 andThen, compose 디폴트 메서드를 제공함
    • andThen: 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환
      • ex) f.andThen(g) → g(f(x))
    • compose: 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공함
      • ex) f.compose(g) → f(g(x))
    Function<String, String> addHeader = Letter::addHeader;
    Function<String, String> transformationPipeline = addHeader.andThen(Letter:checkSpelling).andThen(Letter::addFooter);

     

    댓글