람다식 - (2)

람다식 (Lambda Expression)

목차

  1. 람다식 기본
  2. Java 표준API
  3. 메소드참조

표준 API의 함수적 인터페이스

자바에서 제공되는 표준 API에서 한 개의 추상 메소드를 가지는 인터페이스들은 모두 람다식을 이용해서 익명 구현 객체로 표현이 가능하다. 그리고 객체 생성자로 함수적 인터페이스를 람다식 형태로 전달이 가능하다.

Thread thread = new Thread( () -> System.out.println("Hello, Lambda") );
thread.start();

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

Consumer 함수적 인터페이스

Consumer 함수적 인터페이스의 특징은 리턴값이 없는 accept() 메소드를 제공한다. accept() 메소드는 단지 매개값을 소비하는 역할만 한다. 매개 변수의 타입, 수에 따라 아래와 같은 Consumer 들이 있다.

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

Consumer 사용 예제는 아래와 같다.

Consumer<String> consumer = (t) -> System.out.println(t + "를 소모합니다.");
consumer.accept("에너지");
consumer.accept("가스");

Supplier 함수적 인터페이스

Supplier 함수적 인터페이스의 특징은 매개 변수가 없고 리턴값이 있는 getXXX() 메소드를 제공한다. 이 메소드들은 실행 후 호출한 곳으로 데이터를 공급(return)하는 역할을 한다. 리턴타입에 따라 아래와 같은 Supplier 함수적 인터페이스가 있다.

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

다음 예제는 주사위 숫자를 랜덤하게 공급하는 IntSupplier 인터페이스를 타겟 타입으로 하는 람다식입니다.

IntSupplier intSupplier = () -> {
	int num = (int) (Math.random() * 6) + 1;
	return num;
};

int num = intSupplier.getAsInt();
System.out.println("주사위의 수 : " + num);

Function 함수적 인터페이스

Function 함수적 인터페이스의 특징은 매개변수를 가지고 리턴값을 매핑(형변환) 해주는 applyXXX() 메소드를 제공한다. 매가 변수 타입과 리턴 타입에 따라서 아래와 같은 Function 함수적 인터페이스가 있다.

인터페이스 명 추상 메소드 설명
Function<T, R> R apply(T t) 객체 T를 받아 객체 R로 매핑
BiFunction<T, U, R> R apply(T t, U u) 객체 T와 U를 받아 객체 R로 매핑
IntFunction<R> R apply(int value) int 값을 받아 R로 매핑
DoubleFunction<R> R apply(double value) double 값을 받아 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로 매핑
ToIntBiFunction<T, U> int applyAsInt(T t, U u) 객체 T와 U를 받아 int로 매핑
ToIntFunction<T> int applyAsInt(T t) 객체 T를 받아 int로 매핑
ToDoubleBiFunction<T, U> double applyAsDouble(T t, U u) 객체 T와 U를 받아 double로 매핑
ToDoubleFunction<T> double applyAsDouble(T t) 객체 T를 받아 double로 매핑
ToLongBiFunction<T, U> long applyAsLong(T t, U u) 객체 T와 U를 받아 long으로 매핑
ToLongFunction<T> long applyAsLong(T t) 객체 T를 받아 long으로 매핑

다음 예제는 숫자를 입력하여 문자열 포멧으로 출력하는 Function<Integer, String> 인터페이스를 타겟 타입으로 하는 람다식입니다.

Function<Integer, String> formatter = (value) -> String.format("%,d", value);
System.out.println("1234567 > " + formatter.apply(1234567));

Operator 함수적 인터페이스

Operator 함수적 인터페이스는 Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX() 메소드를 제공한다. 하지만, 이 메소드들은 매개값을 리턴값으로 매핑(형변환)하는 역할보다 매개값을 이용해서 연산을 수행한 후 동일한 타입으로 리턴값을 제공하는 역할을 한다. 매개 변수의 타입과 수에 따라서 아래와 같은 Operator 함수적 인터페이스가 있다.

인터페이스 명 추상 메소드 설명
BinaryOperator<T> BiFunction<T, U, R>의 하위 인터페이스 두 개의 Obj를 연산하여 T 리턴
UnaryOperator<T> Function<T, R>의 하위 인터페이스 하나의 Obj를 연산하여 T 리턴
IntBinaryOperator int applyAsInt(int, int) 두개의 int 를 연산
IntUnaryOperator int applyAsInt(int) 하나의 int 를 연산
LongBinaryOperator int applyAsLong(long, long) 두개의 long을 연산
LongUnaryOperator int applyAsLong(long) 하나의 long을 연산
DoubleBinaryOperator double applyAsDouble(double, double) 두개의 double를 연산
DoubleUnaryOperator double applyAsDouble(double) 하나의 double를 연산

두개의 값을 가지고 더 큰 값, 더 작은 값을 리턴하는 IntBinaryOperator 인터페이스를 타겟으로 하는 람다식 예제입니다.

int[] scores = {99, 82, 94, 77, 63, 58, 97, 86, 66, 70};
IntBinaryOperator maxOperator = (a, b) -> a >= b ? a : b;
IntBinaryOperator minOperator = (a, b) -> a <= b ? a : b;

