람다 표현식
자바에서는 인터페이스를 선언하거나 매개변수로 주어야 할 때 1회용 구현체인 익명 클래스를 사용할 수 있다.
아래 예시에서 Test::printList
의 변화를 확인해본다.
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(2, 1, 3, 5, 4);
printList(list);
// 오름차순 정렬
List<Integer> list2 = list.stream()
.sorted(Comparator.naturalOrder())
.collect(Collectors.toList());
printList(list2);
// 내림차순 정렬
List<Integer> list3 = list.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
printList(list3);
}
private static void printList(List<Integer> list) {
// 1회용 구현체 new Consumer를 매개변수로 활용
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer num) {
System.out.println(num + " ");
}
});
System.out.println();
}
}
private static void printList(List<Integer> list) {
list.forEach(num -> System.out.println(num + " "));
System.out.println();
}
람다를 활용하여 Test::printList
의 내용을 더 간결하게 변경했다.
익명 클래스 new Consumer<Interger>() {...}
가 단 한 줄로 줄어들었으며 개발자의 의도가 드러나는 문장을 사용했다.
forEach의 매개변수에 사용되는 Consumer
타입과 Consumer::accept
가 겉으로 드러나지 않는다.
람다식은 과도한 정보를 제하고 num을 출력하는 로직에 더 집중할 수 있는 구조인 것이다.
람다 표현식?
람다식은 익명 클래스를 간략하게 표현하는 방법이다.
식 전체가 인스턴스로 취급된다.
람다 표현식의 구조
람다 표현식은 기본적으로 아래와 같은 구조를 가진다.
(parameters) -> expression
(parameters) -> { statements; }
- 람다 표현식엔 인터페이스와 오버라이드할 메소드의 이름이 표기되지 않는다.
- 매개변수 목록을 왼쪽에 표기한다.
- 매개변수는 타입을 생략할 수 있다.
- 매개변수가 1개이면 괄호를 생략할 수 있다.
- 매개변수가 0개이면
() ->
으로 매개변수가 없음을 나타내어야 한다.
- 매개변수 목록 작성이 끝나면 화살표 (
->
)를 작성한다. - 화살표 우측에는 람다 바디를 작성한다.
- 한 줄(세미콜론 한 번)으로 구현할 수 있는 경우 expression이다.
- expression은 중괄호, return, 세미콜론을 생략하여야 한다.
1. expression 구현
(String s) -> {return s.length();} >>> s -> s.length() 으로 변경 가능
(Human h) -> h.getAge() > 10 >>> 사람의 나이가 11살 이상임을 검사
2. 매개변수가 없는 경우
() -> System.out.println("Hello, world!")
3. statement 구현
(int x, int y) -> {
int z = x + y;
System.out.println(z);
return z;
}
람다 표현식 사용 범위
모든 인터페이스를 람다식으로 표현할 수 있는 건 아니다.
함수형 인터페이스에만 사용이 가능하다.
함수형 인터페이스
함수형 인터페이스는 추상 메소드가 단 1개만 있는 인터페이스를 의미한다.
특정 부모에게 상속받아 오버라이드한 클래스가 있는 경우는 함수형 인터페이스가 아니다.
@FunctionalInterface
으로 이 인터페이스가 함수형 인터페이스임을 표시할 수 있다.
@FunctionalInterface
함수형 인터페이스를 표현하는 어노테이션이다.
이 어노테이션을 사용했으나 추상 메소드가 2개 이상이면 함수형 인터페이스가 아니라고 판단하여 경고를 내뱉는다.
자주 사용하는 함수형 인터페이스
자바 API는 외우기보다 활용 방법을 이해하고 쓰는 것이 일반적이다.
다만 자바 8에서 새로 추가된 함수형 인터페이스는 어느 정도 암기하는 편이 생산성에 좋을 것 같다.
람다식엔 클래스 타입을 쓰지 않으니 자바가 이미 지원하는 함수형 인터페이스를 중복으로 만들지 않도록 해야 한다.
조건 검증
Predicate<T>
- T 타입을 매개변수로 받고 boolean을 반환한다.
- 조건 검증에 사용한다.
BiPredicate<L, R>
- L, R을 받고 boolean을 반환한다.
- 주어진 두 매개변수를 사용하여 검증할 때 사용한다.
자원 생성/소모
Consumer<T>
- T 타입을 매개변수로 받고 반환 타입이 없다.
Supplier<T>
- 매개변수는 없으며 T 타입을 반환한다.
BiConsumer<T, U>
- T, U를 받고 반환은 하지 않는다.
- 매개변수가 2개 필요하지만 반환 없이 자원을 소모하는 연산에 사용한다.
연산
Function<T, R>
- T 타입을 매개변수로 받고 R 타입을 반환한다.
UnaryOperator<T>
Function<T, R>
을 상속받은 인터페이스이며 T, R을 같은 타입으로 사용한다.- T를 받고 T를 반환한다.
- 단항 연산 시 사용한다.
BiFunction<T, U, R>
- T, U를 매개변수로 받고 R을 반환한다.
- T, U, R 모두 다른 타입을 유연하게 사용할 수 있다.
BinaryOperator<T>
BiFunction<T, U, R>
을 상속받은 인터페이스이며 T, U, R을 같은 타입으로 사용한다.- T, T를 받고 T를 반환한다.
매개변수와 반환형이 중복되는 함수형 인터페이스
람다식은 인터페이스 이름과 오버라이드할 메소드 이름을 명시적으로 나타내지 않는다.
매개변수와 반환 타입이 완전 같은 함수형 인터페이스가 2개 이상 존재하는 경우 누구를 가리키는지 명확하지 않다.
-
Foo1
@FunctionalInterface public interface Foo1 { void test(); }
-
Foo2
@FunctionalInterface public interface Foo2 { void test(); }
-
foo
void foo(Foo1 foo1) { } foo((Foo1) () -> {});
foo에서 매개변수를 Foo1
으로 받고 있으며 컴파일러도 당연히 Foo1
을 기대할 것이다.
그러나 개발자 입장에선 명확하게 와닿지 않는다.
Foo1
을 캐스팅하면 어느 인터페이스를 활용하는지 확실하게 표기할 수 있다.
'Java' 카테고리의 다른 글
Java Stream (6) 스트림 생성 (0) | 2020.07.22 |
---|---|
Java Stream (5) 기본 자료형 스트림 (0) | 2020.07.22 |
Java Stream (4) 스트림 주요 메소드 (0) | 2020.07.17 |
Java Stream (3) 스트림의 개념 (0) | 2020.07.15 |
Java Stream (1) 스트림 개요 (0) | 2020.06.02 |