람다식

4 분 소요

1.람다식이란?

자바는 객체지향 프로그래밍 소트웨어 개발의 주요 패러다임이었던 1990년대에 디자인되었다. 최근 들어 함수적 프로그래밍이 다시 부각되고 있는데, 병렬처리와 이벤트 지향 프로그래밍에 적합하기 때문이다. 그래서 객체 지향 프로그래밍과 함수적 프로그래밍을 혼합함으로써 더욱 효율적인 프로그래밍이 될 수 잇도록 프로그램 개발 언어가 변하고 있다.

람다식은 간단히 말해 함수를 변수처럼 사용할 수 있는 개념이다. 자바8 이전에는 메소드라는 함수 형태가 존재했지만, 객체를 통해서만 접근이 가능하고, 메소드 자체를 변수로 사용하지는 못했다. 자바8 에서는 함수를 변수처럼 사용할 수 있기 때문에, 파라미터로 전달할 수도 있고, 리턴값으로 함수를 받을 수도 있다.

자바 람다식은 완전한 함수형 프로그램방식이라고 할 수는 없다. 자바 람다식은 함수형에 대해서 새로 정의한 것이 아니고, 기존의 인터페이스를 이용하여 람다식을 표현하기 때문이다.

1.1. 람다식의 장점

  • 코드 라인 수가 감소한다.
  • 병렬 프로그래밍이 가능하다.
  • 메소드로 행동방식을 전달할 수 있다.
  • 의도의 명확성

1.2. 람다신의 단점

  • 람다식의 호출을 위해 직접 메소드를 호출해야 한다
  • 재귀 람다식이 까다롭다.
  • 클로저가 지원되지 않는다.
  • 함수 외부의 값을 변경한다.

2. 타켓 타입

람다식의 형태는 매개 변수를 가진 코드 블록이기 때문에 마치 자바의 메소드를 선언하는 것 처럼 보여진다. 자바는 메소드를 단독으로 선언할 수 없고 항상 클래스의 구성 멤버로 선언하기 때문에 람다식은 단순히 메소드를 선언하는 것이 아니라 이 메소드를 가지고 있는 객체를 생성해 낸다.

인터페이스 변수 = 람다식;

람다식은 인터페이스 변수에 대입된다. 이 말은 람다식은 인터페이스의 익명 구현 객체를 생성한다는 뜻이 된다. 인터ㅔ이스는 직접 객체화할 수 없기 때문에 구현 클래스가 필요한데, 람다식은 익명구현 클래스를 생성하고 객체화한다. 람다식은 대입될 인터페이스의 종류에 따라 작성 방법이 달라지기 때문에 람다식이 대입될 인터페이스를 람다식의 타켓 타입(target type)이라고 한다.

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

모든 인터페이스를 람다식의 타켓 타입으로 사용할 수는 없다. 람다식이 하나의 메소드를 정의하기 때문에 두 개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 이용해서 구현 객체를 생성할 수 없다. 하나의 추상 메소드가 선언된 인터페이스만이 람다식의 타켓 타입이 될 수 있는데, 이러한 인터페이스를 함수적 인터페이스(Functional Interface)라고 한다. 함수적 인터페이스를 작성할 때 두 개 이상의 추상메소드가 선언되지 않도록 컴파일러가 체킹해주는 기능이 있는데, 인터페이스 선언 시 @FunctionalInterface 어노테이션을 붙이면 된다. 이 어노테이션은 두 개 이상의 추상 메소드가 선언되면 컴파일 오류를 발생시킨다.

@FunctionalInterface 어노테이션은 선택사항이다. 이 어노테이션이 없더라도 하나의 추상 메소드만 있다면 모두 함수적 인터페이스이다. 그러나 실수로 두 개 이상의 추상 메소드를 선언하는 것을 방지하고 싶으면 붙여주는 것이 좋다. 람다식은 타켓 타입인 함수적 인터페이스가 가지고 있는 추상메소드의 선언 현태에 따라서 작성 방법이 달라진다.

3.1. 매개 변수와 리턴값이 없는 람다식

@FunctionalInterface
public interface MyFunctionalInterface {
    public void method();
}

MyFunctionalInterface fi = () -> {...}
fi.methid(); // 메소드 호출

3.2. 매개 변수가 있는 람다식

@FunctionalInterface
public interface MyFunctionalInterface {
    public void method(int x);
}

MyFunctionalInterface fi = (x) -> {...}
또는
MyFunctionalInterface fi = x -> {...}

fi.method(5); // 메소드 호출

3.3. 리턴값이 있는 람다식

@FunctionalInterface
public interface MyFunctionalInterface {
    public int method(int x, int y);
}

MyFunctionalInterface fi = (x,y) -> {...; return ;}
int return = fi.method(2, 5); // 메소드 호출

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

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

4.1. 클래스의 멤버 사용

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

