java8 - 람다식(Lamdba Expressions) 개념정리

람다식이란

이 람다식은 수학자 알론조(Alonzo Church)가 발표한 람다 계산법에서 사용된 식으로, 이를 제자 존 매카시(John Macarthy)가 프로그래밍 언어에 도입했다.

Java8 버전부터 이 림다식(Lamdba Expressions)을 지원하게 되었다.

람다식은 익명함수(anonymouse function)을 생성하기 위한 식으로 객체 지향 언어보다는 함수 지향 언어에 가깝다.

람다식의 형태는 매개변수를 가진 코드 블록이지만, 런타임 시에는 익명 구현 객체(추상메소드를 한개 포함한)를 생성한다.

람다식 -> 매개 변수를 가진 코드 블록 -> 익명 구현 객체

위 설명처럼 람다식이란 일종의 함수형 프로그래밍에 적합한 문법적 표현방식이다. 그리고 이 람다 문법은 자바 뿐만아니라 스칼라와 같은 다른 언어에서도 지원하고 있다.

함수형 프로그래밍은 병렬처리와 이벤트 지향 프로그래밍에 적합하며, 딥러닝이나 빅데이터와 더불어 일종의 문법적 트렌드처럼 관심을 받고 있다.

Java에서도 이런 트렌드를 따라갈 필요를 느낀것 같으며, 람다식은 그 문법적 간결성으로 기존의 자바 문법보다 쉽게 함수를 표현할수 있다.

그러나 람다 문법은 기존 java 문법과 확연히 다른 형태를 띠고 있기때문에, 기존 java 개발자는 많은 생소함을 느낄 수도 있다.

람다식은 결국 로컬 익명 구현객체를 생성하게 되지만, 이 람다식의 사용 목적은 인터페이스가 가지고 있는 메소드를 간편하게 즉흥적으로 구현해서 사용하는 것이 목적이다.

만약 한개의 추상메소드를 갖는 인터페이스가 있을때, 이 추상메소드를 구현해서 사용하기 위해서는 다음과 같은 방법이 있을 것이다.

1.인터페이스를 직접 클래스로 구현해서 메소드를 호출

인터페이스를 클래스로 구현해서 객체를 생성하여 메소드를 호출

인터페이스를 구현한 클래스를 재사용할 수 있는 장점이 있지만, 재사용이 필요하지 않는 메소드를 만들어 사용해야 할때도 있을 것이다. 그런 것들을 클래스로 구현하게 된다면 불필요한 class 파일이 늘어만 가게 될 것이다.

2.인터페이스를 익명구현객체로 구현해서 메소드를 호출

익명구현객체를 이용한 인터페이스 구현

위 처럼 익명 구현객체를 사용하게 된다면, 불필요한 클래스파일이 생기는 것도 막을 수 있고 프로그래밍 과정도 1번보다는 간결해진다.

3.람다식을 이용해 더 간결하게 인터페이스 구현하기

람다식을 이용한 인터페이스 구현

2번보다 훨씬더 간결하게 인터페이스를 구현할 수 있다.


함수적 인터페이스와 람다식 기본 문법

람다식을 사용하기 위해서는 일단 람다식으로 구현할 인터페이스가 필요하다.

이 인터페이스에는 조건이 하나있는데 한개의 추상메소드만 가지고 있어야 한다는 것이다.

이렇게 에러가 난다.

위 예제를 봐서 알겠지만 람다표현식은 인터페이스의 한개의 메소드밖에 구현 할 수 없다.

그리고 이런 람다식으로 구현할 인터페이스르 미리 함수적 인터페이스(@functionalInterface)라고 부른다.

즉 함수구현 전용 인터페이스라고 부르는 것이다.

그리고 @FunctionalInterface 어노테이션으로 이런 함수적 인터페이스를 명시 할수 있다.

@FunctionalInterface인터페이스가 적용된 인터페이스는 한개의 추상메소드만 선언 할수 있게 된다.

@FunctionalInterface가 선언된 인터페이스에 추상메소드가 1개가 아니면 에러가 발생한다.

람다식의 기본구조는 다음과 같다.

소괄호에는 구현한 함수의 인자를 그리고 화살표 다음에 중괄호에는 구현 할 함수 몸체를 넣으주면 된다.

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

하지만 함수를 간편하고 쉽게 표현하기 위해서 람다는 많은 생략 기법을 사용한다.

  • 람다식 매개인자의 자료형은 생략가능 하다.

  • 람다식의 매개인자가 한개인 경우 매개인자를 감싸는 소괄호를 생락 할 수 있다.

  • 람다식의 함수몸체에 실행문이 한개인 경우 함수의 몸체를 감싸는 중괄호를 생략 할 수 있다.

  • 람다식의 함수몸체에 실행문이 한개이고, 그 실행문이 return문일 경우 함수의 몸체를 감싸는 중괄호와 return을 생략 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
InterfaceA1 a1 = (int a) -> { System.out.println("a:" + a); };
InterfaceA1 a2 = (a) -> { System.out.println("a:" + a); }; // 매개인자 자료형 생략

InteafeceB1 b1 = (int a, int b) -> { System.out.println("a+b:" + (a + b) ); };
InteafeceB1 b2 = (a, b) -> { System.out.println("a+b:" + (a + b) ); }; // 매개인자 자료형 생략

InterfaceA1 a3 = a -> { System.out.println("a:" + a); }; //매개인자가 하나뿐이라 소괄호 생략
InterfaceA1 a4 = a -> System.out.println("a:" + a); //함수의 실행문이 한개라 중괄호를 생략

TestInterface t3 = a -> { return "a:" + String.valueOf(a); };
TestInterface t4 = a -> "a:" + String.valueOf(a); // 함수의 실행문이 한개이며, 리턴문만 있을경우 중괄호와 더불어 return문도 생략이 가능하다.

InterfaceA1 a2 = () -> { System.out.println("인자가 없는 함수 구현"); }; // 매개인자 없는 경우에는 빈 소괄호를 사용해야 한다.

이부분에 대해서는 개인적인 약간의 불만이 있다. 소괄호, 중갈호, return 문생략 까지는 납득이 되지만 람다식 함수인자의 자료형 생략은 과연 옳은 문법인가 라는 것이다. 물론 java의 오버로딩에서 처럼 함수는 함수의 인자의 타입과 그 인자들의 개수로 충분이 구분이 가능하다.

하지만 그건 컴파일러가 구분하는 것이고, 사람은 쉽게 구분을 할수 있는가?

1
TestInterface t4 = (a, b, c) -> a * b - c;

위 람다식으로 구현된 함수를 보고 인자 a,b,c가 어떤 타입인지, 그리고 그 리턴 값이 어떤 타입인 예측 할 수 있는가?

코드의 작성자가 아니라면, 저 인터페이스이 몸체를 찾아가서 확인해야 한다.

만약 문법적으로 인자의 자료형 생략을 하지 않았다면 이런 수고는 없었을 것이다.

Javascript The Good Parts의 저자 더글라스 클락포드는 개발자의 실수를 야기시키는 문법은 좋은 문법이 아니라고 했다.

그의 엄격한 jsLint는 너무하다 싶지만, 그의 이런 생각 자체에는 동의한다.

마무리

이번 글에서는 간단히 Java8의 람다의 개념과 기초 사용법을 정리했다.

람다를 아주 후려치자면, Java에서 추상메소드가 하나인 인터페이스(애시당초 기능적으로 단일 함수로 사용할)를 쉽게 구현해서 사용하는 문법 정도라고 봐도 될것 같다.