3.1 람다란 무엇인가?
람다 표현식: 메서드로 전달할 수 있는 익명 함수를 단순화한 것
람다의 특징
- 익명: 메서드에 이름이 없음
- 함수: 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트를 가질 수 있음
- 전달: 메서드 인수로 전달하거나 변수로 저장 가능
- 간결성: 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요 없음
람다 이용 예시
// 익명 클래스
Comparator<Apple> byWeight = new Comparator<Apple>(){
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
};
// 람다
Comparator<Apple> byWeight = (Apple a1, Apple a2)-> a1.getWeight().compareTo(a2.getWeight());
람다 구성
- 파라미터 리스트: 메서드의 파라미터
- ex) (Apple a1, Apple a2)
- 화살표: 파라미터 리스트와 바디를 구분함
- ex) ->
- 람다 바디: 사과 무게를 비교하는 부분, return이 함축되어 있으므로 return 문을 명시적으로 표현하지 않아도 됨
- ex) a1.getWeight().compareTo(a2.getWeight());
람다 예제
(String s) -> s.length()
(Apple a) -> a.getWeight() > 150
(int x, int y) -> {
System.out.println("Result: ");
System.out.println(x+y);
}
() -> 42
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
() -> new Apple(10)
3.2 어디에, 어떻게 람다를 사용할까?
함수형 인터페이스 문맥에서 람다 표현식을 사용할 수 있음
함수형 인터페이스
정확히 하나의 추상 메서드만 지정
public interface Predicate<T> {
boolean test (T t);
}
public interface Comparator<T> {
int compare(T o1, T o2);
}
public interface Runnable {
void run();
}
public interface ActionListener extends EventListener{
void actioniPerformed(ActionEvent e);
}
public interface Callable<V> {
V call() throws Exception;
}
public interface PrivilegedAction<T> {
T run();
}
함수형 인터페이스로 뭘 할 수 있을까?
람다 표현식을 함수형 인터페이스를 구현한 클래스의 인스턴스로 취급할 수 있음
함수 디스크립터
람다 표현식의 시그니처를 서술하는 메서드
ex) Runnable 인터페이스의 run은 인수와 반환값이 없으므로, 인수와 반환값이 없는 시그니처로 생각할 수 잇음
* 함수의 시그니처: 매개변수 리스트, 반환타입
3.3 람다 활용: 실행 어라운드 패턴
- 자원 처리(데이터베이스의 파일 처리)에 사용하는 순환 패턴: 자원 열기 → 처리 → 자원 닫음
- 설정(setup)과 정리 과정은 대부분 유사
- 실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸는 형태
// Java 7의 try-with-resources 구문
// 자원을 명시적으로 닫을 필요 X
// 파일에서 한 행을 읽는 코드
public String processFile() throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader("data.txt"))){
return br.readLine();
}
}
1단계) 동작 파라미터화를 기억하라
파일에서 한 번에 두줄을 읽으려면?
기존의 설정, 정리과정은 재사용하고 processFile 메서드만 다른 동작을 수행하도록 명령하면 좋을 것
processFile 동작을 파라미터화하는 것: processFile 메서드로 BufferedReader를 이용해서 다른 동작을 수행하도록 동작을 전달
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());
2단계) 함수형 인터페이스를 이용해서 동작 전달
@FunctionalInterface
public interface BufferedReaderProcessor{
String process(BufferedReader b) throws IOException;
}
public String processFile(BufferedReaderProcessor p) throws IOException{
...
}
3단계) 동작 실행
BufferedReaderProcessor에 정의된 process 메서드의 시그니처와 일치하는 람다를 전달할 수 있음
람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으며 전달된 코드는 함수형 인터페이스의 인스턴스로 전달된 코드와 같은 방식으로 처리함
public String processFile(BufferedReaderProcessor p) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
return p.process(br);
}
}
4단계) 람다 전달
String oneLine = processFile((BufferedReader br)->br.readLine());
String twoLines = processFile((BufferedReader br)->br.readLine() + br.readLine());
3.4 함수형 인터페이스 사용
함수 디스크립터: 함수형 인터페이스의 추상 메서드 시그니처
Predicate 인터페이스
java.util.function.Predicate<T>
boolean을 반환하는 test 추상 메서드 정의
제네릭 형식 T의 객체를 인수로 받아 불리언 반환
@FunctionalInterface
public interface Predicate<T>{
boolean test(T t);
}
public <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 = filter(listOfStrings, nonEmptyStringPredicate);
Consumer 인터페이스
java.util.function.Consumer<T>
void를 반환하는 accept 추상 메서드 정의
제네릭 형식 T 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 사용
@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
public <T> void forEach(List<T> list, Consumer<T> c){
for(T t: list){
c.accept(t);
}
}
forEach(
Array.asList(1,2,3,4,5), (Integer i)->System.out.println(i)
);
Function 인터페이스
java.util.function.Function<T, R>
제네릭 형식 T를 인수로 받아서 제네릭 형식 R 객체를 반환하는 추상 메서드 apply 정의
입력을 출력으로 매핑하는 람다를 정의할 때 활용함
@FunctionalInterface
public interface Function<T, R>{
R apply(T t);
}
public <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;
}
// [7,2,6]
List<Integer> l = map(
Arrays.asList("lambdas", "in", "action), (String s)->s.length()
);
기본형 특화
자바의 형식에는 참조형과 기본형이 있음
제네릭 파라미터에는 참조형만 사용 가능
그래서 기본형을 참조형으로 변환하는 박싱 기능을 제공하고,
박싱이 자동으로 이루어지는 언박싱 기능도 제공함
근데 이러한 변환 과정은 비용 소모가 있음
박싱한 값은 기본형을 감싸는 wrapper이고 힙에 저장됨
따라서 박싱한 값은 메모리를더 소비하며 기본형을 가지고 올 때도 메모리탐색 과정이 필요함
그래서 Java8에서는 기본형을 입출력으로 사용하는 상황에서
오토박싱 동작을 피할 수 있도록 특별한 버전의 함수 인터페이스를 제공함
public interface IntPredicate{
boolean test(int t);
}
IntPredicate evenNumbers = (int i) -> i % 2 == 0;
evenNumbers.test(1000); // True, 박싱 없음
Predicate<Integer> oddNumbers = (Integer i) -> i % 2 != 0;
oddNumbers.test(1000); // False, 박싱함
'백엔드 > 모던자바인액션' 카테고리의 다른 글
ch15. CompletableFuture와 리액티브 프로그래밍 컨셉의 기초 (0) | 2023.03.23 |
---|---|
[ch4] 스트림 소개 (0) | 2022.09.19 |
[ch3] 람다 표현식 (2) (0) | 2022.09.03 |
[ch2] 동작 파라미터화 코드 전달하기 (0) | 2022.09.01 |
ch1. 자바 8, 9, 10, 11 무슨 일이 일어나고 있는가? (0) | 2022.07.23 |
댓글