프로그래밍 언어/Java

람다식(Lambda Expressions)의 개념과 사용 방법 (JAVA)

제이온 (Jayon) 2021. 1. 1.

안녕하세요? 코딩 중독입니다.

 

오늘은 람다식의 개념과 함께 사용 방법을 알아보도록 하겠습니다.

 

 

람다식이란?

람다식은 메소드를 하나의 식으로 표현한 것을 말합니다. 그리고 람다식은 함수의 이름이 없기때문에 익명 함수라고도 합니다.  또한, 람다식은 메소드의 매개변수로 전달되거나 메소드의 결과로 반환될 수 있는 특징이 있어서 '힘수를 변수로' 다룰 수 있다는 장점이 있습니다.

 

 

람다식 기본 문법

람다식을 작성하는 방법은 아래와 같습니다.

 

 

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

 

 

'(타입 매개변수, ...)'는 오른쪽 중괄호 블록을 실행하기 위해 필요한 값을 제공하는 역할을 하며, '->' 기호는 매개 변수를 이용해서 중괄호 블록을 실행한다고 이해하면 됩니다.

 

만약, 어떠한 수(a)를 단순히 출력하는 메소드를 작성한다고 하면 아래와 같이 람다식으로 표현이 가능합니다.

 

 

(int a) -> { System.out.println(a); }

 

 

하지만, 매개 변수의 타입은 런타임 시에 대입되는 값에 따라 자동으로 인식될 수 있기때문에 매개 변수의 타입은 일반적으로 언급하지 않으며, 하나의 매개 변수만 존재할 때는 소괄호를 제거할 수 있습니다. 다만, 매개 변수가 없다면 소괄호를 반드시 붙여주어야 합니다.

 

 

a -> { System.out.println(a); }

 

 

또한, 중괄호 안에 실행문이 하나밖에 없다면, 중괄호도 제거가 가능합니다.

 

 

a -> System.out.println(a);

 

 

만약, 중괄호를 실행하고 결과값을 리턴해야한다면, 다음과 같이 return문을 작성하면 됩니다.

 

 

(x + y) -> { return x + y; };

 

 

이때, 중괄호 안에 return문만 있을 경우 중괄호와 return문을 제거하고 사용하는 것이 일반적입니다.

 

 

(x, y) -> x + y;

 

 

일반적으로 자바에서 메소드를 작성하려면, 접근 제한자와 static 유무, 리턴 타입, 함수 이름, 매개 변수를 적어 주어야 하였는데, 람다식을 통해서 짧은 코드로 표현할 수 있습니다. 그리고 이것이 람다식의 최대 장점입니다.

 

 

타겟 타입

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

 

그리고 이 객체의 타입은 다름 아닌 인터페이스입니다. "인터페이스 변수 = 람다식;"과 같이 선언하는 것이죠. 즉, 람다식은 인터페이스의 익명 구현 객체를 생성한다는 말이 됩니다.

 

인터페이스는 직접 객체화할 수 없기 때문에 구현 클래스가 필요한데, 람다식은 익명 구현 클래스를 생성하고 객체화합니다. 또한, 람다식은 대입될 인터페이스의 종류에 따라 작성 방법이 달라지기 때문에 람다식이 대입될 인터페이스를 람다식의 타겟 타입이라고 부릅니다.

 

 

함수적 인터페이스

그렇다면, 모든 인터페이스를 람다식의 타겟 타입으로 사용할 수 있을까요? 아닙니다. 람다식은 하나의 메소드를 정의하기 때문에 두 개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 이용해서 구현 객체를 생성할 수 없습니다. 따라서, 하나의 추상 메소드가 선언된 인터페이스만이 람다식의 타겟 타입이 될 수 있는데, 이러한 인터페이스를 함수적 인터페이스라고 부릅니다.

 

함수적 인터페이스를 정의하는 하나의 코드를 보겠습니다.

 

 

 

 

선언 방식은 우리가 원래 알고 있는 인터페이스의 형식과 크게 다르지 않습니다. 다만, 위에 어노테이션 표기가 낯설 것입니다.

 

