상세 컨텐츠

본문 제목

[Java] 4. java.util.function에 정의된 함수형 인터페이스 사용

프로그래밍 언어/Java(자바)

by Guroo 2023. 8. 8. 13:36

본문

1. 함수형 인터페이스 종류

  • java.util.function 패키지에는 자주 사용되는 함수형 인터페이스를 다양하게 선언해 놓았는데, 이번에는 이미 정의되어 있는 인터페이스를 사용해보자
  • java.util.function 패키지에 정의된 인터페이스는 다양한 타입에서 범용적으로 사용할 수 있도록 제네릭으로 선언되어 있다

 

1-1. 기본적인 함수형 인터페이스의 종류

 

인터페이스 메소드 설명
Predicate<T> boolean test(T t) 데이터 한 개를 받아 조건을 판단하여 boolean으로 반환할 때 사용
Consumer<T> void accept(T t) 반환값은 없고 하나의 매개변수 필요
받는값(매개변수)만 있으므로 소비자(Consumer)
Supplier<T> T get() 매개로 받는 데이터는 없고 반환값이 없으므로 공급자(Supplier)
Function<T, R> R apply(T t) 일반적인 형태의 메소드
매개변수 한 개, 반환 값 한 개가 있다.

 

1) Predicate 인터페이스 사용 예

 

TestLambda03.java

import java.util.function.Predicate;
 
public class TestLambda03 {
    public static void main(String[] args) {
 
        int x = 5;
        Predicate<Integer> p1 = (a) -> a % 2 == 0? true: false;
        System.out.printf("%d는 %s다.%n", x, p1.test(x)? "짝수" : "홀수");
        int age = 24;
 
        Predicate<Integer> p2 = (a) -> a > 19 ? true: false;
        System.out.printf("%d세는 %s이다.%n", age, p2.test(age)? "성인" : "미성년");
 
        String str = "Seoul";
        Predicate<String> p3 = (a) -> a.isEmpty() ? true: false;
        System.out.printf("%s 문자열이다.%n", p3.test(str) ? "비어있는" : "비어있지 않은");
    }
}

 

2) Consumer 인터페이스의 사용 예

 

ConsumerTest.java

import java.util.function.Consumer;
 
public class ConsumerTest {
    public static void main(String[] args) {
        Consumer<Integer> c1 = (x) -> System.out.println(x + "를 두 배 하면 "+ x*2);
        c1.accept(4);
 
        Consumer<String> c2 = (x) -> System.out.println(x + "의 길이: "+ x.length());
        c2.accept("\"무궁화 꽃이 피었습니다.\"");
    }
}

 

3) Supplier 인터페이스의 사용 예

 

SupplierTest.java

import java.util.function.Supplier;

public class SupplierTest {
    public static void main(String[] args) {
        Supplier<Integer> s = () -> (int) (Math.random() * 100);
        for(int i=0; i<10; ++i) System.out.print(s.get() + " ");
    }
}

 

4) Function 인터페이스의 사용

  • java.lang.function.Function 인터페이스에는  1개의 추상 메소드와 2개의 default 메소드, 1개의 static 메소드가 존재한다. (참고)
  • Function 인터페이스의 추상메소드인  R apply(T t)는 특정 타입의 데이터 t를 매개변수로 전달받아 작업을 거치고 난 후 특정타입 R로 반환하도록 설계되어 있다.

 

아래의 코드는 cm에 해당하는 값을 전달한 후 inch로 변환, inch 값을 전달해서 cm로 변환하는 작업을 apply 메소드로 정의한 것과 섭씨 온도와 화씨 온도를 각각 전달하여 변환하는 apply 메소드를 정의하였다.

 

FunctionTest.java

import java.util.function.Function;

