java - 함수형 인터페이스 API(람다의 활용1)

들어가기

자바의 람다는 자바언어에서 쉽고 간편하게 함수를 선언해서 사용하기위한 수단이다. 하지만 자바는 객체 지향언어이고 기본적인 프로그래밍의 단위는 Class이다.

자바에서 구현하는 함수는 이 class의 범위를 벗어 날수가 없다. 함수적 인터페이스(추상클래스가 하나뿐인 인터페이스)를 구현하여 함수를 생성하고 사용하는 매커니즘을 통해서 함수형 프로그래밍을 지원하며, 람다식은 이것에 특화된 문법일 뿐이다. 실질적으로 함수 한개를 생성해서 사용하는 것 뿐이지만, 결과적으로는 함수적 인터페이스를 즉흥적으로 구현한 익명객체의 메소드를 사용하게 되는 것이다.

여기서 고민해볼게 생긴다. 재사용이 필요없고, 즉흥적으로 함수를 구현하기 위해서 익명객체를 생성하는것 까지는 넘어간다 하더라도, 일일이 구현해야 할 함수적 인터페이스를 만들어 줘야 하나?

1
2
3
4
5
6
7
8

void logString(String text){
System.out.println(text);
}

void logInt(Int value){
System.out.println(value);
}

만약 위 처럼 인자만 다르고 하는 일은 똑같은 함수를 구현하기위해 인터페이스를 두개나 만들어야 하는 것인가? 위와 같은 고민은 전혀 할 필요가 없다. 정답은 java.util.function 표준 API 패키지에 있다. Java8에서는 기능적으로 분류된 자주 사용햘 만한 함수형 인터페이스를 API로 제공하고 있기때문에, 그냥 해당 API의 인터페이스를 입맛에 맞게 구현해서 사용하면 된다.

기본으로 제공되는 함수적 인터페이스의 종류

종류 추상 메소드 특징 비고
Consumer 매개값은 있고, 리턴값은 없음
Supplier 매개값은 없고, 리턴값은 있음
Function 매개값도 있고, 리턴값도 있음 주로 매개값을 연산하고 결과를 리턴
Operator 매개값도 있고, 리턴값도 있음 주로 매개값을 연산하고 결과를 리턴
Predicate 매개값은 있고, 리턴 값은 boolean 매개값을 조사하고 true/false를 리턴

java.util.function에서 제공하는 인터페이스들은 위의 메소드 특징에 따라서 5형태의 카테고리로 나뉜다. 추상 메소드의 특징으로 인터페이스를 고른 뒤, 전달한 인자의 종류에 따라서 구체적인 인터페이스를 골라 사용하면 된다.

java.util.function 오라클 DOC https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

Doc문서에서 카테고리별로 정렬을 안해줘서, 보기가 좀 불편한데 인터페이스와 그 추상메소드는 굉장히 직관적이라 알기만 한다면 쉽게 사용할수 있다.

Cunsunmer

인자를 받고 메소드에서 리턴없이 소모시킨다. 인자만 다를 뿐 추상 메소드명은 전부 accept인 것이 특징이다.

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

아래처럼 간단히 값을 출력하는 함수를 만들때 사용할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package java_lamdba;

import java.util.function.BiConsumer;
import java.util.function.Consumer;

public class CunsummerExample {

public static void main(String [] args) {

Consumer<String> c1 = t -> System.out.println(t);

c1.accept("Consumer를 이용해서 로그를 찍어 봅시다.");

BiConsumer<String, String> c2 = (t, u) -> {

String result = "Log:" + t + u;
System.out.println(result);
};

c2.accept("헛둘헛둘", "셋둘셋둘");

}
}

Supplier

인자가 없으며 리턴값이 존재 한다. 리턴하는 값의 타입에 따라 getXXX형태의 추상메소드를 갖는 것이 특징이다.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package java_lamdba;

import java.util.function.IntSupplier;

public class SupplierExample {

public static void main(String[] args) {

IntSupplier myAge = () -> {
return 32;
};

System.out.println("내 나이:" + myAge.getAsInt());

}

}

Function

이 인터페이스의 용도는 인자의 타입을 변형하여 새로운 타입으로 값을 리턴하는 것이다. 즉 일종의 형변환 전용 함수형 인터페이스가 되겠다. 매개인자 타입과 리턴값의 타입이 다르다.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package java_lamdba;

import java.util.function.Function;

public class FunctionExample {

public static void main(String[] args) {

Function<Human, String> f1 = t -> t.name;
System.out.println(f1.apply(new Human()));

}

}

class Human{

public int age = 32;
public String name = "hanumoka";

}

Operator

Function과 동일한 형태의 applyXXX라는 메소드를 가지고 있다. 하지만 매개값의 타입변환의 역활보다 매개값을 이용하여 연산을 수생한 후 동일한 타입으로 리턴값을 제공하는 역할을 한다. 매개인자의 타입과 리턴값의 타입이 동일

인터페이스명 추상 메소드 설명
BinaryOperator<T> T apply(T t1, T t2) 동일한 타입의 t1, t2를 연산후 T를 리턴
UnaryOperator<T> T apply(T) T를 연산한 후 T를 리턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package java_lamdba;

import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;

public class Operator1 {

public static void main(String[] args) {

BinaryOperator<Integer> b1 = (t, u) ->{
return (t * u);
};

System.out.println(b1.apply(3, 10));

UnaryOperator<Integer> u1 = (t)->{
return t + 1;
};

System.out.println(32);

}

}

인터페이스명 추상 메소드 설명
DoubleBinaryOperator double applyAsDouble(double, double) 두개의 double 연산 후 double 리턴
DoubleUnaryOperator double applyAsDouble(double) 한 개의 double 연산후 double리턴
intBinaryOperator int applyAsInt(int, int) 두 개의 int 연산후 int 리턴
intUnaryOperator int applyAsInt(int) 한개의 int연산후 int리턴
LongBinaryOperator long applyAsLong(long, long) 두개의 long 연산후 long 리턴
LongUnaryOperator long applyAsLong(long) 한 개의 long 연산후 long리턴

Predicate

Prediacate는 매개 값을 이용하여 true, false를 리턴한다.

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