안녕하세요. 개발자 Jindory입니다.
오늘은 함수형 인터페이스(Functional Interface)에 대해서 글을 작성해보려고 합니다.
함수형 인터페이스란?
함수형 인터페이스(Functional Interface)는 1개의 추상 메서드를 갖고 있는 인터페이스를 말합니다. Single Abstract Method(SAM)라고 불리기도 합니다.
예를 들어, 아래와 같은 인터페이스를 함수형 인터페이스라고 합니다.
public interface FunctionalInterface {
public abstract void doSomething(String text);
}
함수형 인터페이스를 사용하는 이유는?
함수형 인터페이스를 사용하는 이유는 자바의 람다식은 함수형 인터페이스로만 접근이 되기 때문입니다.
예를 들어, 아래 코드에서 변수 func는 람다식으로 생성한 객체를 가리키고 있습니다. doSomething()에 인자로 문자열을 전달하면 람다식에 정의된 것처럼 로그로 출력을 합니다.
public interface FunctionalInterface {
public abstract void doSomething(String text);
}
FunctionalInterface func = text -> System.out.println(text);
func.doSomething("do something");
// 실행 결과
// do something
함수형 인터페이스를 사용하는 것은 람다식으로 만든 객체에 접근하기 위해서 입니다. 위의 예제처럼 람다식을 사용할 때마다 함수형 인터페이스를 매번 정의하기에는 불편하기 때문에 자바에서 라이브러리로 제공하는 것들이 있습니다.
기본 함수형 인터페이스
자바에서 기본적으로 제공하는 함수형 인터페이스는 다음과 같은 것들이 있습니다.
- Runnable
- Supplier
- Consumber
- Function<T,R>
- Predicate
이 외에도 다양한 것들이 있습니다. 자바의 java.util.function 패키지에 정의되어있으니 더 많은 것을 확인하고 싶으시면 링크를 참고하시기 바랍니다.
Runnable
Runnable은 인자를 받지 않고 리턴값도 없는 인터페이스 입니다.
public interface Runnable {
public abstract void run();
}
아래의 코드처럼 사용할 수 있습니다.
Runnable runnable = () -> System.out.println("run anything!");
runnable.run();
// 결과
// run anything!
Runnable은 run()을 호출해야 합니다. 함수형 인터페이스마다 run()과 같은 실행 메소드 이름이 다릅니다. 인터페이스 종류마다 만들어진 목적이 다르고 그 목적에 맞는 이름을 실행 메소드 이름으로 정하였기 때문입니다.
Supplier
Supplier<T>는 인자를 받지 않고 T타입의 객체를 리턴합니다.
public interface Supplier<T> {
T get();
}
아래 코드처럼 사용할 수 있습니다. get() 메서드를 호출해야합니다.
Supplier<String> getString = () -> "Happy new year!";
String str = getString.get();
System.out.println(str);
// 결과
// Happy new year!
Consumer
Consumer<T>는 T타입의 객체를 인자로 받고 리턴 값은 없습니다.
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
아래 코드처럼 사용할 수 있습니다. accept() 메서드를 사용합니다.
Consumer<String> printString = text -> System.out.println("Miss " + text + "?");
printString.accept("me");
// 결과
// Miss me?
또한, andThen()을 사용하면 두개 이상의 Consumer를 연속적으로 실행할 수 있습니다.
Consumer<String> printString = text -> System.out.println("Miss " + text + "?");
Consumer<String> printString2 = text -> System.out.println("--> Yes");
printString.andThen(printString2).accept("me");
// 결과
// Miss me?
// --> Yes
Function
Function<T,R>는 T타입의 인자를 받고, R타입의 객체를 리턴합니다.
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
다음과 같이 사용할 수 있습니다. apply() 메서드를 사용합니다.
Function<Integer, Integer> multiply = (value) -> value * 2;
Integer result = multiply.apply(3);
System.out.println(result);
// 결과
// 6
compose()는 두개의 Function을 조합하여 새로운 Function 객체를 만들어주는 메서드입니다. 주의할 점은 andThen()과는 실행 순서가 반대입니다. compose()에 인자로 전달되는 Function이 먼저 수행되고 그 이후에 호출하는 객체의 Function이 수행됩니다.
예를들어, 다음과 같이 compose를 사용하여 새로운 Function을 만들 수 있습니다. apply를 호출하면 add 먼저 수행되고 그 이후에 multiply가 수행됩니다.
Function<Integer, Integer> multiply = (value) -> value * 2;
Function<Integer, Integer> add = (value) -> value + 3;
Function<Integer, Integer> addThenMultiply = multiply.compose(add);
Integer result1 = addThenMultiply.apply(3);
System.out.println(result1);
// 결과
// 12
Predicate
Predicate<T>는 T타입 인자를 받고 결과로 boolean을 리턴합니다.
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
다음과 같이 사용할 수 있습니다. test() 메서드를 사용합니다.
Predicate<Integer> isBiggerThanFive = num -> num > 5;
System.out.println("10 is bigger than 5? -> " + isBiggerThanFive.test(10));
// 결과
// 10 is bigger than 5? -> true
and()와 or()는 다른 Predicate와 함께 사용됩니다. 직관적으로 and()는 두개의 Predicate가 true일 때 true를 리턴하며, or()는 두개 중에 하나만 true일 경우 true를 리텅합니다.
Predicate<Integer> isBiggerThanFive = num -> num > 5;
Predicate<Integer> isLowerThanSix = num -> num < 6;
System.out.println(isBiggerThanFive.and(isLowerThanSix).test(10));
System.out.println(isBiggerThanFive.or(isLowerThanSix).test(10));
// 결과
// false
// true
isEqual()은 static 메서드로, 인자로 전달되는 객체와 같은지 체크하는 Redicate 객체를 만들어 줍니다. 다음과 같이 사용할 수 있습니다.
Predicate<String> isEquals = Predicate.isEqual("Google");
isEquals.test("Google");
// 결과
// true
정리
자바에서 기본적으로 제공하는 함수형 인터페이스에 대해서 알아보았습니다. 혹시라도 정정할 내용이나 추가적으로 필요하신 정보가 있다면 댓글 남겨주시면 감사하겠습니다.
오늘도 Jindory 블로그에 방문해주셔서 감사합니다.
[ 참조 ]
'개발 > Java' 카테고리의 다른 글
[Java] JPA와 MyBatis의 차이(ORM과 SQL Mapper) (0) | 2022.04.13 |
---|---|
[Java] Java8의 변경사항 (0) | 2022.04.12 |
[Java] POI로 데이터 엑셀 다운받기 (0) | 2022.03.22 |
[Java] Java 나누기 사용시 주의사항 (0) | 2022.03.09 |
[Java] Json Array 정렬 (0) | 2022.03.06 |