함다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수를 지칭하는 용어이다.
람다의 근간은 수학과 기초 컴퓨터과학 분야에서의 람다 대수이다. 람다 개수는 간단히 말하자면 수학에서 사용하는 함수를 보다 단순하게 표현하는 방법이다.
람다의 특징
람다 대수는 이름을 가질 필요가 없다.(익명 함수)
두 개 이상의 입력이 있는 함수는 최종적으로 1개의 입력만 받는 람다 대수로 단순화 될 수 있다.(커링)
익명함수란?
말 그래도 함수의 이름이 없는 함수. 일급객체라는 특징을 가지고 있다.
람다의 장단점
장점
단점
람다의 표현식
람다식 예제
기존 자바 문법
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Welcome Heejin blog");
}
}).start();
람다식 문법
new Thread(()->{
System.out.println("Welcome Heejin blog");
}).start();
람다식을 사용하여 코드가 훨씬 간결해지고 가독성도 좋아진걸 확인 할 수 있다.
함수형 인터페이스
@FunctionalInterface
구현해야 할 추상 메소드가 하나만 정의된 인터페이스를 가르킨다.
자바 컴파일러는 명시된 함수형 인터페이스에 두 개 이상의 메소드가 선언되면 오류를 발생 시킨다.
EX.
@FunctionalInterface
interface Math {
public int Calc(int first, int second);
}
함수현 인터페이스 선언
public static void main(String[] args){
Math plusLambda = (first, second) -> first + second;
System.out.println(plusLambda.Calc(4, 2));
Math minusLambda = (first, second) -> first - second;
System.out.println(minusLambda.Calc(4, 2));
}
추상 메소드 구현 및 함수형 인터페이스 사용
6
2
실행결과.
람다의 바디에서는 파라미터가 아닌 바디 외부에 있는 변수를 참조할 수 있다.
로컬 클래스, 익명 클래스에서도 참조 가능.
람다 시그니처의 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수를 자유 변수라고 한다.
람다 바디에서 자유 변수를 참조하는 것을 람다 캡쳐링 이라고 한다.
Lambda Capture의 제약 조건
지역 변수를 담다 캡쳐링 할 때 캡쳐링 당하는 자유 변수는 두 가지 제약 조건이 존재한다.
람다에서 변수를 참조하는데 변수에 final 선언을 하지 않았기 때문에 변수는 effectively final이다. 그렇기 때문에 변수에 수정을 가하면 오류가 발생한다.
지역 변수는 JVM 영역 중 stack 영역에 생성된다. 그리고 쓰레드별로 이 stack영역이 별도로 생성된다. 즉, 지역 변수느느 쓰레드끼리 공유가 안된다. 반면 인스턴스 변수는 힙 영역에 생성된다. 따라서 인스턴스 변수는 쓰레드끼리 공유가 가능하다.
람다는 별도의 쓰레드에서 실행이 가능하다. 따라서 지역 변수가 있는 쓰레드가 사라졌을 때, 람다가 이 변수를 참조하고 있다면 오류가 날 것이다. 하지만 위에서 본 코드처럼 람다에서 자유 변수 참조가 가능하다.
Variable capture
람다가 자유 변수를 참조할 때 직접 그 변수를 참조하는 것이 아니라 자유 변수를 자신의 stack에 복사하여 참조하기 때문이다.
때문에 variable capture가 될 자유 변수는 수정이 불가하도록 final이거나 final 처럼 동작해야 한다. 자바 8 이전에는 이런 이유로 final이 아닌 변수는 익명/로컬 클래스, 람다에서 참조를 하지 못했는데 자바8 이후로 final을 붙이지 않아도 effectinely final로 선언이 된다.
Lambda의 Scope
자유 변수의 Scope와 같이 때문에 람다에서 자유 변수와 같은 이름의 변수를 생성하면 에러가 나온다.
반면 로컬 클래스, 익명 클래스의 내부에 자유 변수와 같은 이름의 변수를 선언하는 것은 가능하다. 자유 변수보다 로컬 클래스, 익명 클래스 내부에 생성된 변수의 스코프가 더 지엽적이기 때문에 shadowing 된다.
람다 표현식이 단 하나의 메소드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있도록 해준다.
메소드 참조를 사용하면 불필요한 매개변수를 제거하고 다음과 같이 '::' 기호를 사용하여 표현할 수 있다.
클래스이름::메소드이름
참조변수이름::메소드이름
EX.
(base, exponent) -> Math.pow(base, exponent);
위의 예제는 math 클래스의 pow()메소드로 인수를 전달하는 역할만 하므로, 메소드 참조를 사용하여
Math::pow;
로 간단하게 표현 가능.
EX2.
DoubleUnaryOperator oper;
oper = (n) -> Math.abs(n); // 람다 표현식
System.out.println(oper.applyAsDouble(-5));
oper = Math::abs; // 메소드 참조
System.out.println(oper.applyAsDouble(-5));
특정 인스턴스의 메소드를 참조할 때에도 참조 변수의 이름을 통해 메소드 참조를 사용할 수 있다.
단순히 객체를 생성하고 반환하는 람다 표현식은 생성자 참조로 변활할 수 있다.
EX.
(a) -> { return new Object(a); }
위의 예제는 단순히 Object 클래스의 인스턴스를 생성하고 반환하기만 하므로, 생성자 참조를 사용하면
Object::new;
로 표현할 수 있다.
이때 해당 생성자가 존재하지 않으면 컴파일 시 오류가 발생한다.
또한, 배열을 생성할 때에도 담으과 같이 생성자 참조를 사용할 수 있다.
Function<Integer, double[]> func1 = a -> new double[a]; // 람다 표현식
Function<Integer, double[]> func2 = double[]::new; // 생성자 참조
래퍼런스
https://khj93.tistory.com/entry/JAVA-람다식Rambda란-무엇이고-사용법
https://velog.io/@sdb016/Variable-Capture
http://www.tcpschool.com/java/java_lambda_reference