[모던 자바 인 액션] chap03. 람다 표현식(2)

2023. 2. 24. 00:11Java

람다는 함수형 인터페이스의 인스턴스를 만들 수 있다. 람다 표현식 자체에는 람다가 어떤 함수형 인터페이스를 구현하는지의 정보가 포함되어 있지 않다. 따라서 람다 표현식을 더 제대로 이해하려면 람다의 실제 형식을 파악해야 한다.

람다 표현식의 형식 추론

List<Apple> heavierThan150g =
	filter(inventory, (Apple apple) -> apple.getWeight() > 150);
// 이 경우, filter 메서드는 Predicate<Apple>을 기대한다.
Object o = (Runnalbe) () ->
	{ System.out.println("Tricky example"); };
// 이렇게 캐스팅 할 수도 있다.
List<Apple> greenApples =
	filter(inventory, apple -> GREEN.equals(apple.getColor()));
    // apple에는 파라미터 형식을 명시적으로 지정하지 않았다.
// Apple apple이란 형식이 없어도 함수 디스크립터를 추론하기때문에, 
// 람다 시그니처를 추론한다.

- 상황에 따라 명시적으로 지정하는 것이 좋을수도, 좋지 않을수도 있다. 가독성을 향상 시킬 수 있는지는 개발자가 결정해야 한다.

 

지역 변수 사용

람다 캡처링 - 지금까지는 람다 표현식의 인수를 바디 안에서만 사용했다. 하지만 람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수를 활용할 수 있다.
int portNumber = 1337;
Runnable r = () ->
	System.out.println(portNumber);
// 외부의 portNumber 변수를 캡쳐한다.
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337;
// 가변하는 인자는 람다 캡처링이 불가능하다.
지역 변수의 제약 - 람다에서 참고하는 지역 변수는 final로 선언되거나, final처럼 불변해야한다. 인스턴스 변수는 힙에 저장되는 반면, 지역 변수는 스택에 위치한다. 람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다. 따라서 자바 구현에서는 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공한다. 따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴 것이다.

 

메서드 참조

// 기존의 정렬 코드
inventory.sort((Apple a1, Apple a2) ->
	a1.getWeight().compareTo(a2.getWeight()));

// 메서드 참조를 한 정렬 코드
inventory.sort(comparing(Apple::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) (String s)
-> this.isValidName(s)
System.out::println
 this::isValidName

 

메서드 참조를 만드는 방법

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

 

생성자 참조 - ClassName::new처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 참조를 만들 수 있다. 이것은 정적 메서드의 참조를 만드는 방법과 비슷하다. (어떻게 활용해야 할지 도저히 모르겠다.)
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
// Supplier의 get 메서드를 호출해서 새로운 Apple 객체를 만들 수 있다.

Function<Integer, Apple> c2 = Apple::new;
Apple a1 = c1.apply(110);
// Apple(Integer weight)라는 시그니처를 갖는 생성자는 
// Function 인터페이스의 시그니처와 같다.

List<Integer> weights = Arrays.asList(7, 3, 4, 10);
List<Apple> apples = map(weights, 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;
}
// 이렇게 Integer를 포함하는 리스트를 만들수도 있다.

3단원의 최종목표는 이와 같은 코드를 만드는 것이라고 한다.

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

실제 람다 활용하기

// List의 sort의 메서드 시그니처는 Comparator로 정의함.
inventory.sort(new Comparator<Apple>() {
	public int compare(Apple a1, Applea2) {
    	return a1.getWeight().compareTo(a2.getWeight());
    }
});

//위 코드를 메서드 참조, 람다식을 사용하면 이렇게 줄일 수 있다.
inventory.sort(Comparator.comparingInt(Apple::getWeight))

 

124페이지는 일찍일어나서 정리합시다 꼮!