4.2. 로컬 변수 사용

람다식은 메소드 내부에서 주로 작성되기 때문에 로컬 익명 구현 객체를 생성시킨다고 봐야 한다. 람다식에서 바깥 클래스의 필드나 메소드는 제한 없이 사용할 수 있으나, 메소드의 매개 변수 또는 로컬 변수를 사용하면 이 두 변수는 final 특성을 가져야 한다.(메소드 내에서 생성된 익명 객체는 메소드 실행이 끝나도 힙 메모리에 존재해서 계속 사용할 수 있다. 매개 변수나 로컬 변수는 메소드 실해잉 끝나면 스택 메모리에서 사라지기 때문에 익명 객체에서 사용할 수 없게 되므로 문제가 발생한다.) 따라서 매개 변수 또는 로컨 변수를 람다식에서 읽는 것은 허용되지만, 람다식 내부 또는 외부에서 변경할 수 없다.

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

자바에서 제공되는 표준 API에서 한 개의 추상 메소드를 가지는 인터페이스들은 모두 람다식을 이용해서 익명 구현 객체로 표현이 가능하다. 자바8부터 빈번하게 사용되는 함수적 인터페이스는 java.util.function 표준 API 패키지로 제공한다. 이 패키지에서 제공하는 함수적 인터페이스의 목적은 메소드 또는 생성자의 매개 타입으로 사용되어 람다식을 대입할 수 있도록 하기 위해서이다. 자바8 부터 추가되거나 변경된 API에서 이 함수적 인터페이스들을 매개 타입으로 많이 사용한다. java.util.funcation 패키지의 함수적 인터페이스는 크게 Consumer, Supplier, Function, Operator, Predicate로 구분된다. 구분 기준은 인터페이스에 선언된 추상 메소드의 매개값과 리턴값의 유무이다.

종류 특징 매개변수 유무 리턴 값
Consumer - 매개값은 있고, 리턴값은 없음 O X
Supplier - 매개값은 없고, 리턴값은 있음 X O
Function - 매개값도 있고, 리턴값도 있음
- 주로 매개값을 리턴값으로 매핑(타입 변환)
O O
Operator - 매개값도 있고, 리턴값도 있음
- 주로 매개값은 연산하고 결과를 리턴
O O
Predicate - 매개값은 있고, 리턴 타입은 boolean
- 매개값을 조사해서 true/false를 리턴
O boolean

5.1. Consumer 함수적 인터페이스

Consumer 함수적 인터페이스의 특징은 리턴값이 없는 accept() 메소드를 가지고 있다. accept() 메소드는 단지 매개값을 소비하는 역할만을 한다.

5.2. Supplier 함수적 인터페이스

Supplier 함수적 인터페이스의 특징은 매개 변수가 없고 리턴값이 있는 getXXX() 메소드를 가지고 있다. 이 메소드들은 실행 후 호출한 곳으로 데이터를 리턴(공급) 하는 역할을 한다.

5.3. Function 함수적 인터페이스

Function 함수적 인터페이스의 특징은 매개값과 리턴값이 있는 applyXXX() 메소드를 가지고 있다. 이 메소드들은 매개값을 리턴값으로 매핑(타입변환)하는 역할을 한다.

5.4. Operator 함수적 인터페이스

Operator 함수적 인터페이스는 Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있다. 하지만 이 메소드들은 매개값을 리턴값으로 매핑(타입 변환)하는 역할보다는 매개 값을 이용해서 연산을 수행한 후 동일한 타입으로 리턴값을 제공하는 역할을 한다.

5.5. Predicate 함수적 인터페이스

Predicate 함수적 인터페이스는 매개 변수와 boolean 리턴값이 있는 testXXX() 메소드를 가지고 있다. 이 메소드들은 매개값을 조사해서 true 또는 false를 리턴하는 역할을 한다.

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

디폴트 및 정적 메소드는 추상 메소드가 아니기 때문에 함수적 인터페이스에 선언되어도 여전히 함수적 인터페이스의 성질을 잃지 않는다. 여기서 함수적 인터페이스의 성질이란 하나의 추상 메소드를 가지고 있고, 람다식으로 익명 구현 객체를 생성할 수 있는 것을 말한다.

Consumer, Function, Operator 종류의 함수적 인터페이스는 andThen()과 Compose() 디폴트 메소드를 가지고 있다. andThen()과 compose() 디폴트 메소드는 두 개의 함수적 인터페이스를 순차적으로 연결하고, 첫 번째 처리 결과를 두 번째 매개값으로 제공해서 최종 결과값을 얻을 때 사용한다. andThen()과 compose()의 차이점은 어떤 함수적 인터페이스부터 먼저 처리하느냐이다.

Note: 해당 글은 이것이 자바다를 바탕으로 정리한 내용을 작성한 글입니다.

태그:

카테고리:

업데이트:

댓글남기기