int min = scores[0];
int max = scores[0];
for(int score : scores) {
	min = maxOperator.applyAsInt(min, score);
	max = minOperator.applyAsInt(max, score);
}

Predicate 함수적 인터페이스

Predicate함수적 인터페이스는 매개 변수와 boolean 을 리턴하는 testXXX() 메소드를 제공한다. 이 메소드들은 매개값을 조사해서 true 또는 false 를 리턴하는 역할을 한다. 매개 변수 타입과 수에 따라 아래와 같은 Predicate 함수적 인터페이스들이 있다.

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

입력된 값 중 90 이상인 수의 개수를 구하는 IntPredicate 인터페이스를 타겟으로 하는 람다식 예제입니다.

int[] scores = {99, 82, 94, 77, 63, 58, 97, 86, 66, 70};
IntPredicate a = (value) -> value >= 90;
		
int count = 0;
for(int score : scores) {
	if( a.test(score) ) count++;
}
System.out.println("90점 이상 : " + count);

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

java.util.function 패키지의 함수적 인터페이스는 하나 이상의 디폴트 메소드를 가지고 있다. Consumer, Function, Operator 종류의 함수적 인터페이스는 andThen()과 compose() 디폴트 메소드를 가지고 있다. andThen() 과 compose() 메소드는 두 개의 함수적 인터페이스를 순차적으로 연결하고, 첫 번째 처리 결과를 두 번째 매개값으로 제공해서 최종 결과값을 얻을 때 사용합니다. 두 디폴트 메소드의 차이는 어떤 함수적 인터페이스부터 먼저 처리되느냐에 따라 다르다.

Interface interfaceAB = interfaceA.andThen(interfaceB);
Result result = interfaceAB.method();

interfaceAB 의 method()를 호출하면 우선 interfaceA부터 처리하고 결과를 interfaceB의 매개값으로 사용된다. interfaceB는 제공받은 매개값을 가지고 처리한 후 최종 결과를 리턴한다.

Interface interfaceAB = interfaceA.compose(interfaceB);
Result result = interfaceAB.method();

interfaceAB 의 method()를 호출하면 우선 interfaceB부터 처리하고 결과를 interfaceA에 매개값으로 사용된다. interfaceA는 제공받은 매개값을 가지고 처리한 후 최종 결과를 리턴한다.

아래는 andThen(), compose() 디폴트 메소드를 제공하는 java.util.function 패키지의 함수적 인터페이스 목록이다.

Consumer

함수적 인터페이스 andThen() compose()
Consumer<T> O  
BiConsumer<T, U> O  
DoubleConsumer O  
IntConsumer O  
LongConsumer O  

Function

함수적 인터페이스 andThen() compose()
Function<T, R> O  
BiFunction<T, U, R> O O

Operator

함수적 인터페이스 andThen() compose()
BinaryOperator<T> O  
DoubleUnaryOperator O O
IntUnaryOperator O O
LongUnaryOperator O O

and(), or(), negate() 디폴트 메소드와 isEqual() 정적 메소드

Predicate 종류의 함수적 인터페이스는 and(), or(), negate() 디폴트 메소드를 가지고 있다. 이 메소드들은 각각 논리연산자인 &&, ||, ! 과 대응된다. &&, || 연산의 경우 조건을 모두 체크하지 않기도 하는데 이는 and(), or() 디폴트 메소드에서도 동일하게 적용된다.
아래 예제는 2의 배수, 3의 배수를 조사하는 Predicate와 두 Predicate를 논리연산한 새로운 Predicate 를 생성한다.

IntPredicate predicateA = (a) -> {
	System.out.println("[predicateA 호출됨]");
	return a % 2 == 0;
};
IntPredicate predicateB = (b) -> {
	System.out.println("[predicateB 호출됨]");
	return b % 3 == 0;
};

boolean result;
IntPredicate predicateAB = predicateA.and(predicateB);
result = predicateAB.test(9);
System.out.println("9는 2와 3의 배수입니까? " + result);

predicateAB = predicateA.or(predicateB);
result = predicateAB.test(9);
System.out.println("9는 2또는 3의 배수입니까? " + result);

predicateAB = predicateA.negate();
result = predicateAB.test(9);
System.out.println("9는 홀수입니까? " + result);

Predicate<T> 함수적 인터페이스는 isEqual() 정적 메소드를 별도로 제공한다. isEqual() 메소드는 test() 매개값인 sourceObject 와 isEqual() 의 매개값인 targetObject 를 java.util.Objects 클래스의 equals() 의 매개값으로 제공하고, Objects, equals(source, targetObject) 의 리턴값을 얻어 새로운 Predicate<T> 를 생성한다.

Predicate<Object> predicate = Predicate.isEqual(targetObject);
boolean result = predicate.test(sourceObject);

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

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

Comparator<T>는 o1과 o2를 비교하여 o1작으면 음수를, o1과 o2가 동일하면 0, o1이 크면 양수를 리턴하는 compare() 메소드가 선언된 함수적 인터페이스다.

@FunctionalInterface
public interface Comparator<T> {
    public int compare(T o1, T o2);
}

참조 palpit’s log-b