https://ebook-product.kyobobook.co.kr/dig/epd/ebook/480D220713760
자바 8 람다식 해설서는 총 2장으로 구성된 얇은 책이다. 1장에서는 람다식에 대한 해설을, 2장에서는 람다식을 사용하는 방법(스트림)에 대한 내용을 담고 있다.
이번에는 1장의 람다식에 대한 내용을 정리해본다. 람다식은 자바 8의 핵심적인 기능이다. 람다식을 통해 객체 지향 언어인 자바에서도 함수형 언어의 특징을 필요한 순간에 사용할 수 있게 됐다. 람다식이 무엇인지 살펴본다.
1. 람다식은 무엇인가?
람다식은 자바 8에 도입되어 화살표 연산자를 사용해서 기술한다. 화살표 연산자의 왼쪽에는 인수를, 화살표 연산자의 오른쪽에는 일련의 처리나 반환값을 기술한다. 이를 식으로 표현하면 다음과 같다.
(인수리스트) -> 일련의 처리나 반환값
인수와 반환값이라는 단어에서 람다식에서 메서드의 냄새가 난다. 실제로 람다식이 표현하는 것은 본질적으로 메서드 그 자체라고 할 수 있다.
(x, y) -> x + y
위에서 x와 y가 정수라고 가정한다면, 람다식이 표현하는 것은 다음과 같이 어떤 클래스 내부의 메서드가 된다.
class Sample {
public int calc(int x, int y) {
return x + y;
}
}
이렇게 람다식은 하나의 오브젝트를 표현하는 것처럼 보이지만, 사실은 개념적으로 함수를 표현하고 있다.
2. 함수?
자바에서는 인수를 받아 특정 작업을 수행하고, 그 결과를 반환하는 일련의 처리 묶음을 메서드라고 부른다. 동일한 처리 묶음을 다른 프로그래밍 언어에서는 함수라고 부르기도 한다.
따라서 람다식을 통해 자바라는 프로그래밍 언어에 함수라는 개념이 별도로 도입된 것이다. 자바에서 메서드와 함수에는 다음과 같은 뉘앙스 차이가 존재한다.
메서드
- 메서드는 클래스의 멤버이다.
- 메서드의 정의는 클래스 내부에서 진행된다.
- 클래스의 인스턴스를 통해 메서드에 접근이 된다.
- 따라서 메서드는 오브젝트 내부에서 존재하는 것이다.
함수
- 함수는 그 자체가 오브젝트다.
- 함수는 별도의 멤버가 없고 오로지 한 가지 처리만 표현한다.
이와 같은 뉘앙스 차이는 함수형 프로그래밍 언어에서 함수를 일급 객체로 취급하는 것으로부터 시작된다. 이는 곧 함수를 리터럴과 같이 취급하는 것이 가능한 오브젝트 그 자체라는 것을 의미한다.
String str = "ABC";
위의 자바 문자열 ABC
는 문자열 리터럴이다. 리터럴은 데이터 그 자체를 의미한다. 그리고 이 데이터는 String 클래스의 인스턴스로 치환된다. 따라서 자바의 문자열 클래스는 리터럴과 같이 취급할 수 있는 오브젝트가 된다.
함수형 언어에서는 함수도 이 관계와 동일하게 취급된다. 따라서 함수형 언어에서는 다음과 같은 행동이 가능하다.
- 변수에 함수를 대입할 수 있다.
- 함수의 인수에 함수를 건내는 것이 가능하다.
- 함수의 반환값으로 함수를 반환하는 것이 가능하다.
2,3번 조건을 만족하는 함수는 고계 함수라고 부른다.
고계 함수는 함수를 다루는 함수라는 의미를 가지고 있다.
람다식이 등장하기 전에는 자바의 변수에 메서드를 대입하거나, 인수로 메서드를 건내고, 메서드를 반환하는 것이 불가능 했다. 따라서 람다식의 등장과 함께 추가된 함수라는 개념은 메서드와는 다르다고 볼 수 있다.
자바 8의 람다식이 도입되었다고 해서 자바의 사양이 대규모로 변경된 것은 아니다. 함수형 언어의 일부를 개념적으로 적용을 한 것이고, 실제로는 백그라운드에서 컴파일러가 소스코드를 자동변환해주고 있을 뿐이다.
3. 함수형 인터페이스
따라서 람다식이 무엇인가에 대한 질문에 다음과 같은 대답을 할 수 있다.
- 개념적으로는 함수를 나타낸다.
- 현실적으로는 함수형 인터페이스를 구현한 익명 클래스를 간결하게 기술할 수 있도록 하는 것이다.
자바 8에서는
@FunctionalInterface
어노테이션이 추가되었다. 함수형 인터페이스인 것을 강조하기 위해 사용하며, 사용시 컴파일러가 해당 인터페이스가 하나의 추상 메서드 선언만 가지고 있는지 체크할 수 있게 된다.
그러면 람다식이 어떻게 익명 클래스를 간결하게 기술하는지 살펴보자. 자바 8에는 IntBinaryOperator
라는 함수형 인터페이스가 있다.
@FunctionalInterface
public interface IntBinaryOperator {
/**
* Applies this operator to the given operands.
*
* @param left the first operand
* @param right the second operand
* @return the operator result
*/
int applyAsInt(int left, int right);
}
applyAsInt
라는 추상 메서드 하나만 존재하며, 정수형 인수를 받아 계산을 하고 그 값을 반환하는 간단한 명세를 가지고 있다.
여기서 IntBinaryOperator
를 사용할 수 있는 방법들을 하나씩 살펴보자.
가장 먼저 생각해볼 수 있는 방법은 인터페이스를 구현한 클래스를 만드는 것이다.
public static class MyOperator implements IntBinaryOperator {
@Override
public int applyAsInt(int left, int right) {
return left + right;
}
}
public static void main(String[] args) {
MyOperator ops = new MyOperator();
System.out.println(ops.applyAsInt(1,2));
}
이와 같이 사용하는 것도 문제가 없는 방법이다. 하지만 MyOperator
와 같이 별도의 함수형 인터페이스 구현체를 만들지 않고, 익명클래스를 통해 함수형 인터페이스를 사용하는 것 역시 가능하다.
public static void main(String[] args) {
IntBinaryOperator ops = new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left + right;
}
};
System.out.println(ops.applyAsInt(1,2));
}
new IntBinaryOperator
뒤에 따라오는 {}
블록이 인터페이스의 추상 메서드를 오버라이딩한 익명 클래스의 구현 부분이 된다.
함수형 인터페이스를 구현한 익명클래스는 그저 인터페이스의 추상 메서드에서 명세한 인수와 그 반환값만을 나타내고 있으며, 다른 필드가 없다는 점에서 다른 프로그래밍 언어에서 함수를 표현하는 방식과 매우 유사해지게 된다.
따라서 람다식은 익명클래스의 이러한 구현부분을 아주 간단하게 기술할 수 있도록 도와주는 편리한 기능이 된다.
IntBinaryOperator ops = (x, y) -> x + y;
ops.applyAsInt(1,2);
람다식의 인수와 결과값에는 반환 타입을 기술하지 않는다. 이는 자바 8에서 타입 추론 기능이 강화되었기 때문에 가능하다.
타입 추론은 문맥으로부터 자바 컴파일러가 타입을 추리해주는 기능이다. 덕분에 람다식을 사용할 때 일일이 타입을 기술하지 않아도, 똑똑한 컴파일러가 타입을 정의해주게 된다.
컴파일러의 람다식 변환
람다식을 자바 컴파일러가 해석하는 과정은 다음과 같다.
IntBinaryOperator ops = (x, y) -> x + y;
먼저 자바 컴파일러는 대입연산자 우변에 람다식이 등장했기 때문에 타입 추론을 시작하게 된다. 좌변이 함수형 인터페이스이기 때문에 우변의 람다식 역시 함수형 인터페이스와 동일한 형태를 가지고 있을 것이 틀림없기 때문에 다음과 같이 해석을 한다.
IntBinaryOperator ops = new IntBinaryOperator();
여기서 인터페이스는 인스턴스로 만들 수 없기 때문에 익명클래스를 사용해서 구현을 할 것으로 다시 한 번 추리를 시작한다. 따라서 위의 식은 다음과 같이 해석된다.
IntBinaryOperator ops = new IntBinaryOperator() {
???
};
여기에서 함수형 인터페이스는 하나의 추상 메서드를 가지고 있기 때문에 오버라이딩 할 메서드가 ??? 영역에 만들어진다.
IntBinaryOperator ops = new IntBinaryOperator() {
public int applyAsInt(int x, int y) {
???
}
};
추상 메서드의 구현 부분은 람다식의 화살표 연산자 오른쪽에 구현된 부분을 참조하여 작성된다. 따라서 최종적으로 컴파일러는 람다식을 다음과 같이 해석한다.
IntBinaryOperator ops = new IntBinaryOperator() {
public int applyAsInt(int x, int y) {
return x+y;
}
};
따라서 자바 람다식의 본질은 함수형 인터페이스의 익명클래스를 이용한 구현을 간결하게 기술하는 것에 불과하다.
'프로그래밍 언어 > Java' 카테고리의 다른 글
자바 8 람다식 해설서 - 2장 스트림과 함수형 인터페이스 (0) | 2022.12.21 |
---|---|
서블릿 필터 (0) | 2022.07.12 |
서블릿 (0) | 2022.07.05 |
람다식 (0) | 2022.06.14 |