객체지향 언어인 자바가 8로 접어들면서 "함수형 개발 패러다임"을 지원하기 시작했다고 합니다.
이를 통해 자바가 재사용이 가능한 코드 조각을 조금더 만들 수 있게 되고 유연한 개발을 할 수 있는 가능성이 늘어났다고 합니다.
그렇다면 이 함수형 개발 방식은 객체 지향 개발 방식과 무엇이 다를까요?
둘의 가장 큰 차이는
값을 취급하는 단위가 어디까지 인지에 따라 나눌 수 있다
고 합니다.
자바는 값(상태)와 행위를 다루기 위한 기본 단위를 객체로 정의하고 이 객체를 클래스라는 형태로 구현하였습니다.
이에 반해 함수형 개발 방식은 행위에 해당하는 부분도 값으로 취급이 가능해졌습니다.
이는 코드 재활용 단위가 클래스에서 함수 단위로 재사용이 가능해지면서 좀 더 유연한 개발을 할 수 있게 됩니다.
예를 들어 Calculator
라는 클래스가 있고 이 Calculator
라는 틀의 add
메소드를 실행했다면,
함수형 인터페이스를 통해 add
메소드에 대한 틀을 만들어 바로 재사용할 수 있습니다!
class Calculator {
public int add(int a, int b) {
return a+b;
}
}
public class 함수형_인터페이스 {
public static void main(String[] args) {
BinaryOperator<Integer> add = Integer::sum;
System.out.printf("객체 지향 개발 방식 %d\n", new Calculator().add(3, 5));
System.out.printf("함수형 개발 방식 %d", add.apply(3, 5));
}
}
// 출력
// 객체 지향 개발 방식 8
// 함수형 개발 방식 8
인자와 반환값에 따라 다양한 함수형 인터페이스를 생성할 수 있습니다.
종류 | 인자 | 반환 | 설명 |
---|---|---|---|
Runnable | 기본적인 형태의 인터페이스, 인자와 반환값 모두 없음 | ||
Supplier<T> | <T> | 인자가 없이 제너릭 타입의 반환값만 있는 인터페이스, 항상 같은 값을 반환 | |
Consumer<T> | <T> | 제너릭 타입의 인자만 있고 반환값이 없는 인터페이스 | |
Predicate<T> | <T> | Boolean | 제너릭 타입의 인자와 Boolean 타입의 반환값을 가지는 인터페이스 |
Function<T, R> | <T> | <R> | 제너릭 타입의 인자와 다른 제너릭 타입의 반환값이 같이 있는 인터페이스 |
UnaryOperator<T> | <T, T> | <T> | 같은 제너릭 타입의 인자와 반환값을 가지고 있는 인터페이스 |
BinaryOperator<T> | <T, T> | <T> | 같은 제너릭 타입의 인자 2개를 받고 같은 제너릭 타입의 반환값을 가지는 인터페이스 |
BiConsumer<T, U> | <T, U> | 다른 제너릭 타입의 인자 2개를 받고 반환값이 없는 인터페이스 | |
BiPredicate<T, U> | <T, U> | Boolean | 다른 제너릭 타입의 인자 2개를 받고 Boolean 타입의 반환값을 가지는 인터페이스 |
BiFunction<T, U, R> | <T, U> | <R> | 다른 제너릭 타입의 인자 2개를 받고 다른 제너릭 타입의 반환값을 가지는 인터페이스 |
Comparator<T> | <T, T> | int | 같은 제너릭 타입의 인자 2개를 받고 Integer 반환값을 가지는 인터페이스, 객체 간의 값을 비교하기 위한 compare 기능을 위한 인터페이스 |
"Consumer"는 매개변수를 소비한다는 의미로, accpet()
를 통해 접근하고 리턴값은 가지지 않습니다.
인터페이스명 | 추상메소드 | 설명 |
---|---|---|
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 값을 받아 소비 |
매개변수의 타입에 따라 조금씩 이름이 다르지만, 함수의 수행 로직은 같습니다.
public class 함수형_인터페이스 {
public static void main(String[] args) {
Consumer consumer = s -> System.out.println("입력 값 출력 : " + s);
consumer.accept("hello");
}
}
// 출력
// 입력 값 출력 : hello
Supplier
는 매개변수가 없고 리턴 값만 존재하는 get()
메소드를 가집니다.
인터페이스명 | 추상 메소드 | 설명 |
---|---|---|
Supplier<T> | T get() | T 객체를 리턴 |
BooleanSupplier | boolean getAsBoolean() | boolean 값을 리턴 |
DoubleSupplier | double getAsDouble() | double 값을 리턴 |
IntSupplier | int getAsInt() | int 값을 리턴 |
LongSupplier | long getAsLong() | long 값을 리턴 |
public class 함수형_인터페이스 {
public static void main(String[] args) {
Supplier<String> success = () -> {
return "성공했습니다.";
};
String message = success.get();
System.out.println(message);
}
}
// 출력
// 성공했습니다.
Function
인터페이스는 매개 변수와 리턴값이 있는 apply()
메소드를 가집니다.
이 메소드는 매개값을 리턴값으로 매핑(타입 변화)하는 역할을 합니다.
인터페이스명 | 추상 메소드 | 설명 |
---|---|---|
Function<T, R> | R apply(T t) | 객체 T를 객체 R로 매핑 |
BiFuction<T, U, R> | R apply(T t, U u) | 객체 T, U을 객체 R로 매핑 |
DoubleFuction<R> | R apply(double value) | double을 객체 R로 매핑 |
IntFuction<R> | R apply(int value) | int를 객체 R로 매핑 |
IntToDoubleFuction | double apply(int value) | int를 double로 매핑 |
LongToDoubleFunction | double applyAsDouble(long value) | long을 double로 매핑 |
ToDoubleBiFunction<T, U> | double applyAsDouble(T t, U u) | 객체 T, U를 double로 매핑 |
public class 함수형_인터페이스 {
public static void main(String[] args) {
Function<String, String> messageFunction = s -> {
return "메세지 \""+s+"\"를 전송하였습니다.";
};
String message = "안녕하세요";
System.out.println(messageFunction.apply(message));
}
}
// 출력
// 메세지 "안녕하세요"를 전송하였습니다.
함수형 인터페이스를 처음에 올리고, 다시 차근차근 공부해보았는데요.
예제까지 직접 코딩해보면서 정리해보니 이해가 잘 되네요.
뭔가 고생 끝에 정답을 찾은 느낌이 너무 좋습니다ㅠㅠ
다른 좋은 예제나 코드가 있으면 더 업데이트하도록 하겠습니다!!