15주차 과제: 람다식
목표
자바의 람다식에 대해 학습하세요.
학습할 것
- 람다식 사용법
- 함수형 인터페이스
- Variable Capture
- 메소드, 생성자 레퍼런스
람다식 사용법
람다식(Lambda Expression)이란 함수를 하나의 식으로 표현할 것을 말합니다. 함수를 람다식으로 표현하면 메소드명이 필요없게 됩니다. 그래서 람다식을 익명 함수(Anonoymous Function)의 한 종류라고 볼 수 있습니다.
@FunctionalInterface
public interface MyInterface {
int sum (int x, int y);
}
먼저 인터페이스를 만들고 두 파라미터를 받는 sum
이라는 추상 메소드가 있습니다.
public class Main {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface() {
@Override
public int sum(int x, int y) {
return x + y;
}
};
}
}
main()
메소드에서 인터페이스의 인스턴스를 생성하여 sum()
메소드를 오버라이딩을 하면 위와 같은 코드가 됩니다. sum()
이라는 메소드는 두 파라미터인 x, y
를 받아서 더한 값을 리턴합니다. 하지만 위 코드를 더 간결하게 만들 수 있습니다. 바로 람다식으로 말이죠.
public class Main {
public static void main(String[] args) {
MyInterface myInterface = (x, y) -> {
return x + y;
};
}
}
첫 번째 코드보다 더 간결해졌습니다.
- (x, y) : 괄호안에 두 파라미터를 입력합니다. 이 때 타입을 생략가능합니다.
- { return x + y } : {} 중괄호 안에 로직을 작성합니다.
위 코드보다 더 간결하게 만들 수 있습니다.
public class Main {
public static void main(String[] args) {
MyInterface myInterface = (x, y) -> x + y;
}
}
만약에 {} 중괄호 안에 로직이 단 한 줄이라면 {} 중괄호와 return은 생략가능합니다.
최종적으로 한 줄로 표현식이 완성됩니다. 이게 람다식 입니다.
아마 자바스크립트에 익숙하신 분이라면 함수 표현식을 많이 사용하셔서 람다식도 익숙하실 것 같습니다.
이렇게 람다식을 사용하면 보시는 것처럼 코드가 간결해집니다.
다만 너무 람다식을 남발하면 코드가 오히려 지저분해지고 해석하기 어려울 수 있습니다. 그래서 디버깅도 하기 힘들어집니다. 따라서 적절히 사용하는 것이 좋겠죠.
함수형 인터페이스
위 코드에서 MyInterface 인터페이스가 바로 함수형 인터페이스입니다.
함수형 인터페이스(Functional Interface)란 인터페이스 내에 선언하여 단 하나의 추상 메소드만을 갖도록 제한하는 역할을 합니다. 이러한 함수형 인터페이스를 사용하는 이유는 바로 자바에서 람다식이 이 함수형 인터페이스를 반환하기 때문입니다.
@FunctionalInterface
public interface MyInterface {
int sum (int x, int y);
int sum2 (int x, int y);
}
위 코드처럼 추상 메소드가 두 개라면 함수형 인터페이스가 아닙니다.
보통 함수형 인터페이스라고 지정할 수 있는 방법이 @FunctionalInterface 애노테이션을 사용하는 것입니다. @FunctionalInterface 애노테이션을 붙이고 위 코드처럼 추상 메소드가 두 개라면 컴파일 에러를 발생시킵니다.
여기서 중요한 점은 람다식은 함수형 인터페이스로만 반환한다는 점만 기억하시면 될 것 같습니다.
Variable Capture
public class Main {
private int a = 144;
public void printExample() {
int b = 222;
final MyInterface myInterface = () -> System.out.println(a);
}
}
위 코드에서 람다식에서 람다 시그니처의 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수를 자유 변수(Free Variable)이라고 합니다.
그리고 이 자유 변수를 참조하는 행위를 람다 캡처링(Lambda Capturing)이라고 합니다.
지역 변수를 람다 캡처링할 때 두 가지 제약조건이 있습니다.
- 지역변수는 final로 선언되어 있어야 합니다.
- final로 선언되어 있지 않더라도 지역변수는 final처럼 동작해야 합니다.
그래서 위 코드는 제약조건에 만족합니다. 이유는 final로 선언하지 않더라도 값을 변경하지 않고 final처럼 동작하기 때문이죠.
그렇다면 아래 코드는 어떨까요?
public class Main {
private int a = 144;
public void printExample() {
int b = 222;
a = 1000;
final MyInterface myInterface = () -> System.out.println(a);
}
}
변수 a는 final로 선언되어 있지도 않고 final처럼 동작하지도 않습니다. (값을 변경해버렸죠.)
이 때는 제약조건에 만족하지 않습니다.
그렇다면 람다식에는 왜 이런 제약조건이 생기게 되었을까요?
그 이유는 https://perfectacle.github.io/2019/06/30/java-8-lambda-capturing/ 이 블로그를 꼭 보세요. 설명이 아주 자세하게 나와있습니다.
메소드, 생성자 레퍼런스
람다식 메소드 참조란 메소드를 참조해서 파라미터의 정보 및 리턴 타입을 알아내어 람다식에서 불필요한 파라미터는 제거하는 것을 말합니다.
람다식을 아래 코드처럼 표현할 수 있었습니다.
public class Main {
public static void main(String[] args) {
MyInterface myInterface = (x, y) -> x + y;
}
}
위 코드를 더 생략해서 이제는 파라미터까지 제거할 수 있습니다.
public class Main {
public static void main(String[] args) {
MyInterface myInterface = Integer::sum;
}
}
함수형 인터페이스에서 선언한 추상 메소드의 타입 :: 메소드명 이렇게 적어주면 이게 람다식 메소드 참조입니다.
람다식 생성자 참조도 메소드 참조와 같은 개념입니다.
먼저 멤버 클래스를 하나 만들어줍니다.
public class Member {
private String name;
private int age;
public Member() {
}
public Member(String name, int age) {
this.name = name;
this.age = age;
}
}
@FunctionalInterface
public interface MyInterface {
Member example(String name, int age);
}
public class Main {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface() {
@Override
public Member example(String name, int age) {
return new Member(name, age);
}
};
}
}
이제 위 코드를 람다식으로 바꾸면 아래 코드처럼 되겠죠?
public class Main {
public static void main(String[] args) {
MyInterface myInterface = (name, age) -> new Member(name, age);
}
}
그리고 생성자 참조를 하게 되면 아래 코드처럼 변경할 수 있습니다.
public class Main {
public static void main(String[] args) {
MyInterface myInterface = Member::new;
}
}
아주 간결해졌죠?
대신에 이 코드만 보고 이게 어떤 코드인지는 알기가 정말 힘들 것 같네요 ㅎㅎ;
References
'Java' 카테고리의 다른 글
왜 public static void main(String[] args) ? (0) | 2022.04.16 |
---|---|
HashTable VS HashMap VS ConcurrentHashMap (0) | 2022.04.16 |
[자바 라이브 스터디] 14. 제네릭 (0) | 2021.10.23 |
[자바 라이브 스터디] 13. I/O (0) | 2021.10.20 |
[자바 라이브 스터디] 12. 어노테이션 (0) | 2021.10.19 |
댓글