본문 바로가기
Book

[독서 기록] 모던 자바 인 액션 3장 람다 표현식

by Renechoi 2023. 1. 6.

모던 자바 인 액션 3장 람다 표현식


 

람다 표현식은 파라미터, 화살표, 바디로 이루어진다. 

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

람다 파라미터 / 화살표 / 람다 바디 

 

- 89p 

 

여러가지 람다 표현식 

(String s) -> s.lenght() // String 형식의 파라미터 하나를 가지며 int를 반환한다. 

(Apple a) -> a.getWeight() > 150 // Apple 형식의 파라미터 하나를 가지면 boolean을 반환한다. 

(int x, int y) -> { System.out.prinln(x + y); } // int 형식의 파라미터 두 개를 가지며 리턴값이 없다. 

() -> 45 // 파라미터가 없으며 int 42를 반환한다. 

(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())) // Apple 형식의 파라미터 두 개를 가지며 int를 반환한다.

- 90p 

 

 

 

람다 예제 

불리언 표현식		(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())

- 91p 

 

 

 

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

 

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

- 92p

 

함수형 인터페이스로 뭘 할 수 있을까? 람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로 전체 표현식을 함수형 인터페이스의 인스턴스로 취급(기술적으로 따지면 함수형 인터페이스를 구현한 클래스의 인스턴스)할 수 있다. 

- 93p 

 

Runnable r1 = () -> System.out.println("hello"); 

Runnable r2 = new Runnable() { 
	public void run() {
    	System.out.println("hello")
    }
};

public static void process(Runnable r) {
	r.run();
}

- 94p 

 

 

 

동작을 적용하여 실행 어라운드 패턴을 구현한 예시 

String result = processFile(BufferedReader br) -> br.readLine() + br.readLine());

- 98p 

 

 

 

Predicate 인터페이스를 사용할 때 String 객체를 인수로 받는 람다 예시 

 

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

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

Predicate<String> nonEmptyStringPredicate = (String s) -> is.Empty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);

- 101p 

 

 

 

자바에서는 기본형을 참조형으로 변환하는 기능을 제공한다. 이 기능을 박싱이라고 한다. 참조형을 기본형으로 변환하는 반대 동작을 언박싱이라고 한다. 

- 103p 

 

 

 

람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수를 활용할 수 있다. 이와 같은 동작을 람다 캡쳐링이라고 부른다. 다음은 portNumber 변수를 캡처하는 람다 예제다. 

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);

- 113p 

 

 

메서드 참조 

 

기존 코드

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

다음은 메서드 참조와 java.util.Comparator.comparing을 활용한 코드다. 

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

- 115p 

 

 

명시적으로 메서드명을 참조함으로써 가독성을 높일 수 있다. 메서드 참조는 어떻게 활용할까? 메서드명 앞에 구분자(::)를 붙이는 방식으로 메서드 참조를 활용할 수 있다. 예를 들어 Apple::getWeight는 Apple 클래스에 정의된 getWeight의 메서드 참조다. 실제로 메서드를 호출하는 것은 아니므로 괄호는 필요없음을 기억하자. 결과적으로 메서드 참조는 람다 표현식 (Apple a) -> a.getWeight()를 축약한 것이다. 

- 115p 

 

 

1. 정적 메서드 참조 

- 예를 들어 Integer의 parseInt 메서드는 Integer::parseInt로 표현할 수 있다.

2. 다양한 형식의 인스턴스 메서드 참저 

- 예를 들어 String length 메서드는 String::length로 표현할 수 있다. 

3. 기존 객체의 인스턴스 메서드 참조

- 예를 들어 Transaction 객체를 할당받은 expensiveTransaction 지역 변수가 있고 Transaction 객체에는 getValue 메서드가 있다면, 이를 expensiveTransaction::getValue라고 표현할 수 있다. 

- 116p 

 

 

 

(args) -> ClassName.staticMethod(args) 

=> ClassName::staticMethod

 

 

(arg0, rest) -> arg0.instanceMethod(rest)

=> ClassName::instanceMethod

 

 

(args) -> expr.instanceMethod(args)

=> expr::instanceMethod

- 117p 

 

 

생성자 참조 

Supplier<Apple> c1 = Apple::new = ()->new Apple();

 

- 119p 

 

Apple(Integer weight)라는 시그니처를 갖는 생성자는 Function 인터페이스의 시그니처와 같다. 
=> 

Function<Integer, Apple> c2 = Apple::new; 
Apple a2 = c2.apply(110); 

=> 

Function<Integer, Apple> c2 = (weight) -> new Apple(weight); 
Apple a2 = c2.apply(110);

 

 

map 메서드를 이용해 Apple 생성자로 전달하기. 

 

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 ArrayListM<>();
    
    for (Ineger i:list){
    	result.add(f.apply(i));
    }
    return result;
}

- 119p 

 

 

 

다음과 같은 코드를 만들기 

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

 

1단계 : 코드 전달 

 

inventory.sort(new AppleComparator());

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

 

자바 api에 구현되어 있는 sort 메서드는 Comparator 객체를 인수로 받아 두 사과를 비교한다. 객체 안에 동작을 포함시키는 방식으로 다양한 전략을 전달할 수 있다. 

=> 'sort의 동작은 파라미터화되었다.'라고 말할 수 있다. 

 

2단계 : 익명 클래스 사용 

 

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

=> 한 번만 사용할 Comparator를 위 코드처럼 구현하는 것 보다는 익명 클래스를 이용하는 것이 좋다. 

 

 

 

3단계 : 람다 표현식 사용 

 

추상 메서드의 시그니처(함수 디스크립터라 불림)는 람다 표현식의 시그니처를 정의한다. Comparator의 함수 디스크립터는 (T, T) -> int다. 우리는 사과를 사용할 것이므로 정확히는 (Apple, Apple) -> int로 표현할 수 있다. 

 

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

inventory.sort(( a1,  a2) -> a1.getWeight().compareTo(a2.getWeight())

Comaparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());

=> 

 

inventory.sort(comparing(apple -> apple.getWEight());

 

4단계 : 메서드 참조 사용 

 

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

=> Apple을 weight별로 비교해서 inventory를 sort하라 ! 

 

- 121 ~ 122p 

 

 

Comparator 조합 

- reversed()

- thenComparing 

 

Predicate 조합 

Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150 );

- 124 ~ 125p 

 

Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> f = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g); // 수학적으로 g(f(x))를 표현

 

andThen이 아니라 f.compose(g)로 하면 수학적으로f(g(x))로 표현 

 

- 126p 

 

 

반응형