public class FunctionTest {
    public static void main(String[] args) {
        Function <Double, Double> cm2inch = (num) -> (num * 0.3937007874);  // cm를 inch로 변환
        Function <Double, Double> inch2cm = (num) -> (num * 2.54);          // cm를 inch로 변환

        double cm = 1.0;
        double inch = 1.0;

        System.out.printf("%.5fcm는 %.5finch입니다 %n", cm, cm2inch.apply(cm));
        System.out.printf("%.5finch는 %.5fcm입니다 %n", inch, inch2cm.apply(inch));

        // 섭씨를 화씨온도로 변환
        Function <Double, String> c2f = (c) -> " 화씨 " + ((c * 9.0/5.0) + 32) + " ºF";

        // 화씨를 섭씨온도로 변환
        Function <Double, String> f2c = (f) -> " 섭씨 " + ((f - 32) * 5.0/9.0) + " ºC";

        double celsius = 26.5;
        double fahrenheit = 46.5;

        String temp = null;
        temp = c2f.apply(celsius);

        System.out.println("섭씨 " + celsius + temp);
        temp = f2c.apply(fahrenheit);

        System.out.println("화씨 " + fahrenheit + temp);
    }
}

 

 


 

1-2. 매개 변수가 2개인 함수형 인터페이스

인터페이스 메소드 설명
BiFunction<T, U, R> R apply(T t, U u) 두 개의 매개변수를 T, U를 전달받아 R을 반환한다.
BiConsumer<T, U> void accept(T t, U u) 두 개의 매개변수를 전달받아 연산하고 반환 값은 없다.
BiPredicate<T, U> boolean test(T t, U u) 두 개의 매개변수를 전달받고 결과를 Boolean으로 반환한다.

 

이번에 살펴볼 함수형 인터페이스는 인터페이스에 Bi- 라는 접두사(?)가 붙어있는데, 이것은 인터페이스 내에 선언된 메소드의 매개변수가 2개(Binary)임을 나타낸다고 생각하면 된다.

 

먼저, BiFunction 인터페이스의 추상메소드 apply는 두 개의 매개변수 T, U를 전달받아 연산한 후 R 타입의 결과를 반환 할 때 사용한다.

 

예를 들여 키가 185.5cm, 몸무게 98.5 인 남성의 표준체중을 알고자 하면

 

BiFunction<Double, Double, Double> biFunc;

 

로 선언하고,

 

(185.5, 98.5) -> 표준체중구하는 연산식;

 

의 형태로 람다식을 정의한 후  그 결과를 Double형 데이터로 반환 받을 수 있다.

 

이때, BiFunction 인터페이스의 3번째 Generic 은 반환받고자 하는 타입이다.

만약 반환받고자 하는 데이터의 타입이 문자열이라면

 

BiFunction<Double, Double, String> biFunc;

 

위와 같이 지정할 수 있다.

 

예제 코드를 작성하기 위해 표준 체중을 구하는 일반적인 공식을 알아보자

 

남성 표준 체중(kg) = 키(cm) x 키(cm) x 22

여성 표준 체중(kg) = 키(cm) x 키(cm) x 21

 

BMI(체질량지수) = 몸무게(kg) / 키(m)의 제곱

 

그럼, 위의 공식과 BiFunction 인터페이스를 이용하여 남성의 표준체중을 구해보자

 

 

아래의 예는 Function 인터페이스와 BiFunction 인터페이스 두가지를 모두 활용하여 비교해 본 것이다.

Function 인터페이스에서는 표준 체중을, BiFunction 인터페이스로는 BMI 계산 결과를 반환하는 람다식이다.

 

BiFunctionTest.java

import java.util.function.BiFunction;
import java.util.function.Function;

public class BiFunctionTest {
    public static void main(String[] args) {
        String male = "남";
        double height = 185.5;
        double weight = 98.5;
        double maleStdWeight = 0;
        String bmiResult = null;

        Function<Double, Double> malefunc = (h) -> ((h * h * 0.0001) * 22);
        maleStdWeight = malefunc.apply(height);

        System.out.printf("키 %.2fcm인 남성의 표준체중은 %.2f입니다.%n", height, maleStdWeight);
        BiFunction<Double, Double, String> biFunc = (h, w) -> String.format("키 %.2fcm, 몸무게 %.2fcm인 사람의 BMI는 %.2f입니다.%n", h, w,  (w / (h * h * 0.0001)));

        bmiResult = biFunc.apply(height, weight);
        System.out.println(bmiResult);
    }
}

 

위의 코드에서 maleFunc 는 키를 전달하여 표준체중을 Double형으로 반환 받는 Function 인터페이스의 사용 예이고,

BiFunction<Double, Double, String> biFunc의 메소드 정의는 두 개의 Double 형 데이터인 키와 몸무게를 전달한 후 BMI 결과를 문자열의 형태로 반환 받았다.

 

