람다식 - (2)
람다식 (Lambda Expression)
목차
표준 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);
}