[Java] Lamda, 람다
★이 포스트는 자바의 정석 3판 2권 14장 람다와 스트림을 참고, 요약하여 작성했습니다.
★람다식은 JDK1.8에 등장한 기능으로 객체지향 진영의 선봉에 서있다고 해도 과언이 아닌 Java에 함수형 언어를 얹어줌으로써 Generics의 등장과 함께 Java에 커다란 변화를 가져왔다(고 합니다). 학원에서 지겹도록 가르치는 객체지향이라는 매커니즘에 갖혀있다면(내 얘기) 처음에는 받아들이기 힘들겠지만 익숙해진다면 좀더 간결하고 경제적인 코드작성이 가능할 것이라 생각한다.
★람다식 : 메소드를 하나의 식으로 간략화 하여 표현한 것이다. 람다식 또는 람다함수는 메소드에서 메소드 이름과 반환값을 없애는 방법으로 표기하므로 ‘익명 함수’라고도 한다. 반환값은 추론이 가능하기 때문에 생략할 수 있다.
★람다식의 기본적인 템플릿은 ()->{}이다. ()안에 있는 파라메터를 이용해 {}안의 식을 수행한다는 의미이다.
★표기법 int sum(int n1, int n2) { return n1 + n2; } 를 (int n1, int n2) -> { return n1 + n2; } 로 표기한다.
★여기서 반환값이 있고, 식이 1줄인 경우 중괄호와 return문, 세미콜론을 생략하고 대신 식만으로 표현할 수 있다. (int n1, int n2) -> a + b
★람다식에 선언된 파라메터의 타입은 추론이 가능한 경우 생략할 수 있다 (n1, n2) -> a + b
★위 상태에서 파라메터가 하나인 경우에는 괄호를 생략할 수 있다. n1 -> n1 + n1
★파라메터가 없는 경우에는 빈 괄호만 쓴다. () -> System.out.println(“안농”)
★이러한 람다식은 메소드처럼 선언하고 사용하라고 있는게 아니다. 람다함수는 사실 함수형 인터페이스 객체(익명 클래스)와 동일한 상황에서 사용된다.
★함수형 인터페이스(Functional Interface)란 인터페이스 중에서 하나의 추상메소드만을 가진 인터페이스를 말한다. 포함한 메소드가 하나라면 이 인터페이스는 오직 이 메소드만을 사용하기 위해 정의한 객체이기때문에 사실상 함수에 가까운 인터페이스라 ‘함수형’이라는 이름이 붙었다는 것이 나의 작은 뇌피셜. 어쨌든 아래와 같은 인터페이스를 정의했다고 가정하자.
interface IFunctional { int func(int a, int b); }
★기존에 이 식을 객체로 생성하기 위해 아래와 같이 인터페이스를 익명클래스로 재정의 한 후 메소드를 사용했다.
IFunctional f = new IFunctional() { // 함수형 인터페이스를 정의한다. public int func(int a, int b) { return a *b; } };
int result = f.func(12, 112); // 메소드를 사용한다.
★여기서 위의 규칙대로 new
부분 부터 익명클래스 정의가 끝나는 ;
까지를 람다식으로 전환하면
IFunctional f = (a, b) -> a * b; int result = f.func(12, 112);
모양으로 축약이 가능해 진다. 기존에 함수형 인터페이스를 구현해 변수에 저장하는 것과 사실 같은 작업이지만 Funtional타입의 변수f가 함수를 참조하고 있다는 뉘앙스가 많이 풍긴다. 함수형 인터페이스(1)를 익명클래스(2)로 구현하고, 변수에 저장(3)하는 작업을 (2)부분을 굳이 작성하지 않고 (1)에서 (3)으로 다이렉트로 진행하는 모양새이다. 람다식을 변수에 저장한다는 말은 이 람다식이 정의하고 있는 메소드를 변수처럼 주고받는 것이 가능해 졌다는 의미이다. 물론 실제로 메소드가 이동하는 것은 아니고 함수형 인터페이스를 구현한 객체가 이동하는 것은 변함이 없지만 좀더 직관적인 코드작성이 가능해 졌다.
★하나의 식이 인터페이스에 정의된 메소드를 대체할 수 있는 것은 인터페이스에 정의된 메소드가 단 하나이고, 파라메터와 그 갯수, 리턴타입이 정확히 일치하기 때문이고, 이러한 이유로 람다함수는 함수형 인터페이스를 위해서만 사용할 수 있다.
★@FunctionalInterface를 붙이면 컴파일러가 함수형 인터페이스의 정의를 도와준다.
★함수형 인터페이스를 매개변수로 받는 메소드도 위와 똑같이 람다식을 참조하는 변수를 사용할 수 있다.
void printResult(IFunctional f, int a, int b) { System.out.println(“결과는?” + f.func(a, b)); } 이런 함수가 있다면
IFunctional f = (a, b) -> a * b; printResult(f, 2, 3);
형식으로 사용할 수 있고(당연히 f는 인터페이스를 구현한 객체이므로) 또한 이 두 줄을 합쳐
printResult((a, b) -> a * b, 2, 3);
형식으로도 사용할 수 있다.
★또한 ()->{} 템플릿의 람다식을 어떤 함수의 리턴 타입으로도 지정할 수 있다 public IFunctional doSomethingUseless(int a) { int b = a + 10; return (a, b)->a * b; }
★익명으로 정의한 람다식(뿐만 아니라 모든 익명객체)의 자료형은 컴파일러가 정하기 때문에 컴파일 되기 전에는 알 수 없다. IFunctional f = (a, b) -> a * b; 이 상태에서 좌변과 우변의 타입은 서로 다르다. 하지만 이 경우 양변은 완벽히 매칭 되기 때문에 형 변환이 가능하고, 또한 생략도 가능하기에 위 문장은 에러가 없다. 단 우변의 형 변환은 함수형 인터페이스로만 가능하다. 심지어 Object로의 변환도 불가능 하다. 이건 람다식의 특성인가보다.
★람다식은 익명 클래스의 인스턴스이므로 외부의 변수에 접근하는 규칙은 익명 클래스의 그 것과 동일하다. 메소드 내에 지역변수와 람다식이 있고, 이 람다식이 지역변수를 참조 한다면 이 변수는 final 키워드가 붙지 않더라도 상수로 취급된다. 일단 람다식이 참조한다면 람다식 안에서든 밖에서든 변경할 수 없다.