함수형 인터페이스 사용

반응형
728x90
반응형

함수형 인터페이스의 사용

함수형 인터페이스는 오직 1개의 추상 메서드를 지정하고, 이 추상 메서드는 람다 표현식의 시그니처를 묘사한다. 함수형 인터페이스의 추상 메서드의 시그니처를 함수 디스크립터라고 한다. 다양한 람다 표현식을 사용하려면 공통의 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요하다. 자바 API에 Comparable, Runnable, Callable 등의 다양한 함수형 인터페이스가 있지만 자바 8에 새로 추가된 함수형 인터페이스에 대해 알아보자.

 

 

Predicate

java.util.function.Predicate 인터페이스는 test라는 추상 메서드를 정의하며 test는 제네릭 형식 T의 객체를 인수로 받아 boolean 타입을 반환한다. 따로 정의할 필요 없이 바로 사용이 가능하다.

 

@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)) { // body : !t.isEmpty() (return: boolean)
      results.add(T);
    }
  }

  return results;
}

Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate); // 함수를 매개변수로 전달

 

 

Consumer

java.util.function.Consumer 인터페이스는 추상메서드 accept를 정의한다. accept 메서드는 제네릭 형식 T 객체를 받아서 void를 반환한다. 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); // body : System.out.println(i)
  }
}

forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i));

 

 

 

Function

java.util.fuction<T, R> 인터페이스는 추상메서드 apply를 정의한다. apply 메서드는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R 객체를 반환한다. 입력을 출력으로 매핑하는 람다를 정의할때 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)); // body : s.length()
  }

  return result;
}

List<Integer> l = map(
  Arrays.asList("kim", "seo", "hae"), (String s) -> s.length());
)

 

 

 

박싱, 언박싱, 오토박싱

자바에서 기본 개념 중에 박싱과 언박싱이 있다. 자바의 모든 형식은 참조형(Byte, Integer, Object, List 등)과 기본형(int, double, byte, char)에 해당한다. 우리가 지금까지 얘기한 제네릭 파라미터(Consumer 에서 T)는 참조형만 사용할 수 있다. 자바는 이를 위해서 아래 2개의 기능을 제공한다.

1) 박싱 : 기본형을 참조형으로 변환하는 기능
2) 언박싱 : 참조형을 기본형으로 변환하는 기능
3) 오토박싱 : 프로그래머가 편리하게 코드로 구현할 수 있도록 박싱과 언박싱을 자동으로 실행

 

List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++>) {
  list.add(i)
}

 

int 타입의 i를 add 메서드를 실행하여 Integer 타입의 List에 넣고있다. 이는 기본형(int)를 참조형(Integer)로 오토박싱이 이루어지는 예제이다. 하지만 이런 변환 과정은 비용이 소모된다. 박싱한 값은 기본형을 감싸는 래퍼며 힙에 저장되는데, 박싱한 값은 메모리를 더 소비하며 기본형을 가져올때도 메모리를 탐색하는 과정이 필요하기 때문이다.

 

public interface IntPredicate {
  boolean test(int t);
}

IntPredicate evenNumbers = (int i ) -> i % 2 == 0;
evenNumbers.test(1000); // true : 박싱 없음 

Predicate<Integer> oddNumbers = (Integer i) -> i % 2 != 0;
oddNumbers.test(1000); // false : 박싱

 

반적으로 특정 형식을 입력받는 함수형 인터페이스의 이름 앞에는 DoublePredicate, IntConsumer 처럼 형식명이 붙는다. Function 인터페이스는 ToIntFunction, IntToDoubleFunction 등의 다양한 출력 형식 파라미터를 제공한다.

 

반응형

'Coding > Java' 카테고리의 다른 글

람다의 메서드 참조  (0) 2020.11.06
람다의 형식 검사, 형식 추론, 제약  (0) 2020.11.06
빌더 패턴의 권장 이유  (0) 2020.11.06
실행 어라운드 패턴  (0) 2020.11.06
람다표현식에 대해 알아보기  (0) 2020.11.06

Designed by JB FACTORY