람다란
메서드로 전달할 수 있는 익명 함수를 단순화한 것
이름을 가지지는 않지만, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트는 가질 수 있음
람다의 특징
람다 구조
람다의 대표적인 사용
// 람다의 대표적인 사용 예시로 Predicate 함수형 인터페이스 파라미터로 람다식 전달
List<Apple> filterList = inventory.stream().filter(a -> a.getWeight() > 150);
함수 디스크립터
실행 어라운드 패턴
실제 자원을 처리하는 코드를 설정, 정리 두 과정이 둘러싸는 형태
즉, 하나의 로직을 수행할 때 첫번째로 초기화/준비 코드가 수행 → 마지막에 정리/마무리 코드가 실행
위 코드 사이에 실제 자원을 처리하는 코드를 실행하는 것
예를 들면 try-with-resources 구문에서 설정, 정리 부분의 코드는 중복되므로 로직 구현코드만 바꾸면서 사용하는 패턴
예시 코드
// 아래과정을 통해 실행 어라운드 패턴으로 구현 가능
// 실제 작업을 하는 코드는 return 부분만 해당
public String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
// 아래 함수형 인터페이스 구현체를 위 processFile의 파라미터로 전달
// 이를 통해 동작 파라미터화를 통해 로직만을 바꿔 사용할 수 있음
// 동작 파라미터로는 (BufferedReader -> String) 디스크립터와 일치하는 람다 전달 가능
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
// 아래처럼 람다를 전다랗여 로직을 동작 파라미터화 가능
String oneLine = processFile((BuffredReader) -> br.readLine());
String twoLine = processFile((BuffredReader) -> br.readLine() + br.readLine());
대표적인 함수형 인터페이스
Predicate<T>
test()
라는 추상 메서드를 정의, test()
는 제네릭 형식 T의 객체를 인수로 받아 boolean 반환
예시 코드
// 예제는 String 객체를 인수로 받는 람다 정의
@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(p);
}
}
}
Consumer<T>
제네릭 형식 T 객체를 받아 void를 반환하는 accept()
추상 메서드 정의
예를 들어 Integer 리스트를 인수로 받아 각 항목에 동작을 수행하는 forEach 메서드 정의 가능
예시 코드
// 예제는 Integer 타입 리스트의 각 원소를 출력하는 람다 정의
@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(Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println(i));
Function<T, R>
제네릭 형식 T를 인수로 받아 제넥릭 형식 R 객체를 반환하는 apply()
추상 메서드를 가짐
예를 들면 Apple 객체를 인수로 받아 Integer 타입의 무게를 출력하는 함수
예시 코드
@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> results = new ArrayList<>();
for(T t : list) {
result.add(f.apply(t));
}
return result;
}
List<Integer> i = map(Arrays.asList("lambda", "in", "action"),
(String s) -> s.length());
기본형 특화
자바의 모든 형식은 참조형(Byte, Integer, Object, List) 아니면 **기본형(int, double, byte, char)**에 해당
하지만 제네릭 파라미터(Consumer<T>
의 T)에는 참조형만 가능 → 제네릭 내부 구현 때문에 어쩔 수 없는 일
이를 위해 자바는 기본형을 참조형으로 변환하는 기능 제공 → 이를 박싱이라고 함
이 반대의 동작을 언박싱이라고 함
개발자가 편리하게 사용할 수 있도록 방식/언박싱이 자동으로 이루어 지는데 이를 오토박싱이라고 함
이러한 박싱/언박싱을 사용하면 변환 과정에서 비용이 소모됨 → 박싱된 값은 기본형을 감싸는 래퍼며 힙에 저장됨
Java8에서는 기본형을 입출력으로 사용하는 상황에서 오토박싱을 피할 수 있도록 특별한 버전의 함수형 인터페이스 제공
예시 코드
public interface IntPredicate {
boolean test(int t);
}
// 박싱 없이 진행
IntPredicate evenNumbers = (int i) -> i % 2 == 0;
eventNumbers.test(1000);
// 박싱하여 진행
Predicate<Integer> oddNumbers = (Integer i) i % 2 != 0;
oddNumbers.test(1000);
Java8애 추가된 함수형 인터페이스
예외, 람다, 함수형 인터페이스 관계
함수형 인터페이스는 확인된 예외를 던지는 동작을 허용하지 않음
즉, 예외를 던지는 람다 표현식을 만드려면 확인된 예외를 던지는 함수형 인터페이스를 정의하거나 try/catch 블록으로 감싸야 함
예시 코드
// 확인된 예외를 던지는 함수형 인터페이스 정의
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
// try/catch를 이용해서 확인된 예외를 던지는 람다식
BufferedReaderProcessor p = (BufferedReader b) -> {
try {
return b.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
형식 검사, 형식 추론, 제약 다시 읽어 보기
메서드 참조
메서드 참조를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달 가능
때로는 람다식보다 메서드 참조가 더 가독성이 좋을 수 있음
예시 코드
// 메서드 참조를 사용하지 않은 코드
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
// 메서드 참조를 사용한 코드
// 실제 메서드를 사용하지 않으므로 괄호()를 사용하지 않아도 됨
inventory.sort(comparing(Apple::getWeight));
메서드 참조를 사용하는 방법
생성자 참조
정적 메서드 참조하는 방법과 유사, ClassName::new
를 통해 사용 가능
예시 코드
// 생성자 참조를 사용하지 않은 코드
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
// 생성자 참조를 사용한 코드
Supplier<Apple> c2 = Apple::new;
Apple a2 = c2.get();
// 생성자 파라미터 2개를 받는 생성자 참조
BiFunction<String, Integer, Apple> c3 = (color, weight) -> New Apple(color, weight);
Apple a3 = c3.get();
// 생성자 파라미터 3개를 받는 생성자 참조
TriFunction<String, Integer, Integer, Apple> c4 = (color, weight, order) -> new Apple(color, weight, order);
Apple a4 = c4.get();
부족하면 3.9 다시 보기