람다식(3) - 표준 API의 함수적 인터페이스

2023. 12. 9. 18:04java/java

 

 

자바에서 제공되는 표준 API에서 한 개의 추상 메소드를 가지는 인터페이스들은 모둔 람다식을 이용해서 익명 구현 객체로 표현이 가능하다.

public static void main(String[] args) {
    Runnable runnable = () -> {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    };
    
    Thread thread = new Thread(runnable);
    thread.start();
}

 

Thread 생성자를 호출할 때 다음과 같이 람다식을 매개값으로 대입해도 된다.

Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println(i);
    }
});

 

자바 8부터 빈번하게 사용되는 함수적 인터페이스는 java.util.function 표준 API 패키지로 제공한다. 이 패키지에서 제공하는 함수적 인터페이스의 목적은 메소드 또는 생성자의 매개 타입으로 사용되어 람다식을 대임할 수 있도록 하기 위해서이다.

 

java.util.function 패키지의 함수적 인터페이스는 크게 Cunsumer, Supplier, Function, Operator, Predicate로 구분된다. 구분 기준은 추상 메소드의 매개값과 리턴값의 유무이다.

 

종류 추상 메소드 특징
Cunsumer - 매개값은 있고, 리턴값은 없음
Supplier - 매개값은 없고, 리턴값은 있음
Function - 매개값도 있고, 리턴값도 있음
- 주로 매개값을 리턴값으로 매핑(타입 변환)
Operator - 매개값도 있고, 리턴값도 있음
- 주로 매개값을 연산하고 결과를 리턴
Predicate - 매개값도 있고, 리턴값도 있음
- 주로 매개값을 조사해서 true/false를 리턴

 

 

4.1 Consumer 함수적 인터페이스

Consumer 함수적 인터페이스의 특징은 리턴값이 없는 accept() 메소드를 가지고 있다.

accept() 메소드는 단지 소비하는 역할만 한다.(매개값을 사용할 뿐 리턴하지 않는다.)

 

매개변수의 타입과 수에 따라 다양한 Consumer들이 있다.

인터페이스명 추상 메서드 설명
Consumer<T> void accept(T t) 객체 T를 받아 소비
BiConsumer<T, U> void accept(T t, U u) 객체 T와 u를 받아 소비
DoubleConsumer void accept(double value) value를 받아 소비
IntConsumer<T> void accept(int value) value를 받아 소비
LongConsumer<T> void accept(long value) value를 받아 소비
ObjDoubleConsumer<T> void accept(T t, double value) 객체 T와 value를 받아 소비
ObjIntConsumer<T> void accept(T t, int value) 객체 T와 value를 받아 소비
ObjLongConsumer<T> void accept(T t, long value) 객체 T와 value를 받아 소비

 

Consumer<T> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.

public static void main(String[] args) {
    Consumer<String> consumer = t -> System.out.println(t);
    consumer.accept("consumer!");

    BiConsumer<String, String> bigConsumer = (t, u) -> System.out.println(t + u);
    bigConsumer.accept("consumer", "!");

    DoubleConsumer doubleConsumer = d -> System.out.println("consumer" + d);
    doubleConsumer.accept(808.0);

    ObjIntConsumer<String> objIntConsumer = (t, i) -> System.out.println(t + i);
    objIntConsumer.accept("consumer", 8);
}

=>
consumer!
consumer!
consumer808.0
consumer8

 

 

4.2 Supplier 함수적 인터페이스

Supplier 함수적 인터페이스의 특징은 매개 변수가 없고 리턴값이 있는 getXXXX() 메소드를 가지고 있다.

이 메소드들은 실행 후  호출한 곳으로 데이터를 리턴(공급)하는 역할을 한다.

 

리턴 타입에 따라 아래와 같은 Supplier 함수적 인터페이스들이 있다.

인터페이스명 추상 메소드 설명
Supplier<T> T get() T 객체를 리턴
BooleanSupplier boolean getAsBoolean() boolean 값을 리턴
DoubleSupplier double getAsDouble()  double 값을 리턴
IntSupplier int getAsInt()  int 값을 리턴
LongSupplier long getAsLong()  long 값을 리턴

 

Supplier<T> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.

public class SupplierExample {

    public static void main(String[] args) {
        IntSupplier intSupplier = () -> {
            Random random = new Random();
            return random.nextInt(6);
        };

        int num = intSupplier.getAsInt();
        System.out.println(num);
    }
}

 

 

