Lamda

2021-03-06
  • Java

람다식

자바는 객체를 기반으로 프로그램을 구현한다. 만약 어떤 기능이 필요하다면, 클래스를 먼저 만들어 기능을 구현한 메서드를 만든 뒤, 그 메서드를 호출해야한다. 즉, 클래스가 없으면 메서드를 사용할 수 없다. 함수형 프로그래밍 은 함수의 구현과 호출만으로 프로그램을 만들 수 있는 프로그래밍 방식이다. 자바 8 부터 람다식을 통해 함수형 프로그래밍을 제공한다.

람다식은 간단하게 말하면 익명 함수를 생성하는 식이다. 람다식을 사용하면 메서드의 구현을 간결하게 하여 가독성이 높아지고, 코드가 줄어들며, 함수형 프로그래밍을 바탕으로 하여 병렬 프로그래밍이 가능하다. 또한 지연 연산을 이용하여 향상된 퍼포먼스도 가능하다.

람다식 사용법

문법은 다음과 같다.

(매개변수) -> {실행문;}

[예시]

int add(int x, int y) {
    return x + y;
}

를 람다식으로 표현하면,

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

이다.

문법 특징

매개변수 자료형과 괄호 생략

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

매개변수가 한 개 일 때는 괄호를 생략할 수 있지만, 두 개인 경우 괄호를 생략할 수 없다.

중괄호 생략

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

중괄호 안의 구현 부분이 한 문장인 경우 중괄호를 생략할 수 있다. 하지만 중괄호 안의 구현 부분이 한 문장이더라도, return 문은 중괄호를 생략할 수 없다.

return 생략

(x, y) -> x + y
str -> str.length()

중괄호 안의 구현 부분이 return문 하나라면, 중괄호의 return을 모두 생략하고 식만 쓸 수 있다.

함수형 인터페이스

람다식은 메서드 이름이 없고 메서드를 실행하는 데 필요한 매개변수와 매개변수를 활용한 실행 코드를 구현하는 것이다. 그렇다면 메서드는 어디에 선언하고 구현해야 할까? 함수형 언어에서는 함수만 따로 호출가능하지만, 자바에서는 참조 변수 없이 메서드를 호출할 수 없다. 따라서 람다식을 구현하려면 함수형 인터페이스를 만들고, 인터페이스에 람다식으로 구현할 메서드를 선언하는 것이다. 람다식은 하나의 메서드를 구현하여 인터페이스형 변수에 대입하므로 인터페이스가 두 개 이상의 메서드를 가져서는 안된다.

@FunctionalInterface

람다식으로 구현한 인터페이스에 실수로 다른 메서드를 추가할 수 있으므로, 이러한 실수를 막기위헤 @FunctionalInterface를 사용한다. 이 애노테이션을 사용하면 함수형 인터페이스라는 의미를 갖고, 메서드를 하나이상 선언하면 에러가 난다. 반드시 써야하는 것은 아니지만 함수형 인터페이스라는 것을 명시적으로 표현할 수 있어 나중에 발생할 오류를 방지할 수 있다.

Variable Capture

final 키워드는 위치에 따라 의미가 다른데, 우선 메서드에서는 재정의 할 수 없음을 명시한다. 변수에서는 값이 상수로 고정되고, 객체에서 사용될 경우엔 가리키는 참조가 상수가 되며 기본형인 경우 초기화될 시, 그 값이 변경될 수 없다. 따라서 final은 기본적으로 객체를 제한적으로 사용하게 만든다. 이로인해 성공적으로 컴파일된 프로그램의 엔티티들에 final을 사용하게 되면, final의 제약으로 인해 컴파일에 실패할 수 있다. 하지만 성공적으로 컴파일된 프로그램에서 final이 제거되면, 컴파일 시 영향을 주지 않는 듯 보이지만, 컴파일에 실패할 만큼 심각한 일이 발생한다. 메서드 내에 정의된 내부 클래스가 해당 메서드의 로컬 변수를 참조하는 경우, 해당 로컬 변수를 final로 정의해야한다고 java에게 명령한다. 함수가 실행을 완료하고 모든 변수가 사라진 상태에서는 변수들이 프로세스 스택에서 제거 될 수 있으나, 내부 클래스의 객체가 해당 함수의 특정 지역 변수를 참조하는 경우, 힙에 여전히 있을 가능성이 있다. 따라서 이러한 문제를 막기 위해 java에서는 로컬 변수의 복사본을 만들고, 이를 내부 클래스에 대한 참조로 제공한다. 두 복사본 간의 일관성을 유지하기 위해 로컬 변수는 반드시 final이어야 한다.

컴파일러 단계에서만 내부 클래스를 인식할 수 있는데, 바이트코드는 이너클래스를 <Enclosing Class Name>$<Inner Class Name> 과 같은 식별자를 통해 별도의 클래스로 인식한다.

한편 람다식 역시 컴파일 시, 익명 클래스 로 컴파일이 되는데, <<Enclosing Class name>>$<<1(Number)>> 와 같은 형태로 컴파일 된다. 따라서, 동적으로 값이 변하는 경우, 위에서 처럼 동시성의 문제가 발생할 수 있으므로 변수를 capture를 통해 읽기 전용으로 복사를 하여 동시성 문제가 생기지 않도록 final을 요구한다. 익명 클래스에서 사용하는 모든 변수는 자동으로 만들어진 생성자에 의해 복사가 되는데, 다른 함수에 의해 이 로컬 변수 값이 변경되는 것은 이상할 것이다.

따라서, 자바 람다식에서는 지역 변수 접근이 불가능하다.

출처 -

https://darkstart.tistory.com/199 http://www.devcodenote.com/2015/04/variable-capture-in-java.html

메서드 참조

메서드 참조는 메서드를 참조하여 매개변수의 정보 및 리턴타입을 알아내어 람다식에서 불필요한 매개변수를 제거하는 것이다.

예를들어

(x,y) -> Math.min(x,y)

와 같은 람다식이 있다고 한다면, 이 람다식은 단순히 두 개의 값을 Math.min(x,y)의 매개값으로만 전달하므로, 메서드 참조를 이용해 코드를 줄일 수 있다.

Math :: min;

생성자 참조

생성자를 참조하는 것은 객체를 생성하는 것인데, 단순히 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 변환할 수 있다.

(a,b) => new Student(a,b)

는 아래와 같이 바꿀 수 있다.

Student :: new

출처 - https://steady-coding.tistory.com/307

Profile picture

2yeseul

트리플에서 백엔드 개발을 맡고 있습니다. 무한 삽질을 기록합니다. ⚒️