[기본 개념] 9 | (1.2) Java.util.funcion 패키지, Function 합성, Predicate 결합, 메서드 참조

728x90

[기본 개념] 9 | (1.2) Java.util.funcion 패키지, Function 합성, Predicate 결합, 메서드 참조

1 람다식이란?

2 람다식 작성하기

3 함수형 인터페이스(Functional Interface)

4> java.util.function패키지

5> Function의 합성과 Predicate의 결합

6> 메서드 참조

4. java.util.function패키지

 java.util.function패키지에 일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 정의해 놓았다. 가능하면 이 패키지의 인터페이스를 사용하는 것이 메서드 이름도 통일되고, 재사용성이나 유지보수 측면에서 좋다.

 

함수형 인터페이스 메서드 설명
java.lang.Runnable void run( ) 매개변수도 없고, 반환값도 없음
Supplier<T>  T get( ) T 매개변수는 없고, 반환값만 있음
Consumer<T> T  void accept(T t) 매개변수만 있고, 반환값이 없음
Function<T, R> T R apply(T t) R 일반적인 함수.
하나의 매개변수를 받아 결과를 반환함
Predicate<T> T boolean test(T t) boolean 조건식을 표현하는 데 사용함
매개변수는 하나, 반환타입은 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

 Function의 또 다른 변형으로 매개변수의 타입과 반환타입이 일치한다.

 

함수형 인터페이스 메서드 설명
UnaryOperator<T> T  T apply(T t)  T Function의 자손, Function과 달리
매개변수와 결과의 타입이 같다.
BinaryOperator<T> T, T T apply(T t, T t) T BiFunction의 자손, Bifunction과 달리
매개변수와 결과의 타입이 같다.

 

함수형 인터페이스 예제

 

예제/LamdaEx5.java

import java.util.function.*;
import java.util.*;

public class LamdaEx5 {
    public static void main(String[] args) {
        Supplier<Integer> s = () -> (int) (Math.random() * 100) + 1;
        Consumer<Integer> c = i -> System.out.print(i + ", ");
        Predicate<Integer> p = i -> i % 2 == 0;
        Function<Integer, Integer> f = i -> i / 10 * 10; // i의 일의 자리를 없앤다.

        List<Integer> list = new ArrayList<>();
        makeRandomList(s, list);
        System.out.println(list);
        printEvenNum(p, c, list);
        List<Integer> newList = doSomething(f, list);
        System.out.println(newList);
    }

    static <T> List<T> doSomething(Function<T, T> f, List<T> list) {
        List<T> newList = new ArrayList<>(list.size());

        for (T i : list) {
            newList.add(f.apply(i));
        }

        return newList;
    }

    static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
        System.out.print("[");
        for (T i : list) {
            if (p.test(i)) c.accept(i);
        }
        System.out.println("]");
    }

    static <T> void makeRandomList(Supplier<T> s, List<T> list) {
        for (int i = 0; i < 10; i++) list.add(s.get());
    }
}
실행결과

[41, 45, 100, 4, 72, 77, 29, 10, 83, 89]
[100, 4, 72, 10, ]
[40, 40, 100, 0, 70, 70, 20, 10, 80, 80]

5. Function의 합성과 Predicate의 결합

Function의 합성

 두 람다식을 합성해서 새로운 람다식을 만들 수 있다.

 

 함수 f, g가 있을 때, f.addThen(g)는 함수 f 먼저 적용하고 함수 g를 적용하지만, f.compose(g)는 반대로 함수 g 먼저 적용하고 함수 f를 적용한다.

 

 예를 들어, 문자열을 숫자로 변환하는 함수 f, 숫자를 이진 문자열로 변환하는 함수 g로 andThen( )으로 합성하여 새로운 함수 h를 만들어낸다.

 

Function<String, Integer> f = (s) -> Integer.parseInt(s, 16) ;

Function<Integer, String> g = (i) -> Integer.toBinaryString(i) ;

Function<String, String> h = f.andThen(g) ;

 

 함수 h의 지네릭 타입이 <String, String>이다. 예를 들어 "FF"를 입력하면, 결과로 "11111111"를 얻는다.

 