4.3 Function 함수적 인터페이스

Function 함수적 인터페이스의 특징은 매개값과 리턴값이 있는 applyXXXX() 메소드를 가지고 있다.

=> 주로 매개값을 리턴값으로 타입 변환한다.

인터페이스명 추상 메소드 설명
Function<T, R> R apply (T t) 객체 T를 R로 매핑
BiFunction<T, U, R> R apply (T t, U u) 객체 T와 U를 R로 매핑
DoubleFunction<R> R apply (double value) double을 객체 R로 매핑
IntFunction<R> R apply (int value) int를 객체 R로 매핑
IntToDoubleFunction double applyAsDouble(int value) int를 double로 매핑
IntToLongFunction long applyAsLong(int value) int를 long으로 매핑
LongToDoubleFunction double applyAsDouble(long value) long을 double로 매핑
LongToIntFunction int applyAsInt(long value) long을 int로 매핑
ToDoubleBiFunction<T, U> double applyAsDouble<T t, U u> 객체 T와 U를 double로 매핑

 

Function<T, R> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.

public class FunctionExample {

    private static List<Student> list = Arrays.asList(
            new Student("홍길동", 90, 96),
            new Student("김길동", 40, 50)
    );

    public static void printString(Function<Student, String> function) {
        for (Student student : list) {
            System.out.println(function.apply(student) + " ");
        }
        System.out.println();
    }

    public static void printInt(ToIntFunction<Student> function) {
        for (Student student : list) {
            System.out.println(function.applyAsInt(student) + " ");
        }
        System.out.println();
    }

    public static void printAvg(ToIntFunction<Student> function) {
        int studentNum = list.size();
        int sum = 0;
        for (Student student : list) {
            sum += function.applyAsInt(student);
        }
        System.out.println((double) sum / (double) studentNum);
    }

    public static void main(String[] args) {
        System.out.println("[학생 이름]");
        printString(student -> student.getName());

        System.out.println("[수학 점수]");
        printInt(student -> student.getMathScore());

        System.out.println("[수학 평균]");
        printAvg(student -> student.getMathScore());

        System.out.println("[영어 평균]");
        printAvg(student -> student.getEnglishScore());
    }
}

=>
[학생 이름]
홍길동 
김길동 

[수학 점수]
96 
50 

[수학 평균]
73.0
[영어 평균]
65.0

 

 

 

4.4 Operator 함수적 인터페이스

Operator 함수적 인터페이스는 Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있다.

=> 매객값을 이용해 연산을 수행한 후 동일한 타입으로 리터값을 제공하는 역할을 한다.

인터페이스명 추상 메소드 설명
BinaryOperator T apply(Tt, Tt) T와 T를 연산한 후 T 리턴
UnaryOperator T apply(Tt) T를 연산한 후 T 리턴
DoubleBinaryOperator double applyAsDouble(double, double) 두 개의 double 연산
DoubleUnaryOperaotr double applyAsdouble(double) 한 개의 double 연산
IntBinaryOperator int applyAsInt(int, int) 두 개의 int 연산
IntUnaryOperator int applyAsInt(int) 한 개의 int 연산
LongBinaryOperator long applyAsLong(long, long) 두 개의 long 연산
LongUnaryOperator long applyAsLong(long) 한 개의 long 연산

 

Operator 함수적 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.

public class OperatorExample {

    private static final int[] scores = {92, 94, 95, 96};

    public static int maxOrMin(IntBinaryOperator operator) {
        int result = scores[0];
        for (int score : scores) {
            result = operator.applyAsInt(result, score);
        }
        return result;
    }

    public static void main(String[] args) {
        //최댓값 얻기
        int max = maxOrMin(
                (a, b) -> {
                    if (a <= b) return b;
                    else return a;
                }
        );
        System.out.println("최댓값 : " + max);

        //최솟값 얻기
        int min = maxOrMin(
                (a, b) -> {
                    if (a >= b) return b;
                    else return a;
                }
        );
        System.out.println("최솟값 : " + min);
    }
}

 

4.5 Predicate 함수적 인터페이스

Predicate 함수적 인터페이스는 매개 변수와 boolean 리턴값이 있는 testXXX() 메소드를 가지고 있다.

