Java - 람다식

이새봄·2022년 9월 11일

Java

목록 보기
11/11

이 글은 이것이 자바다를 참고하여 작성되었습니다.

람다식

: 익명 함수를 생성하기 위한 식

  • 자바 코드가 매우 간결해지고 컬렉션의 요소를 필터링하거나 매핑해서 원하는 결과를 쉽게 집계할 수 있다.
  • 람다식의 형태는 매개 변수를 가진 코드 블록이지만 런타임 시에는 익명 구현 객체를 생성한다.
Runnable runnable = new Runnable() {
    public void run() {...}
};
// Runnable 인터페이스의 익명 구현 객체를 생성하는 코드를 람다식으로 표현하면
Runnable runnable = () -> {...};
// 이렇게 된다.
  • 위 코드는 Runnable 변수에 대입되므로 람다식은 Runnable의 익명 구현 객체를 생성하게 된다.

람다식 기본 문법

(타입 매개변수,...) -> {실행문; ...}

  • 매개변수가 없다면 람다식에서 매개 변수 자리가 없어지므로 빈 괄호를 반드시 사용해야 한다.
  • 결과값을 리턴해야 한다면 return문으로 결과값을 지정할 수 있다.
    (x, y) -> {return x+y;};
    중괄호 안에 return문만 있을 경우에는 return문을 사용하지 않고 다음과 같이 작성하는 것이 정석이다.
    (x, y) -> x+y;

타겟 타입과 함수적 인터페이스

  • 람다식이 메소드 선언처럼 보이지만 자바는 메소드를 단독으로 선언할 수 없다. 람다식은 단순히 메소드를 선언하는 것이 아니라 이 메소드를 가지고 있는 객체를 생성해낸다.
    인터페이스 변수 = 람다식;
    람다식은 인터페이스 변수에 대입된다. 인터페이스는 직접 객체화할 수 없기 때문에 람다식은 익명 구현 클래스를 생성하고 객체화한다.
  • 람다식은 대입될 인터페이스의 종류에 따라 작성 방법이 달라진다. 람다식이 대입될 인터페이스를 람다식의 타겟 타입(target type)이라고 한다.

함수적 인터페이스(@FunctionalInterface)

  • 람다식은 하나의 메소드를 정의하기 때문에 두 개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 이용해 구현 객체를 생성할 수 없다.
  • 함수적 인터페이스 : 하나의 추상 메소드가 선언된 인터페이스
@FunctionalInterface
public interface MyFunctionalInterface {
	public void method();
}
public class MyFunctionalInterfaceExample {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyFunctionalInterface fi;
		fi = () -> {
			String str = "method call1";
			System.out.println(str);
		};
		fi.method();
		fi = () -> {System.out.println("method call2");};
		fi.method();
		// method() 호출은 람다식의 중괄호를 실행시킨다.
	}
}

클래스 멤버와 로컬 변수 사용

  • 람다식의 실행 블록에는 클래스의 멤버(필드와 메소드) 및 로컬 변수를 사용할 수 있다.
  • 클래스의 멤버는 제약 사항이 없지만 로컬 변수는 제약 사항이 있다.

클래스의 멤버 사용

  • 클래스의 멤버를 제약 사항 없이 사용할 수 있지만 this 키워드를 사용할 때 주의가 필요하다.
  • 익명 객체 내부에서 this는 익명 객체의 참조이지만 람다식에서 this는 내부적으로 생성되는 익명 객체의 참조가 아니라 람다식을 실행할 객체의 참조이다.

로컬 변수 사용

  • 람다식은 메소드 내부에서 주로 작성되기 때문에 로컬 익명 구현 객체를 생성시킨다.
  • 바깥 클래스의 필드나 메소드는 제한 없이 사용할 수 있으나 메소드의 매개 변수 또는 로컬 변수를 사용하면 이 두 변수는 final 특성을 가져야 한다. → 람다식에서 매개 변수 또는 로컬 변수를 읽는 것은 허용되지만 람다식 내부 또는 외부에서 변경할 수 없다.

표준 API의 함수적 인터페이스

Consumer 함수적 인터페이스
: 리턴값이 없는 accept() 메소드를 가지고 있다.

  • accept() 메소드는 단지 매개값을 소비하는 역할만 한다. 사용만 할 뿐 리턴값이 없다.

Supplier 함수적 인터페이스
: 매개변수가 없고 리턴값이 있는 getXXX() 메소드를 가지고 있다.

  • 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 한다.

Function 함수적 인터페이스
: 매개값과 리턴값이 있는 applyXXX() 메소드를 가지고 있다.

  • 매개값을 리턴값으로 매핑(타입 변환)하는 역할을 한다.

Operator 함수적 인터페이스
: 매개값과 리턴값이 있는 applyXXX() 메소드를 가지고 있다.

  • 매개값을 리턴값으로 매핑(타입 변환)하는 역할보다는 매개값을 이용해서 연산을 수행한 후 동일한 타입으로 리턴값을 제공하는 역할을 한다.

Predicate 함수적 인터페이스
: 매개값과 boolean 리턴값이 있는 testXXX() 메소드를 가지고 있다.

  • 매개값을 조사해서 true 또는 false를 리턴하는 역할을 한다.

andThen()과 compose() 디폴트 메소드

디폴트 및 정적 메소드는 추상 메소드가 아니기 때문에 함수적 인터페이스에 선언되어도 여전히 함수적 인터페이스의 성질을 가지고 있다.

  • 함수적 인터페이스의 성질 : 하나의 추상 메소드를 가지고 람다식으로 익명 구현 객체를 생성할 수 있는 것
  • Consumer, Function, Operator 종류의 함수적 인터페이스는 andThen()과 compose() 디폴트 메소드를 가지고 있다.
  • andThen()과 compose() 디폴트 메소드 : 두 개의 함수적 인터페이스를 순차적으로 연결하고 첫 번째 처리 결과를 두 번째 매개값으로 제공해서 최종 결과값을 얻을 때 사용한다.

andThen()
인터페이스AB = 인터페이스A.andThen(인터페이스B);
최종결과 = 인터페이스AB.method();

  • 인터페이스AB의 method()를 호출하면 우선 인터페이스A부터 처리하고 결과를 인터페이스B의 매개값으로 제공한다.
  • 인터페이스 B는 제공받은 매개값을 가지고 처리한 후 최종 결과를 리턴한다.

compose()
인터페이스AB = 인터페이스A.compose(인터페이스B);
최종결과 = 인터페이스AB.method();

  • 인터페이스AB의 method()를 호출하면 우선 인터페이스B부터 처리하고 결과를 인터페이스A의 매개값으로 제공한다.
  • 인터페이스 A는 제공받은 매개값을 가지고 처리한 후 최종 결과를 리턴한다.

  • Consumer 종류의 함수적 인터페이스는 처리 결과를 리턴하지 않기 때문에 andThen() 디폴트 메소드는 함수적 인터페이스의 호출 순서만 정한다.
  • Function과 Operator 종류의 함수적 인터페이스는 먼저 실행한 함수적 인터페이스의 결과를 다음 함수적 인터페이스의 매개값으로 넘겨주고 최종 처리 결과를 리턴한다.

0개의 댓글