백엔드/Java의 정석

TIL 정리_26

ran4 2022. 3. 13. 19:26

함수형 인터페이스 

람다식 = 익명 클래스의 객체와 동일하다

인터페이스 : 단 하나의 추상 메서드만 선언

람다식을 다루기 위한 인터페이스 : 함수형 인터페이스 (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의 사용이 더 효율적이다

 

-병렬 스트림

작업을 병렬로 처리한다 

'백엔드 > Java의 정석' 카테고리의 다른 글

TIL 정리_28  (0) 2022.03.15
TIL 정리_27  (0) 2022.03.14
TIL 정리_25  (0) 2022.03.12
TIL 정리_24  (0) 2022.03.11
TIL 정리_23  (0) 2022.03.10