[Java] 함수형 인터페이스(Functional Interface)
함수형 인터페이스(Functional Interface)는 하나의 추상 메서드만을 가지며, 람다식 또는 메서드 참조를 이용해 함수 객체를 생성할 수 있는 인터페이스입니다.
Java는 Java 8부터 함수형 인터페이스를 통해 함수형 프로그래밍을 지원합니다.
함수형 인터페이스 생성
함수형 인터페이스는 아래와 같이 1개의 추상 메소드가 있어야 합니다.
@FunctionalInterface
어노테이션은 이 인터페이스가 함수형 인터페이스라는 것을 표시int calculateLength(String str)
: 인자 1개를 받고 리턴 값이 있는 추상 메소드
@FunctionalInterface
interface StringLengthCalculator {
int calculateLength(String str);
}
함수형 인터페이스 사용 방법
위에서 만든 함수형 인터페이스 StringLengthCalculator를 람다 표현식으로 구현하였습니다.
- 구현된 객체는
calculator.calculateLength("Hello, World!")
처럼 호출할 수 있고, 결과를 받음 - 람다 표현식과 함께 사용하여 간결하고 가독성 있는 코드를 작성할 수 있음
public class Example {
@FunctionalInterface
interface StringLengthCalculator {
int calculateLength(String str);
}
public static void main(String[] args) {
StringLengthCalculator calculator = (str) -> str.length();
int length = calculator.calculateLength("Hello, World!");
System.out.println("Length of the string: " + length);
}
}
Output:
Length of the string: 13
함수형 인터페이스를 익명 클래스로 구현
아래 예제는 람다 대신에 익명 클래스를 이용하여 인터페이스를 구현하였습니다. 이렇게 구현하면 람다보다 코드 양이 많아집니다.
- 람다와 익명 클래스 구현의 동작은 동일
- 람다로 구현하는게 간결하고 가독성 있는 프로그램을 만들 수 있음
public class Example {
@FunctionalInterface
interface StringLengthCalculator {
int calculateLength(String str);
}
public static void main(String[] args) {
StringLengthCalculator calculator = new StringLengthCalculator() {
@Override
public int calculateLength(String str) {
return str.length();
}
};
int length = calculator.calculateLength("Hello, World!");
System.out.println("Length of the string: " + length);
}
}
Output:
Length of the string: 13
Java의 기본 함수형 인터페이스
함수형 인터페이스를 사용할 때마다 인터페이스를 직접 구현해야 한다면, 매우 번거로울 수 있습니다.
이 때문에 다음과 같이 자바에서 기본적으로 제공하는 함수형 인터페이스가 있습니다. 개발하는데 사용되는 함수형 인터페이스는 대부분 있기 때문에, 새로운 함수형 인터페이스를 만들어야 하는 경우는 많지 않을 것 같습니다.
각 인터페이스에 대한 자세한 내용 및 예제는 아래에서 소개하겠습니다.
- Runnable: () -> void
- Supplier: () -> T
- Consumer: T -> void
- Function: T -> R
- Predicate: T -> boolean
- BiFunction: (T, U) -> R
- UnaryOperator: T -> T
- BinaryOperator: (T, T) -> T
Runnable: () -> void
인자를 받지 않고 리턴 값도 없는 함수형 인터페이스입니다.
public interface Runnable {
public abstract void run();
}
아래 예제는 Runnable 인터페이스를 구현하여 스레드에서 실행할 코드를 구현하고, 이를 스레드에서 실행합니다.
- Runnable은
run()
으로 함수 호출 - Thread는 Runnable 인터페이스를 인자로 받음, 쓰레드 내부에서
run()
호출 - 인자가 필요하지 않고, 리턴할 필요도 없는 경우 Runnable을 사용하면 됨
public class Example {
public static void main(String[] args) {
Runnable runnable = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("Hello, World!");
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
Output:
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Supplier: () -> T
인자는 받지 않고 T 타입의 객체를 리턴하는 함수형 인터페이스입니다.
public interface Supplier<T> {
T get();
}
아래 예제는 Supplier 인터페이스를 사용하여 특정 값을 제공합니다.
- 예제의 Supplier의 Integer 타입 42를 항상 리턴하도록 구현됨
- Supplier는
get()
으로 함수 호출
import java.util.function.Supplier;
public class Example {
public static void main(String[] args) {
Supplier<Integer> supplier = () -> 42;
int result = supplier.get();
System.out.println("Result: " + result);
}
}
Output:
Result: 42
Consumer: T -> void
T 타입의 객체를 인자로 받으며, 리턴 값은 없는 함수형 인터페이스입니다.
public interface Consumer<T> {
void accept(T t);
}
아래 예제에서 Consumer는 인자로 받은 String을 메시지로 출력합니다.
- Consumer는
accept()
로 함수 수행 - 화면에 문자열만 출력하고 리턴 값은 없음
import java.util.function.Consumer;
public class Example {
public static void main(String[] args) {
Consumer<String> consumer = (message) -> System.out.println("Message: " + message);
consumer.accept("Hello, Consumer!");
}
}
Output:
Message: Hello, Consumer!
Function: T -> R
T 타입의 객체를 인자로 받으며, R 타입 객체를 리턴하는 함수형 인터페이스입니다.
public interface Function<T, R> {
R apply(T t);
}
아래 예제는 Integer 값을 인자로 받아서 문자열로 만들고 String 값을 리턴합니다.
- Function은
apply()
로 함수 호출
import java.util.function.Function;
public class Example {
public static void main(String[] args) {
Function<Integer, String> function = (num) -> "Number: " + num;
String result = function.apply(123);
System.out.println(result);
}
}
Output:
Number: 123
Predicate: T -> boolean
T 타입의 객체를 인자로 받으며, boolean을 리턴하는 함수형 인터페이스입니다.
public interface Predicate<T> {
boolean test(T t);
}
아래 예제는 인자로 전달된 Integer가 짝수인지 확인 후 결과를 boolean으로 리턴합니다.
- Predicate는
test()
로 함수 호출
import java.util.function.Predicate;
public class Example {
public static void main(String[] args) {
Predicate<Integer> isEven = (num) -> num % 2 == 0;
boolean result = isEven.test(10);
System.out.println("Is 10 even? " + result);
}
}
Output:
Is 10 even? true
BiFunction: (T, U) -> R
T, U 타입의 객체 2개를 인자로 받으며, R 타입 객체를 리턴하는 함수형 인터페이스입니다.
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
아래 예제는 Integer 2개를 인자로 전달하면 두개의 합을 Integer로 리턴합니다.
- BiFunction은
apply()
로 함수 호출
import java.util.function.BiFunction;
public class Example {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
int result = add.apply(5, 3);
System.out.println("5 + 3 = " + result);
}
}
Output:
5 + 3 = 8
UnaryOperator: T -> T
T 타입 객체를 인자로 받으며, T 타입 객체를 리턴하는 함수형 인터페이스입니다.
public interface UnaryOperator<T> extends Function<T, T> {
// Function<T, T> 를 상속
}
아래 예제는 Integer를 인자로 받고, 제곱을 계산하여 리턴합니다.
- UnaryOperator는
apply()
로 함수 호출 UnaryOperator<T>
는Function<T, T>
는 동일 인자, 동일 리턴 타입
import java.util.function.UnaryOperator;
public class Example {
public static void main(String[] args) {
UnaryOperator<Integer> square = (num) -> num * num;
int result = square.apply(4);
System.out.println("Square of 4: " + result);
}
}
Output:
Square of 4: 16
BinaryOperator: (T, T) -> T
T 타입 객체 두개를 인자로 받고, T 타입 객체를 리턴하는 함수형 인터페이스입니다.
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
// BiFunction<T,T,T>를 상속
}
아래 예제는 두개의 Integer를 받고, 두 숫자의 곱을 Integer로 리턴합니다.
- BinaryOperator는
apply()
로 함수 호출 BinaryOperator<Integer>
와BiFunction<Integer, Integer, Integer>
는 동일 인자, 동일 리턴 타입
import java.util.function.BinaryOperator;
public class Example {
public static void main(String[] args) {
BinaryOperator<Integer> multiply = (a, b) -> a * b;
int result = multiply.apply(6, 7);
System.out.println("6 * 7 = " + result);
}
}
Output:
6 * 7 = 42