반응형
람다란?
메소드로 전달할 수 있는 익명 함수를 단순화한 것.
람다의 키 포인트
- 익명
- 함수
- 람다는 메소드처럼 특정 클래스에 종속되지 않으므로 함수로가 부른다.
- 전달
- 람다 표현식을 메소드 인수로 전달하거나 변수로 저장할 수 있다.
- 간결성
- 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요가 없다.
// 기존의 익명 클래스을 이용한 코드
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
}
// 람다를 이용한 코드
Comparator<Apple> byWeight = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
람다 표현식
(Apple a1, Apple a2) → a1.getWeight().compareTo(a2.getWeight());
- 파라미터 리스트
- 화살표
- 람다 바디
람다는 어디에 사용할 수 있을까?
- 함수형 인터페이스라는 문맥에서 람다 표현식을 사용할 수 있다.
함수형 인터페이스란?
- 정확히 하나의 추상 메소드를 지정하는 인터페이스. (default method는 추상메소드가 아님)
@FunctionalInterface // 선택
public interface Predicate<T> {
boolean test (T t);
}
@FunctionalInterface
- 함수형 인터페이스임을 가르키는 어노테이션
- 함수형 인터페이스가 아니면 컴파일러가 에러를 발생시킨다.
- 예를 들어 추상 메소드가 두 개 이상이면 에러가 발생.
함수 디스크립터
- 람다 표현식의 시그니처를 서술하는 메소드
- ex) (Apple, Apple) → int
람다 활용: 실행 어라운드 패턴
- 실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸인 형태
public String processFile() throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return br.readLine(); // 실제 작업 (위 그림에서 작업 A, 작업 B)
}
}
1. 동작 파라미터화를 기억하라
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());
2. 함수형 인터페이스를 이용해서 동작 전달
@FunctionalInterface
public interface BUfferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
public String processFile(BuffereedReaderProcessor p) throws IOException {
...
}
3. 동작 실행
public String processFile(BufferedProcessor 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());
함수형 인터페이스 사용
- Predicate T - > boolean
(List<String> list) -> list.isEmpty()
- Consumer T -> void
(Apple a) -> System.out.println(a.getWeight())
- Function<T, R> T -> R
(String s) -> s.length
- Supplier () -> T
() -> new Apple(10)
- UnaryOperator T -> T
(Integer i) -> i + 1
이외에도..
- BinaryOperator (T, T) -> T
- BiPredicate<T, U> (T, U) -> boolean
- BiConsumer<T, U> (T, U) -> void
- BiFunction<T, U, R> (T, U) -> R
기본형 함수형 인터페이스
- 제네릭 파라미터에는 내부 구현 때문에 참조 형만 사용할 수 있다. (Consumer에서 T)
- 자바 기본형 -> 참조형으로 변환하는 기능 (박싱, 반대는 언박싱)
- but..! 변환 과정은 비용이 소모된다. 박싱 한 값을 기본형을 감싸는 래퍼며 힙에 저장됨.
- 따라서 박싱한 값은 메모리를 더 소비하며, 기본형을 가져올 때도 메모리를 탐색하는 과정이 필요. 따라서 기본형 함수형 인터페이스인 IntPredicate 등을 자바에서 따로 제공.
형식 검사
람다가 사용되는 Context를 이용해서 람다의 형식을 추론할 수 있다. 어떤 콘텍스트에서 기대되는 람다 표현식의 형식을 대상 형식이라고 부른다.
List<Apple> result=filter(inventory,(Apple apple)->apple.getWeight() > 150);
1. 람다가 사용된 콘텍스트는 무엇인가? 우선 filter의 정의를 확인하자
filter(List<Apple> inventory, Predicate<Apple> p)
2. 대상 형식은 Predicate이다. (T는 Apple로 대치됨.)
3. Predicate 인터페이스의 추상 메소드는 무엇인가?
boolean test(Apple apple)
4. Apple을 인수로 받아 boolean을 반환하는 test 메소드다.
Apple -> booelan
5. 함수 디스크립터는 Apple -> boolean 이므로 람다의 시그니처와 일치한다. 람다도 Apple을 인수로 받아 boolean을 반환하므로 코드 형식 검사가 성공적으로 완료된다.
- 이러한 대상 형식이라는 특징 때문에 같은 람다 표현식이라도 호환되는 추상 메소드를 가진 다른 함수형 인터페이스로 사용될 수 있다.
형식 추론
- 자바 컴파일러는 람다 표현식이 사용된 콘텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다. 즉 대상 형식을 이용해서 함수 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그니처도 추론할 수 있다.
// 형식 추론을 하지 않음
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// 형식 추론
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
지역 변수 사용
- 람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수를 활용할 수 있다.
- 여기서 자유 변수란 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수
- 이와 같은 동작을 람다 캡처링이라고 부른다.
int portNumber = 1337;
Runnable r1 = () -> System.out.println(portNumber); // 자율변수를 활용
- 단 제약은 있다.
- 지역 변수는 명시적으로 final로 선언되어 있거나, final로 선언된 변수와 똑같이 사용되어어야 한다. 즉 불변성을 보장해야 한다.
- 왜??
- 우선 내부적으로 인스턴스 변수와 지역변수는 태생부터 다름.
- 인스턴스 변수 -> 힙에 저장, 지역 변수 -> 스택에 위치
- 람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있음.
- 따라서 자바 구현에서는 원래 변수에 접근을 허용하는 것이 아니라, 자유 지역 변수의 복사본을 제공한다.
- 따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에서는 한 번만 값을 할당해야 한다는 제약이 생김
메소드 참조
- 특정 람다 표현식을 축약한 것
// 기존의 람다 표현식
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
// 메소드 참조
inventory.sort(Comparator.comparing(Apple::getWeight));
- 위를 비교해 보면 알 수 있듯이 메소드 참조를 사용해서 가독성을 높일 수 있다.
람다 표현식을 조합할 수 있는 유용한 메소드
- 자바 8에서 Compartor, Function, Predicate, Consumer 등 함수형 인터페이스에서 다양한 유틸리티 메소드를 제공한다.
- 근데 함수형 인터페이스는 추상 메소드가 하나만 있어야 하는데 이게 가능할까?라고 의문이 들 수 있는데
- 그건 바로 디폴트 메소드를 통해 유틸성 메소드를 제공하기 때문에 이것이 가능해진다.
1. Compartor 역 정렬
inventory.sort(Comparator.comparing(Apple::getWeight).reversed());
2. thenComparing
- 첫 번째 비 교자를 이용해서 두 객체가 같다고 판단되면 두 번째 비교자에 객체를 전달할 수 있다.
inventory.sort(Comparator.comparing(Apple::getWeight)
.reversed()
.thenComparing(Apple::getCountry)); // 두 사과의 무게가 같으면 국가별로 정렬
3. Predicate의 or, and, netgate
Predicate<Apple> notRedApple = redApple.negate(); // 결과를 반전
Predicate<Apple> redAndHeavyApple = redApple
.and(apple -> apple.getWeight() > 150);
Predicate<Apple> redAndHeavyApple = redApple
.or(apple -> apple.getWeight() > 150);
람다 정리
- 람다 표현식은 익명 함수의 일종이다. 이름은 없지만, 파라미터 리스트, 바디, 반환 형식을 가지며 예외를 던질 수 있다.
- 람다 표현식으로 간결한 코드를 구현 가능
- 함수형 인터페이스는 하나의 추상 메소드만을 정의하는 인터페이스 (디폴트 메소드 제외)
- 함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용할 수 있다.
- 람다 표현식을 이용해서 함수형 인터페이스의 추상 메소드를 즉석으로 제공할 수 있으며, 람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다.
- 람다 표현식의 기대 형식을 대상 형식이라고 한다.
- 메소드 참조를 이용하면 기존의 메소드 구현을 재사용하고 직접 전달할 수 있다.
- Comparator, Predicate, Function 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메소드를 제공한다.
반응형
'Application > JAVA & Kotlin' 카테고리의 다른 글
Java Thead (1) | 2021.07.31 |
---|---|
JVM이란? JVM의 구조 간단 정리 (0) | 2021.03.26 |
[모던 자바 인 액션 정리] 7장. 병렬 데이터 처리와 성능 (2) | 2020.12.30 |
[모던 자바 인 액션 정리] 4장. 스트림 소개 (0) | 2020.12.30 |
[모던 자바 인 액션 정리] 2장. 동작 파라미터화 코드 (0) | 2020.12.24 |