위의 코드에서 BiFunction의 세 번째 Generic은 String으로 선언되었음을 확인할 수 있다. 즉, 2개의 Double 타입을 전달하여 String으로 그 결과를 반환 받는 형태임을 나타내는 것이다.

 


이번에는 BiPredicate 인터페이스를 포함한 3가지 예를 한꺼번에 살펴보도록 하자.

앞서 언급하였던 일급함수의 특성메소드를 전달하는 Lambda 식의 예도 함께 포함되어 있다.

 

먼저, BiPredicate 인터페이스의 추상 메소드는 2개의 전달인자를 받아 연산을 한 후 boolean 으로 결과를 반환하는 test() 메소드가 있다.

 

 boolean test(T t, U u)

 

아래 코드 중 첫 번째 예는 두 값을 전달받아 첫 번째 값이 두 번째 값보다 크거나 같으면 true, 아니면 false를 반환하는 BiPredicate.인터페이스의 추상메소드를 정의하였다.

 

BiPredicateTest.javaBiPredicateTest.java

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiPredicate;

public class BiPredicateTest {

    public static void main(String[] args) {
        int a =  10, b = 15;


        BiPredicate<Integer, Integer> biPredict = (x, y) -> (x >= y) ? true: false;
        if(biPredict.test(a, b)) 
            System.out.println(a +"는 " + b +"이상이다.");
        else
            System.out.println(a +"는 " + b +"미만이다.");

        // 메소드의 전달인자로 사용되는 Lambda 식 
        predict((str1, str2) -> (str1.compareTo(str2)) < 0 ? true: false );

        List<String> flowers = Arrays.asList("장미", "국화", "수선화", "목련", "진달래");

        // Collections.sort() 메소드의 2번째 전달인자로 Lambda 식을 전달하면 List 내의 데이터를 오름차순으로 정렬하여 반환한다.
        Collections.sort(flowers, (f1, f2) -> f1.compareTo(f2));
        System.out.println(flowers);
    }

    private static void predict(BiPredicate<String, String> strCompare) {
        boolean result = strCompare.test("장미", "국화");
        System.out.println(result? "장미가 국화보다 작은 문자열이다." : "장미가 국화보다 큰 문자열이다.");
    }
}

 

그리고 두 번째 예에서는 람다식이 메소드의 전달인자로 사용되는 예이다.

 myPredicate 메소드 호출 부분에는 두개의 문자열을 전달한 후 앞의 문자열이 뒤의 문자열보다 작으면 true, 아니면 false를 반환하는 익명함수가 argument임을 확인할 수 있다.

 

이 함수를 받을 수 있는 인자는 두 개의 값을 받아 boolean을 반환하는 BiPredicate 인터페이스 내에 정의된 test() 메소드이다. 

코드 아래 정의된 myPredicate 메소드의 매개변수의 타입이 BiPredicate 인터페이스라는 것을 확인할 수 있다.

 

 

마지막으로 세 번째의 예에서는 java.util.Collections 클래스에는 sort라는 이름의 static 메소드가 있는데 이 메소드는 첫 번째 전달인자로 List 를 두 번째 전달인자로 Comparator 인터페이스 타입의 전달 받는 형태로 선언되어 있다.

 

static <T> void       sort(List<T> list,   Comparator<? super T> c)

 

여기에서 주목할 것은 두번째 전달인자가 Comparator인터페이스 인데,

 

Comparator 인터페이스는 @FunctionInterface로 선언되어 있다.

 

때문에 두 번째 전달인자는 Comparator 인터페이스 내의 추상 메소드인 compare(T , T) 타입에 맞도록 데이터를 전달하면 된다.

 

int      compare(T o1, T o2)

위의 예에서는 sort() 메소드의 두 번째 전달인자를 List에서 두개의 문자열을 전달하여 람다식 내부에서는  다시 두 개의 문자열의 비교의 결과값을 int로 반환하는 compareTo를 호출하는 형태로 작성되었다.

 

이 sort 메소드를 이용하면 리스트 내의 문자열을 정렬할 수 있다.

 

 

BiConsumer 인터페이스의 accept() 메소드는 직접 정의해 보자.

 

void    accept(T t, U u)


 

이전글: 3. Lambda 사용을 위한 문법

 

다음 글: 5. 컬렉션에 정의된 함수형 인터페이스

관련글 더보기

댓글 영역