함수형 인터페이스
람다식 = 익명 클래스의 객체와 동일하다
인터페이스 : 단 하나의 추상 메서드만 선언
람다식을 다루기 위한 인터페이스 : 함수형 인터페이스 (functional interface)
-> 함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있다
@FunctionalInterface를 붙이면 컴파일러가 함수형 인터페이스를 올바르게 정의했는지 확인한다
함수형 인터페이스 타입의 매개변수와 반환타입
메서드의 매개변수가 MyFunction 타입이면 이 메서드를 호출할 때 람다식을 참조하는
참조변수를 매개변수로 지정해야 한다
참조변수 없이 람다식을 매개변수로 지정
aMethod( () -> System.out.println("myMethod()"));
람다식의 타입과 형변환
함수형 인터페이스로 람다식을 참조할 수 있지만 타입이 일치하지는 않는다
익명객체(람다식)는 타입이 없다 //있지만 컴파일러가 임의로 정함
타입 일치를 위한 형변환
MyFunction f = (MyFunction) ( () -> { }); //양변의 타입이 다르다
Object obj = (Object) ( () -> { }) //함수형 인터페이스로만 형변환 가능
Object obj = (MyFunction) (() -> { });
java.util.function 패키지
주요 함수형 인터페이스
함수형 인터페이스 | 메서드 | 설명 |
java.lang Runnable |
void run() | 매개변수도 없고, 반환값도 없음 |
Supplier<T>//공급자 | T get() ->T | 매개변수는 없고, 반환값만 있음 |
Consumer<T>//소비자 | T-> void accept(T t) | Supplier와 반대로 매개변수만 있고 반환값이 없음 |
Function<T,R>//함수 | T-> R apply(T t) ->R | 일반적인 함수, 하나의 매개변수를 받아서 결과를 반환 |
Predicate<T>//조건식 | T-> boolean test(T t) ->boolean |
조건식을 표현하는데 사용됨 매개변수는 하나. 반환타입은 boolean |
Predicate
조건식의 표현에 사용된다
Function의 변형으로 반환타입이 boolean이다
매개변수가 2개인 함수형 인터페이스
이름 앞에 접두사 Bi가 붙는다
함수형 인터페이스 | 메서드 | 설명 |
BiConsumer<T,U> | T,U -> void accept(T t, U u) | 두 개의 매개변수만 있고 반환값이 없음 |
BiPredicate<T,U> | T,U -> boolean test(T t, U u) -> boolean | 조건식을 표현하는데 사용 매개변수는 둘, 반환값은 boolean |
BiFunction<T,U,R> | T,U -> R apply(T t, U u) -> R | 두 개의 매개변수를 받아서 하나의 결과를 반환 |
UnaryOperator와 BinaryOperator
매개변수의 타입과 반환타입의 타입이 모두 일치한다
함수형 인터페이스 | 메서드 | 설명 |
UnaryOperator<T> | T-> T.apply(T t) -> T | Function의 자손, Function과 달리 매개변수와 결과의 타입이 같다 |
BinaryOperator<T> | T, T -> T.apply(T t, T t) -> T | BiFunction의 자손, BiFunction과 달리 매개변수와 결과의 타입이 같다. |
컬렉션 프레임워크와 함수형 인터페이스
컬렉션 프레임워크의 인터페이스에 다수의 디폴트 메서드가 추가됨
그 중 함수형 인터페이스를 사용하는 메서드들의 목록
함수형 인터페이스 | 메서드 | 설명 |
Collection | boolean removeIf(Predicate<E> filter) | 조건에 맞는 요소를 삭제 |
List | void replaceAll(UnaryOperator<E> operator) | 모든 요소를 변환하여 대체 |
Iterable | void forEach(Consumer<T> action) | 모든 요소에 작업 action을 수행 |
Map | V compute(K key, BiFunction<K,V,V>f) | 지정된 키의 값에 작업 f를 수행 |
V computeIfAbsent(K key, Function<K,V>f) | 키가 없으면, 작업 f 수행 후 추가 | |
V computeIfPresent(K key, BiFunction<K,V,V>f) | 지정된 키가 있을 때, 작업 f 수행 | |
V merge(K key, V value, BiFunction<V,V,V>f) | 모든 요소에 병합작업 f를 수행 | |
void forEach(BiConsumer<K,V>action) | 모든 요소에 작업 action을 수행 | |
void replaceAll(BiFunction<K,V,V>f) | 모든 요소에 치환작업 f를 수행 |
기본형을 사용하는 함수형 인터페이스
함수형 인터페이스의 매개변수&반환값의 타입이 지네릭 타입
-> 기본형 타입의 값을 처리할때도 래퍼클래스를 사용 -> 비효율적
효율적인 처리를 위해 기본형을 사용하는 함수형 인터페이스
함수형 인터페이스 | 메서드 | 설명 |
DoubleToIntFunction | double-> int applyAsInt(double d) -> int | AToBFunction은 입력이 A타입 출력이 B타입 |
ToIntFunction<T> | T -> int applyAsInt(T value) -> int | ToBFunction은 출력이 B타입이다 입력은 지네릭 타입 |
IntFunction<R> | int -> R apply(T t, U u) -> R | AFunction은 입력이 A타입이고 출력은 지네릭 타입 |
ObjIntConsumer<T> | T, int -> void accept(T t, U u) | ObjAFunction은 입력이 T, A 타입이고 출력은 없다. |
Function의 합성과 Predicate의 결합
java.util.fuction패키지의 함수형 인터페이스에는 추상메서드 외에도
디폴트 메서드와 static 메서드가 정의되어 있다
Function과 Predicate에 정의된 메서드
Function default <V> Function<T, V> andthen(Function<? super R,? extends V> after) default<V> Function<V, R> compose(Function<? super V,? extends T> before) static <T> Function<T, T> identity() |
Predicate default Predicate<T> and(Predicate<? super T> other) default Predicate<T> or(Predicate<? super T> other) default Predicate<T> negate() static <T> Predicate<T> isEqual(Object targetRef) |
**Function 인터페이스는 두 개의 타입을 지정해 줘야하기 때문에, 두 타입이 같아도
Function<T>라고 쓸 수 없다. Function <T, T>라고 써야한다
메서드 참조
람다식이 하나의 메서드만 호출하는 경우에 메서드 참조로 람다식을 간략히 할 수 있다
예) 문자열을 정수로 변환하는 람다식
Function<String, Integer> f = (String s) -> Integet.parseInt(s);
클래스이름::메서드이름 || 참조변수::메서드이름
종류 | 람다 | 메서드 참조 |
static 메서드 참조 | (x) -> ClassName.method(x) | ClassName::method |
인스턴스 메서드 참조 | (obj.x)->obj.method(x) | ClassName::method |
특정 객체 인스턴스메서드 참조 | (x) ->obj.method(x) | obj::method |
**static메서드 참조와 인스턴스 메서드 참조를 자주 사용한다
생성자의 메서드 참조
생성자를 호출하는 람다식도 메서드 참조로 변환할 수 있다
Supplier<Myclass> s = () -> new MyClass(); //람다식
Supplier<Myclass> s = MyClass::new; //메서드 참조
스트림(stream)
데이터 소스를 추상화하고 데이터를 다루는데 자주 사용되는 메서드들을 정의한다
배열이나 컬렉션뿐만 아니라 파일에 저장된 데이터도 같은 방식으로 다룰 수 있다
스트림을 사용한 코드가 간결하고 이해하기 쉬우며 재사용성도 높다
스트림의 특징
-스트림은 데이터 소스를 읽기만하고 소스를 변경하지 않는다
-스트림은 Iterator처럼 일회용이다 한 번 사용하면 닫혀서 다시 사용할 수 없다
-지연된 연산
최종 연산 전까지 중간연산이 수행되지 않는다
-스트림은 작업을 내부 반복으로 처리한다 //내부반복 : 반복문을 메서드의 내부에 숨길 수 있다
-스트림의 연산
중간 연산과 최종 연산으로 분류한다
중간 연산 : 연산 결과가 스트림인 연산, 스트림에 연속해서 중간 연산할 수 있음
최종 연산 : 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능
-Stream<Integer>와 IntStream
오토박싱&언박싱으로 인한 비효율을 줄이기 위해 데이터 소스 요소를 기본형으로 다루는 스트림
IntStream, LongStream, DoubleStream
Stream<Integer>보다 IntStream의 사용이 더 효율적이다
-병렬 스트림
작업을 병렬로 처리한다