티스토리 뷰
3.1 람다란 무엇인가?
- 람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있음.
- 람다 표현식에는 이름은 없지만, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트는 가질 수 있음.
- 람다의 특징
- 익명 : 보통의 메서드와 달리 이름이 없음
- 함수 : 특정 클래스에 종속되지 않으므로 함수라고 부름.
- 전달 : 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있음
- 간결성 : 익명 클래스처럼 많은 코드를 작성할 필요 없음
- 람다는 세 부분으로 이루어짐.
// (파라미터 리스트) -> 람다 바디
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
- 파라미터 리스트
- 화살표
- 람다 바디
- 람다는 표현하는 방식이 두 가지
- 표현식 스타일 : (parameters) -> expression
- 블록 스타일 : (parameters) -> { statements; }
3.2 어디에, 어떻게 람다를 사용할까?
3.2.1 함수형 인터페이스
- 함수형 인터페이스는 오직 하나의 추상 메서드만 갖는 인터페이스.
- Java 8부터 인터페이스는 디폴트 메서드, 스태틱 메서드도 가질 수 있지만, 오로지 추상 메서드의 개수로만 판단함.
- 람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로 전체 표현식을 함수형 인터페이스의 인스턴스로 취급 할 수 있음.
3.2.2 함수 디스크럽터
- 람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크럽터라고 부름.
- 람다 표현식은 변수에 할당하거나 함수형 인터페이스를 인수로 받는 메서드로 전달할 수 있으며, 함수형 인터페이스의 추상 메서드와 같은 시그니처를 가짐
- @FunctionalInterface -> 함수형 인터페이스임을 가리키는 어노테이션. @FunctionalInterface로 인터페이스를 선언했지만 실제로 함수형 인터페이스가 아니면 컴파일러가 에러를 발생시킴
3.3 람다 활용 : 실행 어라운드 패턴
- 자원 처리에 사용하는 자원을 열고, 처리한 다음 자원을 닫는 순서로 이루어짐. 이러한 형식의 코드를 실행 어라운드 패턴이라고 부름.
public String processFile() throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return br.readLine();
}
}
3.3.1 1단계 : 동작 파라미터화를 기억하라
- 기존의 설정, 정리 과정을 재사용하고 실작업 메서드만 다른 동작을 수행하도록 명령하려면, 실작업에 대한 동작을 파라미터화 하는 것.
- 아래와 같이 람다를 이용해서 동작을 전달할 수 있음.
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());
3.3.2 2단계 : 함수형 인터페이스를 이용해서 동작 전달
- BufferedReader -> String과 IOException을 던질 수 있는 시그니처와 일치하는 함수형 인터페이스를 전달 할 수 있음.
import java.io.BufferedReader;
import java.io.IOException;
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader br) throws IOException;
}
public String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
3.3.3 3단계 : 동작 실행
- processFile 바디 내에서 BufferedReaderProcessor 객체의 process를 호출할 수 있음.
3.3.4 4단계 : 람다 전달
String oneLine = processFile((BufferedReader br) -> br.readLine());
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
3.4 함수형 인터페이스 사용
- 함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 묘사함.
- 함수형 인터페이스의 추상 메서드 시그니처를 함수 디스크럽터라고 함.
- 자바 8은 java.util.function 패키지로 여러 가지 새로운 함수형 인터페이스를 제공.
3.4.1 Predicate
- java.util.function.Predicate<T> 인터페이스는 test라는 추상 메서드를 정의하며 test는 제네릭 형식 T의 객체를 인수로 받아 불리언을 반환함.
- T 형식의 객체를 사용하는 불리언 표현식이 필요한 상황에서 Predicate 인터페이스를 사용할 수 있음.
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for (T t : list) {
if (p.test(t)) {
results.add(t);
}
}
return results;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = FilterExam.filter(listOfStrings, nonEmptyStringPredicate);
3.4.2 Consumer
- java.util.function.Consumer<T> 인터페이스는 제네릭 형식 T 객체를 받아서 void를 반환하는 accept라는 추상 메서드를 정의함.
- T 형식의 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 Consumer 인터페이스를 사용할 수 있음.
public static <T> void forEach(List<T> list, Consumer<T> c) {
for (T t : list) {
c.accept(t);
}
}
ConsumerExam.forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i));
3.4.3 Function
- java.util.function.Function<T, R> 인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R 객체를 반환하는 추상 메서드 apply를 정의함.
- 입력을 출력으로 매핑하는 람다를 정의할 때 Function 인터페이스를 활용할 수 있음.
public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
List<R> result = new ArrayList<>();
for (T t : list) {
result.add(f.apply(t));
}
return result;
}
List<Integer> l = FunctionExam.map(Arrays.asList("lamdas", "in", "action"), (String s) -> s.length());
- 위와 같은 함수형 인터페이스 외에도 특화된 형식의 함수형 인터페이스도 존재함.
- 위의 Predicate, Consumer, Function의 경우 참조형만 사용할 수 있음. 기본형의 경우 박싱을 해줘야 하는데, 이런 변환 과정은 비용이 소모됨.
- 자바 8에서는 기본형을 입출력으로 사용하는 상황에서 오토박싱 동작을 피할 수 있도록 특별한 버전의 함수형 인터페이스를 제공함. -> IntPredicate, DoublePredicate, IntConsumer, LongBinaryOperator 등..
3.5 형식 검사, 형식 추론, 제약
3.5.1 형식 검사
- 람다가 사용되는 콘텍스트를 이용해서 람다의 형식을 추론할 수 있음.
- 어떤 콘텍스트에서 기대되는 람다 표현식의 형식을 대상 형식이라고 부름
List<Apple> heavierThan150g = filter(inventory, (Apple apple) -> apple.getWeight() > 150);
- 코드의 형식 확인 과정은 아래와 같음.
- filter 메서드의 선언을 확인함.
- filter 메서드는 두 번째 파라미터로 Predicate<Apple> 형식 (대상 형식)을 기대함.
- Predicate<Apple>은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스임.
- test 메서드는 Apple을 받아 boolean을 반환하는 함수 디스크럽터를 묘사함.
- filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 함
3.5.2 같은 람다, 다른 함수형 인터페이스
- 대상 형식이라는 특징 때문에 같은 람다 표현식이라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있음.
- 람다의 바디에 일반 표현식이 있으면, void를 반환하는 함수 디스크럽터와 호환됨.
3.5.3 형식 추론
- 자바 컴파일러는 람다 표현식이 사용된 콘텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론함.
// 형식 추론 X
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// 형식 추론
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
- 상황에 따라 명시적으로 형식을ㅇ 포함하는 것이 좋을 때도 있고 배제하는 것이 가독성을 향상시킬 때도 있으므로,어떤 방법이 좋은지 정해진 규칙은 없음.
3.5.4 지역 변수 사용
- 람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수(파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용할 수 있음. 이와 같은 동작을 람다 캡처링이라고 부름
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
- 람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처할 수 있지만, 그러려면 그 변수는 명시적으로 final로 선언되어 있어야 하거나 실질적으로 final로 선언된 변수와 똑같이 사용되어야 함. (effective final)
3.6 메서드 참조
- 메서드 참조를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있음.
3.6.1 요약
- 메서드 참조는 특정 메서드만을 호출하는 람다의 축약형이라고 생각할 수 있음.
- 메서드 참조를 이용하면 기존 메서드 구현으로 람다 표현식을 만들 수 있음. 이때 명시적으로 메서드명을 참조함으로써 가독성을 높일 수 있음.
// 람다
(Apple apple) -> apple.getWeight()
// 메서드 참조 단축 표현
Apple::getWeight
- 메서드 참조 만드는 방법
- 정적 메서드 참조 (Integer의 parseInt() -> Integer::parseInt)
- 람다 : (args) -> ClassName.staticMethod(args)
- 메서드참조: ClassName::staticMethod
- 다양한 형식의 인스턴스 메서드 참조 (String의 length() -> String::length)
- 람다 : (arg0, rest) -> arg0.instanceMethod(rest)
- 메서드참조 : ClassName::instanceMethod
- 기존 객체의 인스턴스 메서드 참조
- 람다 : (args) -> expr.instanceMethod(args)
- 메서드참조 : expr::instanceMethod
- 정적 메서드 참조 (Integer의 parseInt() -> Integer::parseInt)
3.6.2 생성자 참조
- ClassName::new처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 참조를 만들 수 있음.
// 생성자 참조 사용
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
// 기존 람다 표현식
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
// 생성자 참조 사용
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c1.apply(110);
// 기존 람다 표현식
Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Apple a2 = c2.get();
// 생성자 참조 사용
BiFunction<Color, Integer, Apple> c3 = Apple::new;
Apple a3 = c3.apply(GREEN, 110);
// 기존 람다 표현식
BiFunction<Color, Integer, Apple> c3 = (color, weight) -> new Apple(color, weight);
Apple a3 = c3.get();
3.7 람다, 메서드 참조 활용하기
3.7.1 1단계 : 코드 전달
- 자바 8의 List API에서 제공하는 sort 메서드의 정렬 동작을 전달하기 위해 Comparator를 구현한 클래스의 인스턴스를 생성해서 파라미터로 전달함.
- sort의 동작은 파라미터화됨.
public class AppleComparator implements Comparator<Apple> {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
}
// 1단계 Comparator 객체를 인수로 받아 두 사과를 비교
inventory.sort(new AppleComparator());
3.7.2 2단계 : 익명 클래스 사용
// 2단계 익명 클래스를 이용해서 Comparator를 인수로 넘김
inventory.sort(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
3.7.3 3단계 : 람다 표현식 사용
- 추상 메서드의 시그니처(함수 디스크립터)는 람다 표현식의 시그니처를 정의함.
- Comparator의 함수 디스크립터는 (T, T) -> int임.
// 3단계 람다 표현식 사용
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
- 자바 컴파일러는 람다 표현식이 사용된 콘텍스트를 활용해서 람다의 파라미터 형식을 추론하므로 아래와 같이 더 축약 가능
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
- Comparator는 어떤 키로 비교를 할 것인지 Comparable 키를 추출하는 Function 함수를 인수로 받는 정적 메서드 comparing을 포함하므로 한번 더 간소화할 수 있음.
inventory.sort(comparing(apple -> apple.getWeight()));
3.7.4 4단계 : 메서드 참조 사용
// 4단계 메서드 참조 사용
inventory.sort(comparing(Apple::getWeight));
3.8 람다 표현식을 조합할 수 있는 유용한 메서드
3.8.1 Comparator 조합
- Comparator는 조합할 수가 있는데, 예로 한 기준으로는 컬렉션을 sorting할 수 없을 때, thenComparing으로 조합할 수 있음.
// Comparator 조합
// 디폴트 메서드인 reversed를 사용
// 무게를 내림차순으로 정렬하고 두 사과의 무게가 같으면 국가별로 정렬함
inventory.sort(comparing(Apple::getWeight)
.reversed()
.thenComparing(Apple::getCountry));
3.8.2 Predicate 조합
- Predicate는 negate, and, or 세 가지 조합 메서드를 제공함.
Predicate<Apple> redApple = apple -> Color.RED.equals(apple.getColor());
Predicate<Apple> notRedApple = redApple.negate();
// 프레디케이트를 조합해서 사용 가능
Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150);
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(apple -> apple.getWeight() > 150)
.or(apple -> Color.GREEN.equals(apple.getColor()));
3.8.3 Function 조합
- Function 인터페이스는 Function 인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메서드를 제공함.
- andThen 메서드는 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환.
- compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공.
// Function 인터페이스도 조합 가능
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h1 = f.andThen(g);
Function<Integer, Integer> h2 = f.compose(g);
int result1 = h1.apply(1); // 4
int result2 = h2.apply(1); // 3
3.9 비슷한 수학적 개념
3.10 마치며
- 람다 표현식은 익명 함수의 일종. 이름은 없지만, 파라미터 리스트, 바디, 반환 형식을 가지며 예외를 던질 수 있음.
- 람다 표현식은 간결한 코드를 구현할 수 있음.
- 함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스
- 함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용할 수 있음
- 람다 표현식을 이용해서 함수형 인터페이스의 추상 메서드를 즉석에서 제공할 수 있으며, 람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급됨
- java.util.function 패키지는 Predicate<T>, Function<T, R>, Supplier<T> 등을 포함해서 자주 사용하는 다양한 함수형 인터페이스를 제공함
- 자바 8d은 Predicate<T>와 Function<T, R>과 같은 제네릭 함수형 인터페이스와 관련한 박싱 동작을 피할 수 있는 IntPredicate, IntToLongFunction 등과 같은 기본형 특화 인터페이스도 제공함
- 메서드 참조를 이용하면 기존의 메서드 구현을 재사용하고 직접 전달할 수 있음
- Comprator, Predicate, Function 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공함.
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 프로그래머스
- non-blocking
- Handler Interceptor
- 프로그래머스 Level 2
- 논블로킹
- 코딩테스트 고득점 Kit
- 스택/큐
- 블로킹
- 필터
- http://www.nextree.co.kr/p6960/
- 해시
- Filter
- Synchronous
- 인터셉터
- blocking
- 프로그래머스 Level 1
- 프로그래머스 Level 3
- a
- 비동기
- Asynchronous
- 동기
- 핸들러 인터셉터
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
글 보관함