System.out.println(h.apply("FF")) ;       // "FF" -> 255 -> "11111111"

 

 compose( )를 사용해서 반대 순서로 합성해보자.

 

Function<Integer, String> g = (i) -> Integer.toBinaryString(i) ;

Function<String, Integer> f = (s) -> Integer.parseInt(s, 16) ;

Function<Integer, Integer> h = f.compose(g) ;

 

 함수 h의 지네릭 타입이 <Integer, Integer>이다. 예를 들어 2를 입력하면, 결과로 16을 얻는다.

 

System.out.println(h.apply(2)) ;       // 2 -> "10" -> 16

 

 indentity( )라는 항등함수가 필요할 때 사용한다. 이 함수는 람다식으로 'x -> x'이다.

 

Function<String, String> f = Function.identity( ) ;      // x -> x ; 로도 사용할 수 있다.

System.out.println(f.apply("AAA")) ;        // "AAA"가 그대로 출력

 

 잘 사용되지는 않지만 map( )으로 변환작업할 때, 변환없이 그대로 처리할 때 사용한다.

 

Predicate의 결합

 여러 Predicate를 and( ), or( ), negate( )로 연결해서 하나의 새로운 Predicate로 결합한다.

 

Predicate<Integer> p = i -> i < 100 ;

Predicate<Integer> q = i -> i < 200 ;

Predicate<Integer> r = i -> i % 2 == 0 ;

Predicate<Integer> notP = p.negate( ) ;

 

// 100 <= i && (i < 200 || i % 2 == 0)

Predicate<Integer> all = notP.and(q.or(r)) ;    // notP.and(i -> i < 200).or(i -> i % 2 == 0) ; 람다식도 가능

System.out.println(all.test(150)) ;        // true

 

 Predicate의 끝에 negate( )를 붙이면 조건식 전체가 부정이 된다.

 

 그리고 static메서드인 isEqual( )은 두 대상을 비교하는 Predicate를 만들 때 사용한다. isEquals( )의 매개변수로 비교대상 하나를 지정하고, 다른 비교대상은 test( )의 매개변수로 지정한다.

 

boolean result = Predicate.isEquals(str1).test(str2) ;    // str1과 str2가 같은지 비교

6. 메서드 참조

 람다식이 하나의 메서드만 호출하는 경우에, 참조변수의 타입으로 알 수 있으므로 람다식의 매개변수를 제거해 메서드 참조로 변경할 수 있다.

 

BiFunction<String, String, Boolean> f = (s1, s2) -> s1.equals(s2) ;

BiFunction<String, String, Boolean> f = String :: equals ;   // 메서드 참조

 

종류 람다 메서드 참조
static메서드 참조 (x) -> ClassName.method(x) ClassName :: method
인스턴스메서드 참조 (obj, x) -> obj.mehtod(x) ClassName :: method
특정 객체 인스턴스메서드 참조 (x) -> obj.method(x) obj :: method

 

하나의 메서드만 호출하는 람다식은

'클래스이름 :: 메서드 이름' 또는 '참조변수 :: 메서드 이름'으로 바꿀 수 있다.

 

생성자의 메서드 참조

 생성자를 호출하는 람다식도 메서드 참조로 변환할 수 있다.

 

Supplier<MyClass> s = ( ) -> new MyClass( ) ;        // 람다식

Supplier<MyClass> s = MyClass :: new ;                  // 메서드 참조

 

 매개변수가 있는 생성자라면, 매개변수의 개수에 맞는 함수형 인터페이스를 사용하면 된다.

 

 배열을 생성할 때는 아래와 같이하면 된다.

 

Function<Integer, int[ ]> f = x -> new int[x] ;          // 람다식

Function<Integer, int[ ]> f2 = int[ ] :: new ;             // 메서드 참조

 

 메서드 참조는 람다식을 static변수처럼 다룰 수 있게 해 준다.

 

 

 

 

 

출처 | Java의 정석 (남궁 성)

728x90