이것은 함수적 인터페이스를 작성할 때 두 개 이상의 추상 메소드가 선언되지 않도록 컴파일러가 체킹해주는 기능입니다. 만약, 추상 메소드가 두 개 이상이 선언될 경우 컴파일 에러가 발생합니다.

 

 

람다식의 예제

위에서 설명한 람다식 기본 문법과 함수적 인터페이스를 사용한 예제를 살펴보겠습니다. 여기서 함수적 인터페이스는 바로 위에서 정의한 MyFunctionalInterface를 사용합니다.

 

 

(1) 매개 변수와 리턴값이 없는 람다식

 

 

매개 변수와 리턴값이 없을 경우 '( ) -> { }' 형태로 람다식을 기술하면 됩니다. 만약, 중괄호 안에 실행문이 하나일 경우 중괄호를 제거할 수 있습니다.

 

위와 같이 인터페이스 객체를 정의한 후, 객체가 가지고 있는 메소드를 호출하면 됩니다. 실행 결과는 "덧셈 함수입니다."가 나올 것입니다.

 

 

(2) 매개 변수가 있는 람다식

 

 

MyFunctionalInterface에서 sum 메소드에 매개 변수 int x와 int y를 추가해 줍니다.

 

그리고 메인 함수에서는 아래와 같이 인터페이스 객체를 정의해 주고, 객체가 가지고 있는 메소드를 호출하면 됩니다. 출력 결과는 "3 + 6 = 9"가 될 것입니다.

 

 

 

 

(3) 매개 변수와 리턴값이 있는 람다식

 

 

MyFunctionalInterface에서 sum 메소드에 리턴 타입을 int로 정의해 줍니다.

 

그리고 메인 함수에서는 아래와 같이 인터페이스 객체를 정의해 주고, 객체가 가지고 있는 메소드를 호출하면 됩니다. 출력 결과는 "9"가 될 것입니다.

 

 

 

 

중괄호 블록 안에 실행문이 return뿐 일 때는 위와 같이 중괄호와 return문을 동시에 생략이 가능하다고 하였습니다.

 

 

람다식에서의 지역 변수 사용

람다식에서 바깥 클래스의 필드는 메소드는 제한없이 사용할 수 있으나, 메소드의 매개 변수 또는 지역 변수를 사용하면 이 두 수는 final 특성을 가져야 합니다. 따라서 메소드의 매개 변수 또는 지역 변수를 람다식에서 읽는 것은 허용되지만, 람다식 내부 또는 외부에서 변경할 수 없습니다.

 

하나의 예시를 보겠습니다. 먼저, MyFunctionalInterface에는 매개 변수와 리턴값이 없는 read 메소드가 있다고 가정하겠습니다.

 

 

 

 

위와 같이 var를 40이라고 정의하였고, 람다식에서 이를 읽는 데 사용하고 있습니다. 하지만, var는 final이 선언되어있지만, final의 특징을 가져야 합니다. 위와 같이 var를 다른 값으로 변경하면 에러가 발생하는 것을 알 수 있습니다.

 

마찬가지로, 이 var 변수는 람다식 내부에서도 값을 변경할 수 없으며, 람다식의 매개 변수도 값을 변경할 수 없습니다.

 

 

주의 사항

위에서 매개 변수에 대해서 오해의 소지가 있을 것 같아서 첨언합니다. 특히, 바로 위의 내용에서 메소드의 매개 변수나 지역 변수의 값을 변경하면 안 된다고 하였습니다. 이때, 메소드의 매개 변수라는 것은 람다식의 매개 변수가 아니고, 람다식을 사용하는 메소드의 매개 변수를 말하는 것입니다.

 

 

정리

지금까지 람다식의 개념과 기본 사용 방법에 대해서 알아 보았습니다. 하지만, 우리는 의문이 하나 있습니다. 왜 람다식에서 메소드의 매개 변수나 지역 변수는 final 특징을 가져야 하는 것일까요?

 

이를 해결하기 위하여 JVM 메모리 구조와 함께 약 2개의 포스팅을 걸쳐서 설명하도록 하겠습니다.

 

 

참고 서적

이것이 자바다. - 저자 신용권

댓글

추천 글