/////
Search
Duplicate
4️⃣

함수형 인터페이스 사용

: 함수형 인터페이스에 정의된 추상 메서드는 람다 표현식의 시그니처를 정의한다. 그리고 이 시그니처를 함수 디스크립터라고 한다.
: 다양한 람다 표현식을 사용하려면 공통의 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요하다.
⇒ 뭔가 포괄적으로 사용하는 마스터키같은 느낌보다는 화장실 관련 문을 연다거나 창고 관련 문을 여는 키가 필요하다는 느낌같다..
: 자바 8 라이브러리 설계자들은 java.util.function 패키지로 여러 가지 새로운 함수형 인터페이스를 제공한다. 이 절에는 Predicate, Consumer, Function 인터페이스를 설명한다.

1. Predicate

: Predicate<T> 인터페이스는 제네릭 형식 T의 객체를 인수로 받아 불리언을 반환하는 test라는 추상 메서드를 정의한다.
: T 형식의 객체를 사용하는 불리언 표현식이 필요한 상황에서 Predicate 인터페이스를 바로 사용할 수 있다.
: 다음과 같이 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(t); } } return results; } Predicat<String> nonEmptyStrinPredicate = (String s) -> !s.isEmpty(); List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
Java
복사

2. Consumer

: Consumer<T> 인터페이스는 제네릭 형식 T 객체를 받아서 void를 반환하는 accept라는 추상 메서드를 정의한다.
: 제네릭 형식 T의 객체를 인수로 받아 어떠한 동작을 수행하고 싶을 때 Consumer 인터페이스를 사용할 수 있다.
: 다음은 Integer 리스트를 인수로 받아 각 항목에 forEach를 이용하여 리스트의 모든 항목을 출력하는 예제이다.
@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) );
Java
복사

3. function

: Function<T,R> 인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R 객체를 반환하는 추상 메서드 apply를 정의한다.
: 입력을 출력으로 매핑하는 람다를 정의할 때 Function 인터페이스를 활용할 수 있다.
: 다음은 String 리스트를 인수로 받아 각 String의 길이를 포함하는 Integer 리스트로 변환하는 map 메서드를 정의하는 예제이다.
@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"), (Strin s) -> s.length());
Java
복사

기본형 특화

: 지금까지 세 개의 제네릭 형식을 인수로 받아 처리하는 함수형 인터페이스를 살펴봤다. 하지만 특화된 형식의 함수형 인터페이스도 있다.
: 자바의 모든 형식은 참조형 아니면 기본형에 해당하는데, 제네릭 파라미터를 인수로 받게되면 참조형만 사용할 수 있다. 그렇다면..? 기본형은 못쓰는 걸까?
: 자바에서는 기본형을 참조형으로 변환하는 기능을 제공하는데, 이를 박싱이라고 한다. 참조형을 기본형으로 반환하는 반대 동작을 언박싱이라고 한다.
: 또한 프로그래머가 편리하게 코드를 구현할 수 있게 박싱과 언박싱이 자동으로 이루어지는 오토박싱이라는 기능도 제공한다. 예를 들어 다음은 유효한 코드이다.
List<Integer> list = new ArrayList<>(); for(int k=0; k<500; k++) { list.add(k); // int가 Integer로 언박싱됨 }
Java
복사
: 하지만 우린 알고 있다. 대가없는 쾌락은 없다는 것을.. 이 작업은 비용이 소모된다. 박싱한 값은 기본형을 감싸는 래퍼며 힙에 저장된다. 따라서 박싱한 값은 메모리를 참조형보다 더 소모하며 기본형을 가져올 때도 메모리를 탐색하는 과정이 필요하다.
: 자바 8에서는 기본형을 입출력으로 사용하는 상황에서 오토박싱을 피할 수 있도록 특별한 버전의 함수형 인터페이스를 제공한다.
: 예를 들어 아래 예제에서 IntPredicate는 1000이라는 값을 박싱하지 않지만 Predicate<Integer>는 1000이라는 값을 Integer 객체로 박싱한다.
public interface IntPredicate { boolean test(int t); } IntPredicate evenNumbers = (int i) -> i % 2 == 0; evenNumbers.test(1000); // 참(박싱 없음) Predicate<Integer> oddNumbers = (Integer i) -> i % 2 != 0; oddNumbers.test(1000); // 거짓(박싱)
Java
복사
: 일반적으로 특정 형식을 입력으로 받는 함수형 인터페이스의 이름 앞에는 DoublePredicate, IntConsumer와 같이 형식명이 붙는다.
: 다음은 함수형 인터페이스와 람다를 요약한 것이다.
사용 사례
람다 예제
대응하는 함수형 인터페이스
불리언 표현
(List<String> list) → list.isEmpty()
Predicate<List<String>>
객체 생성
() → new Apple(10)
Supplier<Apple>
객체에서 소비
(Apple a) → System.out.println(a.getWeight())
Consumer<Apple>
객체에서 선택/추출
(String s) → s.length()
Function<String,Integer> 또는 ToIntFunction<String>
두 값 조합
(int a, int b) → a * b
IntBinaryOperator
두 값 비교
(Apple a1, Apple a2) → a1.getWeight().compareTo(a2.getWeight())
Comparator<Apple> 또는 BiFunction<Apple, Apple, Integer> 또는 ToIntBiFunction<Apple, Apple>