람다
람다 정의
자바 8부터 도입된 람다는 자바에서 함수형 프로그래밍을 지원하기 위한 핵심 기능이다.
람다는 익명 함수이다. 따라서 이름 없이 함수를 표현한다.
보통 메서드나 함수는 다음과 같이 표현한다.
반환타입 메서드명(매개변수) {
본문
}람다는 다음과 같이 간결하게 표현한다.
(매개변수) -> {본문}
//이름이 없는 함수자바는 독립적인 함수를 지원하지 않으며, 메서드는 반드시 클래스나 인터페이스에 속한다.
👆 용어 - 람다와 람다식
람다 (Lambda) : 익명 함수를 지칭하는 일반적인 용어 (개념)
람다식 (Lambda Expression) :
(매개변수) -> {본문}형태로 람다를 구현하는 구체적인 문법 표현을 지칭한다.람다는 개념을 의미하고(넓은 의미), 람다식은 자바에서 그 개념을 구현하는 구체적인 문법을 의미한다.
람다는 변수처럼 다룰 수 있다.
Procedure procedure = () -> { // 람다를 변수에 담음
System.out.println("hello! lambda");
};
procedure.run(); // 변수를 통해 람다를 실행람다도 익명 클래스처럼 클래스가 만들어지고, 인스턴스가 생성된다.
public class InstanceMain {
public static void main(String[] args) {
//익명 클래스
Procedure procedure1 = new Procedure() {
@Override
public void run() {
System.out.println("hello, lambda!");
}
};
System.out.println("class.class = " + procedure1.getClass());
System.out.println("class.instance = " + procedure1);
//람다
Procedure procedure2 = () -> {
System.out.println("hello, lambda!");
};
System.out.println("lambda.class = " + procedure2.getClass());
System.out.println("lambda.instance = " + procedure2);
}
}===========================실행 결과===========================
class.class = class lambda.lambda1.InstanceMain$1
class.instance = lambda.lambda1.InstanceMain$1@1d81eb93
lambda.class = class lambda.lambda1.InstanceMain$$Lambda/0x0000019e63003c28
lambda.instance = lambda.lambda1.InstanceMain$$Lambda/0x0000019e63003c28@34a245ab익명 클래스의 경우 $로 구분하며 뒤에 숫자가 붙고, 람다의 경우 $$로 구분하며 뒤에 복잡한 문자가 붙는다.
함수형 인터페이스
함수형 인터페이스는 정확히 하나의 추상 메서드를 가지는 인터페이스를 말한다.
람다는 클래스, 추상 클래스에는 할당할 수 없다. 오직 단일 추상 메서드를 가지는 함수형 인터페이스에만 할당할 수 있다.
/**
* SAM : 단일 추상 메서드, Single Abstract Method
*/
public class SamMain {
public static void main(String[] args) {
SamInterface samInterface = () -> {
System.out.println("Hello World!");
};
samInterface.run();
/*컴파일 오류*/
/*
NotSamInterface notSamInterface = () -> {
System.out.println("Hello World!");
};
*/
}
public interface NotSamInterface {
void run();
void go();
}
@FunctionalInterface
public interface SamInterface {
void run();
}
}람다는 하나의 함수이다. 따라서 람다를 인터페이스에 담으려면 하나의 메서드(함수) 선언만 존재해야 한다.
인터페이스는 여러 메서드(함수)를 선언할 수 있는데, 이 중 하나에 할당해야 하는 문제가 발생한다.
자바는 이러한 문제를 해결하기 위해 단 하나의 추상 메서드만을 포함하는 함수형 인터페이스에만 람다를 할당할 수 있도록 제한했다.
👆 @FunctionalInterface
이 애노테이션을 통해 함수형 인터페이스임을 선언해두면 나중에 실수로 추상 메서드를 추가할 때 컴파일 오류가 발생한다.
따라서 함수형 인터페이스임을 보장할 수 있다.
람다를 사용할 함수형 인터페이스라면
@FunctionalInterface애노테이션을 필수로 추가하는 것을 권장한다.
람다와 시그니처
람다를 함수형 인터페이스에 할당할 때는 메서드의 형태를 정의하는 요소인 메서드 시그니처가 일치해야 한다.
@FunctionalInterface
public interface MyFunction {
int apply(int a, int b);
}메서드의 시그니처
이름 :
apply매개변수 :
int,int반환 타입 :
int
람다는 익명 함수이므로 시그니처에서 이름은 제외한다. 그 외 매개변수, 반환 타입이 함수형 인터페이스에 선언한 메서드와 맞아야 한다. (매개변수 이름은 상관없다. 타입과 순서만 맞으면 된다.)
MyFunction function = (int a, int b) -> {
return a + b;
};또 다른 예시
@FunctionalInterface
public interface Procedure {
void run();
}이름 :
run매개변수 : 없음
반환 타입 : 없음
Procedure procedure = () -> {
System.out.println("Hello, World!");
}람다 생략
람다는 간결하게 코드를 작성할 수 있는 다양한 문법 생략을 지원한다.
@FunctionalInterface
public interface MyFunction {
int apply(int a, int b);
}public class LambdaSimpleV1 {
public static void main(String[] args) {
//기본
MyFunction function1 = (int a, int b) -> {
return a + b;
};
System.out.println(function1.apply(1, 2));
//단일 표현식의 경우 중괄호와 리턴 생략 가능
MyFunction function2 = (int a, int b) -> a + b;
System.out.println(function2.apply(1, 2));
//단일 표현식이 아닐 경우 중괄호와 리턴 모두 필수
MyFunction function3 = (int a, int b) -> {
System.out.println("람다 실행");
return a + b;
};
System.out.println(function3.apply(1, 2));
}
}매개변수와 반환 값이 없는 경우
@FunctionalInterface
public interface Procedure {
void run();
}public class LambdaSimpleV2 {
public static void main(String[] args) {
Procedure procedure1 = () -> {
System.out.println("Hello, Lambda!");
};
procedure1.run();
//단일 표현식은 중괄호 생략 가능
Procedure procedure2 = () -> System.out.println("Hello, Lambda!");
}
}타입 추론
@FunctionalInterface
public interface MyFunction {
int apply(int a, int b);
}이 함수형 인터페이스를 보면 이미
(int a, int b)로 매개변수의 타입이 정의되어 있다.이 정보를 사용하면 람다에서 타입 정보를 생략할 수 있다.
public class LambdaSimpleV3 {
public static void main(String[] args) {
//타입 생략 전 (타입 직접 입력)
MyFunction function1 = (int a, int b) -> a + b;
//타입 생략 후 (타입 추론)
MyFunction function2 = (a, b) -> a + b;
int result = function2.apply(1, 2);
}
}자바 컴파일러는 람다가 사용되는 함수형 인터페이스의 메서드 타입을 기반으로 람다의 매개변수와 반환값의 타입을 추론한다. 따라서 람다는 타입을 생략할 수 있다.
반환 타입은 문법적으로 명시할 수 없다. 대신에 컴파일러가 자동으로 추론한다.
매개변수 괄호 생략
public class LambdaSimpleV4 {
public static void main(String[] args) {
MyCall call1 = (int value) -> value * 2; //기본
MyCall call2 = (value) -> value * 2; //타입 추론
MyCall call3 = value -> value * 2; //매개변수 1개, () 생략 가능
}
interface MyCall {
int call(int value);
}
}매개변수가 정확히 하나이면서 타입을 생략하고 이름만 있는 경우 소괄호(
())를 생략할 수 있다.매개변수가 없는 경우 또는 매개변수가 둘 이상이면
()가 필수이다.
람다의 전달
람다는 함수형 인터페이스를 통해 변수에 대입하거나 메서드에 전달하거나 반환할 수 있다.
public class LambdaPassMain1 {
public static void main(String[] args) {
MyFunction add = (a, b) -> a + b;
MyFunction sub = (a, b) -> a - b;
System.out.println("add.apply(1, 2) = " + add.apply(1, 2));
System.out.println("sub.apply(1, 2) = " + sub.apply(1, 2));
MyFunction cal = add;
System.out.println("cal.apply(1, 2) = " + cal.apply(1, 2));
cal = sub;
System.out.println("cal.apply(1, 2) = " + cal.apply(1, 2));
}
}기본형이나 참조형이 변수에 값을 대입할 수 있는 것처럼 함수형 인터페이스로 선언한 변수에 람다 인스턴스의 참조값을 대입할 수 있다.
람다도 인터페이스(함수형 인터페이스)를 사용하므로 람다 인스턴스의 참조값을 변수에 전달할 수 있다.
변수에 참조값을 전달할 수 있으므로 다음과 같은 사용이 가능하다.
매개변수를 통해 메서드(함수)에 람다를 전달할 수 있다. (정확히는 람다 인스턴스의 참조값을 전달)
메서드가 람다를 반환할 수 있다. (정확히는 람다 인스턴스의 참조값을 반환)
람다를 메서드에 전달
public class LambdaPassMain2 {
public static void main(String[] args) {
MyFunction add = (a, b) -> a + b;
MyFunction sub = (a, b) -> a - b;
//변수를 통해 전달
calculate(add);
calculate(sub);
//람다를 직접 전달
calculate((a, b) -> a + b);
calculate((a, b) -> a - b);
}
private static void calculate(MyFunction function) {
int a = 1, b = 2;
int result = function.apply(a, b);
System.out.println("result = " + result);
}
}메서드가 람다를 반환
public class LambdaPassMain3 {
public static void main(String[] args) {
MyFunction add = getOperation("add");
MyFunction sub = getOperation("sub");
MyFunction xxx = getOperation("xxx");
System.out.println("add.apply(1, 2) = " + add.apply(1, 2));
System.out.println("sub.apply(1, 2) = " + sub.apply(1, 2));
System.out.println("xxx.apply(1, 2) = " + xxx.apply(1, 2));
}
private static MyFunction getOperation(String operator) {
return switch (operator) {
case "add" -> (a, b) -> a + b;
case "sub" -> (a, b) -> a - b;
default -> (a, b) -> 0;
};
}
}람다는 함수형 인터페이스를 구현한 익명 클래스 인스턴스와 같은 개념이다.
람다를 변수에 대입한다는 것은 람다 인스턴스의 참조값을 대입하는 것이고, 람다를 메서드(함수)의 매개변수나 반환값으로 넘긴다는 것 역시 람다 인스턴스의 참조값을 전달, 반환하는 것이다.
람다를 자유롭게 전달하거나 반환할 수 있기 때문에 코드의 간결성과 유연성이 높아진다. 만약 익명 클래스를 작성했다면 매우 번잡했을 것이다.
👆 고차 함수 (Higher-Order Function)
고차 함수는 함수를 값처럼 다루는 함수를 의미하며, 일반적으로 다음 두 가지 중 하나를 만족하면 고차 함수라 한다.
함수를 인자로 받는 함수(메서드)
함수를 반환하는 함수(메서드)
자바에서 람다(익명 함수)는 함수형 인터페이스를 통해서만 전달할 수 있다. 즉 자바에서 함수를 주고받는다는 것은 함수형 인터페이스를 구현한 어떤 객체(람다 or 익명 클래스)를 주고받는 것과 같다.
고차 함수는 함수를 다루는 추상화 수준이 더 높다는 데에서 유래했다. 보통 일반적인 함수는 데이터(값)을 다루는데, 이것을 넘어 함수라는 개념 자체를 값처럼 다룬다는 점에서 추상화의 수준이 한 단계 높아진다고 해서 고차 함수라고 부른다.
Last updated