람다식이 뭘까? 라고 고민을 해보면, 01장을 보신 분들은 메서드에 파라메터로 넘길 익명 클래스를 단순화 한 것이라고 생각할 수 있습니다. 맞습니다. 람다식을 이용하면 익명 클래스를 사용할 때 추가되는 자질구리한 코드를 넣을 필요가 없어집니다.

한번 람다의 특징부터 하나하나씩 간략하게 집어 가는 것을 시작으로 람다식이 먼지 알아보기로 합시다.

람다의 특징

  • 익명
    이름이 없으므로 익명이라는 특징을 갖습니다.
  • 함수
    특정 클래스에 종속되지 않고 기능만을 위주로 구현이 되어 있으니 함수적인 특징을 갖습니다.
  • 전달
    람다식을 람다식을 사용할 메소드에 파라메터로 넘길 수 있으니 전달이라는 특징을 갖습니다.
  • 간결성
    익명 클래스처럼 중복될만한 코드를 구현할 필요가 없으니 간결성이라는 특징을 갖습니다.

람다의 몸체

람다식은 간략하게 총 3부분으로 이루어져 있습니다.
  • 파라메터 리스트
    람다식 몸체에서 사용하는 파라메터를 정의 합니다.
  • 화살표
    파라메터 리스트와 람다식 몸체를 구분합니다.
  • 람다식 몸체
    파라메터 리스트의 정보를 통해서 기능을 구현하는 실제 로직이 들어가 있습니다.

/**
 * (Weapon weapon) : 파라메터 리스트
 * -> : 화살표
 * "USA".equals(weapon.getMaker()) : 람다식의 몸체
 */
(Weapon weapon) -> "USA".equals(weapon.getMaker());

여기까지가 람다식의 간략한 개요였습니다. 자 이제는 람다식을 어디에 어떻게 사용해야하는가? 하는 것에 대해서 알아봐야 하지 않을까요?

우선 그전에 예전 샘플을 보면 filterWeapons메소드의 2번째 파라메터는java.util.function.Predicate<T> 인터페이스형태 였습니다. 이 파라메터에 람다식을 넘겨서 동작함을 확인했습니다. 이 Predicate<T>를 함수형 인터페이스라고 부릅니다.

람다식을 파라메터로 전달하기 위해서는 이 함수형 인터페이스로 파라메터를 설정을 해주어야 합니다.

함수형 인터페이스

함수형 인터페이스는 오직 하나의 추상 메서드를 가지고 있습니다. 그리고 여러 디폴트 메서드(Default Method)를 가질 수 있습니다. 즉 많은 디폴트 메소드가 있어도 추상 메소드가 1개이면 이것은 함수형 인터페이스 입니다.

그리고 함수형 인터페이스 안에 오직 하나만 존재하는 추상 메서드의 형태랑 람다식의 형태는 그 형태가 동일 해야합니다. 왜냐하면 컴파일러는 람다식이 사용되는 곳에서 형식 검사라는 것을 하기 때문입니다.

형식 검사는 다음과 같습니다.

  • filterWeapons(...) 메서드을 확인합니다.
  • 두번째 파라메터로 Predicate<Weapon>이 들어 있음을 확인하고 이 형식을 분석합니다.
  • Predicate<Weapon>은 test라는 하나의 추상메소드를 가지고 있는것을 확인합니다.
  • test메소드는 Weapon을 받아서 boolean을 리턴하는 형식이라고 기억합니다.
  • filterWeapons(...) 두번째 파라메터로 위와 같은 형태의 람다식이 오지 않으면 오류를 발생시킵니다.

@FunctionalInterface
public interface Predicate {
 /**
  * Evaluates this predicate on the given argument.
  *
  * @param t the input argument
  * @return {@code true} if the input argument matches the predicate,
  * otherwise {@code false}
  */
 boolean test(T t); //Predicate의 하나 존재하는 test추상 메소드, 형식은 하나의 T(Weapon)을 받아서 boolean을 리턴한다.
 ...
}

...

(Weapon weapon) -> "USA".equals(weapon.getMaker());  // 람다식은 Weapon하나를 받아서 Boolean값을 리턴한다.

@FunctionalInterface는 함수형 인터페이스임을 뜻하는 어노테이션입니다. 만약 이게 선언되어있는데 함수형 인터페이스 구조가 아닐 경우 컴파일러가 오류를 발생 시킵니다.

위 코드는 Predicate의 test 추상메소드의 형태와 람다식의 형태가 동일하기에 Predicate를 파라메터로 받는 메소드 filterWeapons에서 저 람다식을 파라메터로 넘길 수 있습니다. 즉 형식검사를 통해 올바른 사용법이라고 결정이 난 것입니다.

함수형 인터페이스들

이제 어느정도 이해가 되실 것이라 생각이 듭니다. Java는 java.util.function.Predicate<T>같은 각각의 상황에 따른 함수형 인터페이스를 제공하고 있습니다.

java.util.function패키지 안에 대부분이 선언되어 있습니다. 궁금하시면 아래 링크를 통해서 살펴보시기 바랍니다.

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

지역변수사용

람다식 내부에서 외부 지역변수를 사용할 수 있습니다. 그런데 여기엔 제약조건이 있습니다. 람다식 내부에서 사용할 변수는 Final변수처럼 오직 딱 한번만 초기화 해야 합니다. 안그러면 컴파일러에서 오류를 발생합니다.

왜? 이런 제약조건이 있을까요?

저는 함수형 프로그래밍 때문이라고 생각이 듭니다. 함수형 프로그래밍의 개념은 일반적인 명령형 프로그래밍 패턴처럼 먼가 병렬화를 방해하는 프로그래밍 패턴이 아닌 오직 함수 본연의 순수한 기능을 추구하는 것이라 알고 있습니다.

그래서 람다식 내부에서는 외부의 변수를 변경해서는 안되며 오직 A를 넣으면 A에 대한 함수 본연의 기능만 해야 합니다. 이러한 기능을 위해 제약 조건이 걸렷다고 생각이 듭니다.

정리


  • 람다식은 이름은 없지만, 파라메터 리스트, 몸체, 둘을 구분하는 화살표를 가지고 있다.
  • 람다표현식으로 익명 클래스를 간략히 구현 할수 있다.
  • 람다표현식을 파라메터로 전달하기 위해선 함수형 인터페이스가 필요하다.
  • 함수형 인터페이스는 하나의 추상 메서드를 가진다. 그리고 여러 디폴트 메소드를 가질 수 있다.
  • java.util.funtion에는 자바에서 제공해주는 함수형 인터페이스들이 있다.