티스토리 뷰

Funtional Interface

  • 함수형 인터페이스는 오직 하나의 추상 메서드를 지정하는 인터페이스 입니다.
  • 함수형 인터페이스의 추상 메서드 시그니처를 함수 디스크립터라고 합니다.
    • 쉽게는 T -> boolean, T -> R등과 같이 메서드를 람다로 표현한 형태를 말함
  • 자바 8 라이브러리 설계자 들은 java.util.function 패키지에 여러가지 함수형 인터페이스를 제공합니다.
    • 이번 글에서는 Predicate, Consumer, Function 인터페이스를 자세히 소개할 예정
  • 첨언을 해보자면 저의 경우 실무에서 아주 유용하게 사용하고 있는 녀석입니다.
    • 마치 함수 포인터를 전달하듯 클래스에 정적인 데이터가 아니라 어떠한 동작을 전달하고 싶을 때 간단하게 주입해줄 수 있는 용도로 요긴하기 쓰임 ..   

Java API 함수형 인터페이스

Predicate

  • java.util.function.Predicate<T>인터페이스는 test라는 추상 메서드를 정의하며 제네릭 형식 T의 객체를 인수로 받아 boolean을 반환합니다.
  • T 형식의 객체를 사용하는 불리언 표현식이 필요한 상황에서 Predicate 인터페이스를 사용할 수 있음
    • 스트림 사용시 filter 메서드의 인자가 Predicate임
  • 저의 경우는 Predicate를 직접 전달하는 경우는 흔치는 않았습니다.
@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;
}

Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmptyStringList = filter(stringList, nonEmptyStringPredicate);

 

Consumer

  • java.util.function.Consumer<T>인터페이스는 제네릭 형식 T객체를 받아서 void를 반환하는 accept라는 추상 메서드를 정의합니다.
  • T 형식의 객체를 인수로 받아 동작을 수행하고자 할 때 사용할 수 있습니다.
    • 저의 경우는 외부 컬렉션에 데이터를 추가/삭제 하고자 하는 경우 (외부 컬렉션이 바뀔 수 있는 상황)
@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);
)
//Collector 클래스
//myQueue가 변경될 여지가 있음
Queue<MyData> myQueue;

void setQueue(Queue<MyData> queue) {
	myQueue = queue;
}

//외부 데이터 입력 클래스
Consumer<MyData> dataConsumer = (MyData data) -> myQueue.add(data);

void updateData(MyData data) {
	dataConsumer.accept(data);
}

 

Function

  • java.util.function.Function<T,R>인터페이스는 제네릭 T 를 받아 제네릭 R을 반환하는 apply추상 메서드를 정의합니다.
  • 일반적인 함수의 형태로 입력, 출력을 모두 매핑하는 람다를 정의할 때 사용합니다.
  • Function의 경우 특정 케이스가 있다기 보다는 가장 일반적으로 많이 사용되는 것 같습니다.
    • 아래 예제 처럼 데이터를 어떠한 패턴으로 변형을 시켜야 하는 경우에도 사용
@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;
}
List<Integer> list = map(
    Arrays.asList("lambdas", "in", "action"),
    (String s) -> s.length()
);

 

Supplier

  • java.util.function.Supplier<T>인터페이스는 제니릭 T를 반환하는 추상메서드 get을 정의합니다.
    • 책에는 소개되지 않지만 많이 쓰여서 넣었습니다..
  • 말그대로 T 형태를 출력만을 가지고 있는 공급의 역할이 주된 인터페이습니다.
  • 저의 경우는 데이터를 넘겨줄 때 변경될 여지가 있는 데이터 일때 변경에 영향이 없게 하기 위하여 많이 사용하엿습니다. (예제 참고)
    • 데이터를 참조하는 것이 아닌 Supplier를 이용하여 그때 그때 데이터를 공급받아 사용하는 방식  
  @FunctionalInterface
  public interface Supplier<T> {
    T get();
  }

  class Config {
    //value..
  }

  class Service {
    Supplier<Config> configSupplier;
    public Service(Supplier<Config> configSupplier) {
      this.configSupplier = configSupplier;
    }

    public void doSomething() {
      //Config가 외부에서 변경되어도 영향이 없음
      Config config = configSupplier.get();
    }
  }

 

기본형 특화

  • 위 함수형 인터페이스에 Java 기본형을 사용하는 경우 어쩔 수 없이 참조형 <-> 기본형 사이 언/박싱 일어나게 됩니다.
    • 기본으로 참조형인 제니릭 타입 T를 사용하기 때문
  • 물론 Java에서 제공해주는 오토박싱으로 인해 문제는 없지만 이러한 변환과정에서 메모리 소비가 발생합니다.
  • 그래서 Java8에서는 오토박싱을 피할 수 있도록 기본형을 사용하는 함수형 인터페이스를 제공합니다.
    • IntPredicate, IntConsumer, IntFunction 등등

 

Java 함수형 인터페이스 정리

 

함수형 인터페이스 함수 디스크립터 기본형 특화
Predicate<T> T -> boolean IntPredicate, LongPredicate,
Double Predicate
Consumer<T> T -> void IntConsumer, LongConsumer,
DoubleConsumer
Function<T, R> T -> R IntFunction<R>,
IntToDoubleFunction,
IntToLongFunction,
LongFunction<R>,
LongToDoubleFunction,
LontToIntFunction,
DoubleFunction<R>,
DoubleToIntFunction,
DoubleToLongFunction,
ToIntFunction<T>,
ToDoubleFunction<T>,
ToLongFunction<T>
Supplier<T> () -> T BooleanSupplier, IntSupplier,
LongSupplier, DoubleSupplier
UnaryOperator<T> T -> T IntUnaryOperator,
LongUnaryOperator,
DoubleUnaryOperator
BinaryOperator<T> (T, T) -> T IntBinaryOperator,
LongBinaryOperator,
DoubleBinaryOperator
BiPredicate<L, R> (T, U) -> boolean  
BiConsumer<T, U> (T, U) -> void ObjIntConsumer<T>,
ObjLongConsumer<T>,
ObjDoubleConsumer<T>
BiFunction<T, U, R>  (T, U) -> R ToIntBiFunction<T, U>
ToLongBiFunction<T, U>
ToDoubleBiFunction<T, U>

참고

 

모던 자바 인 액션

자바 1.0이 나온 이후 18년을 통틀어 가장 큰 변화가 자바 8 이후 이어지고 있다. 자바 8 이후 모던 자바를 이용하면 기존의 자바 코드 모두 그대로 쓸 수 있으며, 새로운 기능과 문법, 디자인 패턴�

www.yes24.com

 

반응형
댓글