람다
- 자바 8에서 소개된 문법이다.
- 람다를 사용하면 코드를 줄일 수 있으며, 가독성이 좋다.
예시를 통해 람다를 알아보자.
interface Printable {
void print(String s);
}
전 포스팅에서 다뤘던 익명클래스를 사용해 위 Printable 인터페이스를 구현하는 인스턴스를 생성하기 위해 다음과 같이 코드를 작성했었다.
📌 익명 클래스 인스턴스 생성
Printable prn = new Printable() {
@Override
public void print(Stirng s) {
System.out.println(s);
}
};
이것을 람다를 기반으로 수정하면 다음과 같다.
📌 람다식의 두가지 표현 방법
Printable prn = (String s) -> { Sytem.out.println(s); };
Printable prn = (s) -> { Sytem.out.println(s); }; // 매개변수 타입은 생략 가능
- 이때 대입 연산자 (=) 오른쪽에 위치한 것이 람다식(Lambda Expression) 이다.
- 인터페이스형 참조변수 오른쪽은 해당 인터페이스를 구현한 인스턴스의 생성으로 이어진다고 유추할 수 있다.
- 인스턴스를 생성하려면 인터페이스의 추상메소드를 구현하는 몸체가 있어야 된다.
- 따라서 { }; 안에 그 몸체를 작성해준다.
- 이때 println(s)의 s가 매개변수 임을 명시해주며, 람다식의 구분을 위해 람다 연산자인 "->"를 추가하여 람다식을 표현한다.
- 매개변수의 타입은 생략이 가능하다.
- 생략 시 컴파일러가 인터페이스의 추상메소드를 보고 매개변수 s의 타입이 String임을 확인 한다.
람다식의 인자 전달
- 기존 람다식은 참조변수를 선언하고 람다식을 작성해 초기화하는 방식으로 이뤄졌다.
- 다음과 같이 람다식을 메소드의 인자로 전달해 매개변수를 초기화하는 방식도 가능하다.
📌 기존 람다식
Printable prn = (s) -> {System.out.println(s);};
📌 람다식의 인자 전달
void method(Printable prn) {...}
method((s) -> System.out.println(s));
interface Printable {
void print(String s);
}
class Lambda {
public static void ShowString(Printable p, String s) {
p.print(s);
}
public static void main(String[] args) {
ShowString((s) -> {System.out.println(s);}, "Hello Lambda");
}
}
함수형 인터페이스
- 1개의 추상메소드를 갖는 인터페이스를 함수형(funtional) 인터페이스라 한다.
- 자바의 람다 표현식은 함수형 인터페이스로만 사용 가능하다.
interface Printable {
void print(Stirng s); // 추상메소드 1개
}
@FunctionalInterface
- 함수형 인터페이스에 부합하는지 확인하기 위한 어노테이션
- 함수형 인터페이스의 조건을 갖추었는지에 대한 검사를 컴파일러에게 요청한다.
→ 인터페이스에 둘 이상의 추상 메소드가 존재하면, 컴파일 오류 - 이때 staic, default 선언이 붙은 메소드는 구현이 필요없는 메소드이므로 함수형 인터페이스 정의에 아무런 영향을 미치지 않는다.
@FunctionalInterface
interface Calculate {
int cal(int a, int b); // 구현해야할 추상 메소드
default int add(int a, int b) { return a + b; } // 구현 필요 없음
static int sub(int a, int b) { return a - b; } // 구현 필요 없음
}
Calculate 인터페이스는 구현해야할 추상 메소드가 1개이므로, 함수형 인터페이스 조건에 부합하다 할 수 있다.
람다의 다양한 표현식
매개변수 있고 반환하지 않는 람다식
- 람다식은 기본적으로 매개변수 정보에 소괄호, 메소드 몸체에 중괄호를 한다.
- 매개변수가 1개라면 소괄호 생략이 가능하다.
- 메소드 몸체가 하나의 문장으로만 이뤄졌다면 중괄호 생략이 가능하다. 중괄호 생략시 해당 문장 끝에 위치한 세미콜론(;)도 함께 지운다.
- 예외 - return문 (아래에서 자세히 설명)
- 람다식의 매개변수 정보는 생략이 가능하다.
- 해당 람다식이 채우게 될 추상메소드 정보를 통해서 컴파일러가 유추 가능하기 때문이다.
interface Printable {
void print(String s); // 매개변수 하나, 반환형 void
}
class OneParamNoReturn {
public static void main(String[] args) {
Printable p;
// 줄임 없는 표현
p = (String s) -> { System.out.println(s); };
p.print("Lambda exp one.");
// 중괄호 생략 (문장이 하나인 경우)
p = (String s) -> System.out.println(s);
p.print("Lambda exp two.");
// 매개변수 타입 생략
p = (s) -> System.out.println(s);
p.print("Lambda exp three.");
// 매개변수 소괄호 생략 (매개변수가 하나인 경우)
p = s -> System.out.println(s);
p.print("Lambda exp four.");
}
}
Lambda exp one.
Lambda exp two.
Lambda exp three.
Lambda exp four.
매개변수가 둘 이상인 람다식
- 매개변수가 둘 이상일 경우 소괄호를 사용해 묶어준다.
interface Calculate {
void cal(int a, int b); // 매개변수 둘, 반환형 void
}
class TwoParamNoReturn {
public static void main(String[] args) {
Calculate c;
c= (a, b) -> System.out.println(a+b); // 덧셈 진행
c.cal(4,3);
c= (a, b) -> System.out.println(a-b); // 뺄셈 진행
c.cal(4,3);
}
}
7
1
매개변수 있고 반환하는 람다식
- return문의 중괄호는 생략이 불가하다.
- 문장 실행 결과 값만 남아있을 경우 자동으로 return 해주기 때문에 return문의 생략이 가능하다.
interface Calculate {
int cal(int a, int b); // 값을 반환하는 추상 메소드
}
class TwoParamAndReturn {
public static void main(String[] args) {
Calculate c;
c = (a, b) -> { return a + b; }; // return문의 중괄호는 생략 불가
System.out.println(c.cal(4, 3));
c = (a, b) -> a + b; // 값만 남으면, 별도로 명시하지 않아도 반환 대상이 됨(return 생략)
System.out.println(c.cal(4, 3));
}
}
7
7
interface HowLong {
int len(String s); // 값을 반환하는 메소드
}
class OneParamAndReturn {
public static void main(String[] args) {
HowLong h1 = s -> s.length(); // return문 생략
System.out.println(h1.len("Happy"));
}
}
5
- length() 메소드는 값을 반환하기 때문에, 메소드 호출 결과로 반환된 값만 남게 된다.
- 반환된 값은 따로 명시해주지 않아도 반환의 대상이 되므로, return문을 생략해줄 수 있다.
- 이때 return문을 생략해주지 않는다면 다음과 같이 중괄호를 써줘야 한다는 것을 기억하자
- s -> { return s.length(); };
매개변수 없는 람다식
- 매개변수가 없는 람다식은 매개변수 정보를 담는 소괄호 안을 비워준다.
interface Generator {
int rand(); // 매개변수가 없는 추상메소드
}
class NoParamAndReturn {
public static void main(String[] args) {
Generator gen = () -> { // 빈 소괄호 명시
Random rand = new Random();
return rand.nextInt(50);
};
System.out.println(gen.rand());
}
}
- 매개변수 선언이 없기 때문에 매개변수 정보를 담는 소괄호가 비어있다.
- 이때 둘 이상의 문장으로 이뤄진 람다식은 중괄호로 반드시 감싸줘야 하며, 값을 반환할 때에도 return문을 반드시 사용해야함을 기억하자.
람다식과 제네릭
- 인터페이스는 제네릭으로 정의 할 수 있다. 따라서 제네릭으로 정의된 함수형 인터페이스를 대상으로 람다식을 구성할 수 있다.
- 인터페이스형 참조변수를 선언할 때 제네릭 T의 타입 인자 전달하면 추상 메소드의 T가 결정된다.
@FunctionalInterface
interface Calculate3<T> { // 제네릭 기반의 함수형 인터페이스
T cal(T a, T b);
}
class Lambda_Generic {
public static void main(String[] args) {
Calculate3<Integer> ci = (a, b) -> a + b; // <T>:Integer
System.out.println(ci.cal(4, 3));
Calculate3<Double> cd = (a, b) -> a + b; // <T>:Double
System.out.println(cd.cal(4.32, 3.45));
}
}
7
7.7700000000000005
- 참조 변수 ci, cd가 참조하는 람다식은 동일하다.
- 하지만 참조변수의 자료형이 다르기 때문에 둘은 전혀 다른 인스턴스의 생성으로 이어진다.
'☕ Java > 이론' 카테고리의 다른 글
[Java] 스트림 (Stream) (0) | 2022.02.08 |
---|---|
[Java] Optional 클래스 (0) | 2022.02.07 |
[Java] 메소드 참조 (Method References) (0) | 2022.02.04 |
[Java] 함수형 인터페이스 (0) | 2022.02.03 |
[Java] 네스티드 클래스, 이너 클래스 (0) | 2022.01.27 |
[Java] 가변인자, 어노테이션 (0) | 2022.01.26 |
[Java] 열거형 (enum) (0) | 2022.01.26 |
[Java] 컬렉션 기반 알고리즘 (sort, binarySearch, copy) (0) | 2022.01.25 |