카테고리 없음

Java 항해일지 - 15. 람다식

Youngbin Kim 2021. 3. 6. 00:19

목표

자바의 람다식에 대해 학습하세요.

학습할 것 (필수)

  • 람다식 사용법
  • 함수형 인터페이스
  • Variable Capture
  • 메소드, 생성자 레퍼런스

람다란 무엇인가?

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

 

람다의 특징 4가지

  1. 익명: 메서드 이름 없이 사용 가능해 메서드 이름 짓는 것에 대한 고민이 덜어진다.
  2. 함수: 특정 클래스에 종속되지 않는다.
  3. 전달: 람다 표현식 자체를 메서드의 인수로 전달하거나 변수로 저장할 수 있다.
  4. 간결성: 익명클래스보다 간결하다

 

람다표현식의 구성

( 람다 파라미터 ) -> { 람다 바디 }
  • 표현식 스타일
    • (parameters) -> expression
  • 블록 스타일
    • (parameters) -> { statements; }

 

람다 사용법

함수형 인터페이스

함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스이다. 우테코 로또 미션에서 만들었던 로또 생성 인터페이스를 예시로 들 수 있다.

public interface LottoGenerator { 
    int MIN_LOTTO_NUMBER = 1; 
    int MAX_LOTTO_NUMBER = 45; 
    int FROM_INDEX = 0; 
    
    Lotto generate(); // 추상 메서드가 오직 하나 
}

람다 표현식을 통해 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있어 람다 표현식 자체를 함수형 인터페이스의 인스턴스로 취급할 수 있다. 마찬가지로 로또 미션에서 예시를 가져왔다.

public static Lotto ofRandomLotto() { 
    // 람다 표현식으로 변경한 예제 
    LottoGenerator randomGenerator = () -> new Lotto(Arrays.asList(1,2,3,4,5,6)); 
    return randomGenerator.generate(); 
} 
    
public void makeRandomLottos(int autoAmount) { 
    for (int i = 0; i < autoAmount; i++) { 
        lottos.add(Lotto.ofRandomLotto()); 
    } 
}

 

함수 디스크립터

함수 디스크립터란 람다 표현식의 시그니처를 서술하는 메서드이다. Predicate, Consumer, Function, Supplier 등이 있다.

 

  • Predicate
    제네릭 형식 T의 객체를 인수로 받아 불리언을 반환한다.
Predicate<String> startsWithSeoul = (str) -> str.startsWith("Seoul"); 

System.out.println(startsWithSeoul.test("SeoulCity")); // true 출력

 

 

  • Consumer
    제네릭 형식 T의 객체를 인수로 받아 void를 반환하는 accept라는 추상 메서드를 정의한다.
Consumer<String> printCity = (city) -> System.out.println(city); 

printCity.accept("Seoul");

 

 

  • Function
    제네릭 형식 T의 객체를 인수로 받아서 제네릭 형식 R 객체를 반환하는 추상 메서드 apply를 정의한다.
Function<Integer, Integer> plus10 = (number) -> number + 10; 

System.out.println(plus10.apply(10)); // 20 출력

 

 

그 외 다양한 함수 디스크립터

  • () -> T : Suuplier
  • T -> T : UnaryOperator
  • (T, T) -> T : BinaryOperator
  • (T, U) -> T : BiPredicate<L, T>
  • (T, U) -> void : BiConsumer<T, U>
  • (T, U) -> R : BiFunction<T, U, R>

 

 

지역 변수 사용

람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처할 수 있으나 지역 변수는 명시적으로 final로 선언되어 있어야 하거나, final로 선언된 변수와 똑같이 사용되어야 한다. 다시 말해 한 번만 할당할 수 있는 지역 변수를 캡처(외부에서 정의된 변수를 사용하는 것)할 수 있다.

 

 

메서드 레퍼런스

메서드 참조를 이용하면 람다 표현식의 인수를 더 깔끔히 전달할 수 있다.

  1. 정적 메서드 참조 (Integer::parseInt)
  2. 다양한 형식의 인스턴스 메서드 참조 (String::length)
  3. 기존 객체의 인스턴스 메서드 참조 (LottoNumber::getNumber)

 

생성자 레퍼런스

ClassName::new와 같이 클래스명과 new 키워드를 이용해 기존 생성자의 참조를 만들 수 있다. 정적 메서드의 참조를 만드는 방법과 비슷하다.

 

 

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

Comparator 조합

reversed(), thenComparing() 등을 조합해 사용할 수 있다.

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

 

 

Predicate 조합

negate(), or(), and() 등을 조합해 사용할 수 있다.

redApple.and(apple -> apple.getWeight() > 150)
    .or(apple -> GREEN.equals(apple.getColor())); 

// 빨간 사과 중 무게가 150 넘는 사과 혹은 초록 사과 (오른쪽 연결)

 

 

Function 조합

andThen(), compose() 등을 조합해 사용할 수 있다.

Function<Integer, Integer> plus10 = (number) -> number + 10;
Function<Integer, Integer> multiply3 = (number) -> number * 3;

Function<Integer, Integer> multiply3AndPlus10 = plus10.compose(multiply3); 
System.out.println(multiply3AndPlus10.apply(4));  // 결과 22
        
        
Function<Integer, Integer> plus10AndMultiply3 = plus10.andThen(multiply3);
System.out.println(plus10AndMultiply3.apply(4);  // 결과 42

 

 

 

정리

  1. 람다 표현식은 익명 함수의 일종이다. 이름만 없을 뿐 파라미터 리스트, 바디, 반환형식을 가지며 예외도 던질 수 있다.
  2. 함수형 인터페이스하나의 추상 메서드만을 정의하는 인터페이스이다.
  3. 람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다.