람다(Lambda)란?
람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다.
람다 표현식에는 이름은 없지만, 파라미터 리스트, 바디, 반환형식, 발생할 수 있는 예외 리스트는 가질 수 있다.
람다는 다음과 같은 특징을 가지고 있다.
- 익명 : 보통의 메서드와 달리 이름이 없으므로 익명이라 표현한다. 구현해야 할 코드에 대한 걱정거리가 줄어든다.
- 함수 : 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부른다. 하지만 메서드처럼 파라미터 리스트, 바디, 반환 형식, 가능한 예외 리스트를 포함한다.
- 전달 : 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다.
- 간결성 : 익명 클래스처럼 많은 자질구례 한 코드를 구현할 필요가 없다.
람다는 세 부분으로 이루어진다.
- 파라미터 리스트 : 메서드 파라미터
- 화살표 : 람다의 파라미터 리스트와 바디를 구분한다.
- 람다 바디 : 두 사과의 무게를 비교한다. 람다의 반환값에 해당하는 표현식이다.
어디에 어떻게 람다를 사용할까?
함수형 인터페이스라는 문맥에서 람다 표현식을 사용할 수 있다.
일단 함수형 인터페이스가 무엇인지 살펴보자.
함수형 인터페이스
public interface Predicate<T> {
boolean test(T t);
}
간단히 말해 함수형 인터페이스는 정확히 하나의 추상 메서드를 지정하는 인터페이스다.
람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로 전체 표현식을 함수형 인터페이스의 인스턴스로 취급할 수 있다.
@FunctionalInterface는 함수형 인터페이스임을 가리키는 어노테이션이다.
@FunctionalInterface로 인터페이스를 선언했지만 실제로 함수형 인터페이스가 아니면 컴파일러가 에러를 발생시킨다.
함수 디스크립터(function descriptor)
함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 묘사한다.
함수형 인터페이스의 추상 메서드 시그니처를 함수 디스크립터(function descriptor)라고 한다.
다양한 람다 표현식을 사용하려면 공통의 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요하다.
자바 8 라이브러리 설계자들은 java.util.function
패키지로 여러 가지 새로운 함수형 인터페이스를 제공한다.
Function<T,R>
Function<Integer, Integer> plus10 = (i) -> {
return i+10;
};
Function<Integer, Integer> multifly2 = (i) -> i*2;
//compose는 Function의 입력전에 compose안의 함수를 처리하고 결과 값을 입력값으로 변환
Function<Integer, Integer> multiply2AndPlus10 = plus10.compose(multifly2);
System.out.println(multiply2AndPlus10.apply(2));
//andThen은 plus10의 결과 값을 multiply2의 입력값으로 변환
plus10.andThen(multifly2);
- T 타입을 받아서 R 타입을 리턴하는 함수 인터페이스
- R apply(T t)
- 함수 조합용 메서드
- compose : apply 이전에 함수를 처리하고 결과 값을 apply의 입력관으로 변환한다.
- andThen : applay 이후에 결과 값을 입력값으로 변환하여 함수를 실행한다.
BiFunction<T,U,R>
- 두 개의 값(T, U)을 받아서 R 타입을 리턴하는 함수 인터페이스
Consumer<T>
//void 형식 지원
Consumer<Integer> printT = (i) -> System.out.println(i);
printT.accept(10);
- T 타입을 받아서 아무 값도 리턴하지 않는 함수 인터페이스
- void Accept(T, t)
- 함수 조합용 메서드
- andThen
Supplier<T>
//입력해주는 값 없이 받아 올 값을 정의
Supplier<Integer> get10 = () -> 10;
System.out.println(get10.get());
- T 타입의 값을 제공하는 함수 인터페이스
- T get()
Predicate<T>
//true, flase를 반환
Predicate<String> startsWithStar = (s) -> s.startsWith("star");
Predicate<Integer> isEven = (i) -> i%2 == 0;
- T 타입을 받아서 boolean을 리턴하는 함수 인터페이스
- boolean test(T t)
- 함수 조합용 메서드
- And
- Or
- Negate
UnaryOperator<T>
//입력 타입과 리턴 타입이 같을 때
UnaryOperator<Integer> UPlus10 = (i) -> i+10;
UnaryOperator<Integer> UMultiply2 = (i) -> i*2;
- Fuction<T,R> 의 특수한 형태로, 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스
BinaryOperator<T>
- BiFunction<T,U,R>의 특수한 형태로, 동일한 타입의 입력값 두 개를 받아 리턴하는 함수
지역 변수 사용
람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수(파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용할 수 있다.
이와 같은 동작을 람다 캡처링(capturing Lambda)이라고 부른다.
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
하지만 자유 변수에도 약간의 제약이 있다.
람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처(자신의 바디에서 참조할 수 있도록) 할 수 있다.
하지만 그러려면 지역 변수는 명시적으로 final로 선언되어 있어야 하거나 실질적으로 final로 선언된 변수와 똑같이(effective final) 사용되어야 한다.
즉, 람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡처할 수 있다.
지역 변수의 제약
인스턴스 변수는 힙에 저장되는 반면 지역 변수는 스택에 위치한다.
람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다.
따라서 자바 구현에서는 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공한다.
따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴 것이다.
메서드 참조
메서드 참조를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있다.
때로는 람다 표현식보다 메서드 참조를 사용하는 것이 더 가독성이 좋으며 자연스러울 수 있다.
메서드를 참조하는 방법
- 스태틱 메서드 참조
- 타입::스태틱 메서드
- 특정 객체의 인스턴스 메서드 참조
- 객체 레퍼런스::인스턴스 메서드
- 임의 객체의 인스턴스 메서드 참조
- 타입::인스턴스 메서드
- 생성자 참조
- 타입::new
스태틱 메서드 참조
//스태틱 메서드 참조
UnaryOperator<String> hi = Greeting::hi;
특정한 인스턴스 참조
//특정한 인스턴스 메서드 참조
Greeting greeting = new Greeting();
UnaryOperator<String> hello = greeting::hello;
문자열을 받는/받지 않는 생성자 참조
//문자열을 받는 생성자 참조
Function<String,Greeting> starGreeting = Greeting::new;
Greeting star = starGreeting.apply("star");
System.out.println(star.getName());
//문자열을 받지 않는 생성자 참조
Supplier<Greeting> newGreeting = Greeting::new;
임의 객체의 인스턴스 메서드 참조
//임의 객체의 인스턴스 메소드 참조
String[] names = {"star", "tobi", "mark"};
Arrays.sort(names, String::compareToIgnoreCase);
System.out.println(Arrays.toString(names));
참고 자료 : https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391
'백엔드 > Java' 카테고리의 다른 글
[Java] 템플릿 메서드 패턴(Template Method Pattern) (0) | 2023.05.17 |
---|---|
[Java] 스트림(Stream) (0) | 2023.05.11 |
[Java] 동작 파라미터화(Behavior Parameterization) (0) | 2023.05.09 |
[Java] 런타임 데이터 영역 (Runtime Data Area) (0) | 2023.05.05 |
[Java] 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2023.04.20 |