인터페이스명 추상 메소드 설명
Predicate<T> boolean test(T t) 객체 T를 조사
BiPredicate boolean test(T t, U u) 객체 T와 U를 비교 조사
DoublePredicate boolean test(double value) double 값을 조사
intPredicate boolean test(int value) int 값을 조사
LongPredicate boolean test(long value) long 값을 조사

 

Predicate<T> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.

public class PredicateExample {

    private static List<Student> list = List.of(
            new Student("홍길동", "남자", 90),
            new Student("김길동", "남자", 40),
            new Student("이길동", "여자", 91),
            new Student("최길동", "여자", 39)
    );

    public static double avg(Predicate<Student> predicate) {
        int count = 0, sum = 0;
        for (Student student : list) {
            if (predicate.test(student)) {
                sum += student.getScore();
                count ++;
            }
        }

        if (count == 0)
            return 0;
        else
            return (double) sum / (double) count;
    }

    public static void main(String[] args) {
        double maleAvg = avg(
                student -> student.getSex().equals("남자")
        );
        System.out.println("남자 평균 점수 : " + maleAvg);

        double femaleAvg = avg(
                student -> student.getSex().equals("여자")
        );
        System.out.println("여자 평균 점수 : " + femaleAvg);
    }
}

=>
남자 평균 점수 : 65.0
여자 평균 점수 : 65.0

 

 

4.6 andThen()과 compose() 디폴트 메소드

디폴트 및 정적 메소드는 추상 메소드가 아니기 때문에 함수적 인터페이스에 선언되어도 여전히 함수적 인터페이스의 성질을 잃지 않는다.

 

Consumer, Function, Operator 종류의 함수적 인터페이스는 andThen() 과 compose() 디폴트 메소드를 가지고 있다.

두 디폴트 메소드는 두 개의 함수적 인터페이스를 순차적으로 연결하고, 첫 번째 처리 결과를 두 번째 매개값으로 제공해서 최종 결과값을 얻을 때 사용한다.

andThen()과 compose()의 차이점은 어떤 함수적 인터페이스를 먼저 처리하느냐이다.

 

1. andThen()

인터페이스AB = 인터페이스A.andThen(인터페이스B)

최종결과 = 인터페이스AB.method();

 

인터페이스 A 처리 후 결과값을 인터페이스 B에 매개값으로 전달

 

2. compose()

인터페이스AB = 인터페이스A.compose(인터페이스B)

최종결과 = 인터페이스AB.method();

 

인터페이스 B 처리 후 결과값을 인터페이스 A에 매개값으로 전달

 

public class FunctionAndThenComposeExample {
    public static void main(String[] args) {
        Function<Member, Address> functionA;
        Function<Address, String> functionB;
        Function<Member, String> functionAB;

        String city;

        functionA = (member -> member.getAddress());
        functionB = (address -> address.getCity());
        
        functionAB = functionA.andThen(functionB);
        city = functionAB.apply(new Member("홍길동", "hong", new Address("한국", "서울")));

        System.out.println(city);

        functionAB = functionB.compose(functionA);
        city = functionAB.apply(new Member("홍길동", "hong", new Address("한국", "서울")));
        System.out.println(city);
    }
}

=>
서울
서울

 

4.7 minBy(), maxBy() 정적 메소드

BinaryOperator<T> 함수적 인터페이스는 minBy(), maxBy() 정적 메소드를 제공한다. 이 두 메소드는 매개값으로 제공되는 Comparator 를 이용해서 최대 T와 최소T를 얻는 BinaryOperator<T>를 리턴한다.

 

public static void main(String[] args) {
    BinaryOperator<Student> binaryOperator;
    Student student;
    
    binaryOperator = BinaryOperator.minBy((f1, f2) -> {
        return Integer.compare(f1.getMathScore(), f2.getMathScore());
    });
    
    student = binaryOperator.apply(new Student("길동", 30, 40), new Student("길은", 40, 50));
    System.out.println(student.getName());
}

'java > java' 카테고리의 다른 글

람다식(2) - 클래스 멤버와 로컬 변수 사용  (0) 2023.12.08
람다식 (1) - 기본 문법, 함수적 인터페이스  (1) 2023.12.07
Generic  (1) 2023.12.04
중첩 클래스와 중첩 인터페이스란?  (0) 2023.11.29
어노테이션  (2) 2023.11.26