반응형
함수형 프로그래밍?
함수형 프로그래밍은 데이터와 상태 변화보다는 순수 함수를 조합하여 문제를 해결하는 데 중점을 둡니다. 주로 순수 함수와 불변성을 기반으로 하며, 부작용(Side Effect)을 최소화하고 상태 변화 없이 데이터를 처리하는 것을 목표로 합니다.
@FunctionalInterface는 Java 8부터 도입된 어노테이션으로, 인터페이스가 함수형 인터페이스임을 명확하게 지정하기 위해 사용됩니다. 함수형 인터페이스는 정확히 하나의 추상 메서드를 가지는 인터페이스로, 람다 표현식이나 메서드 참조를 사용할 때 주로 사용됩니다.
@FunctionalInterface의 역할
- 명시적인 선언: @FunctionalInterface 어노테이션을 사용하면, 해당 인터페이스가 함수형 인터페이스로 사용되기를 의도하고 있음을 명확하게 나타낼 수 있습니다. 이는 코드의 가독성을 높이고, 개발자가 의도를 쉽게 파악할 수 있도록 돕습니다.
- 컴파일러 검증: 이 어노테이션이 붙은 인터페이스가 정확히 하나의 추상 메서드를 가지는지 컴파일러가 검사합니다. 만약 두 개 이상의 추상 메서드가 있다면 컴파일 오류가 발생합니다. 이를 통해 실수로 다른 메서드를 추가하여 함수형 인터페이스의 규칙을 깨는 것을 방지할 수 있습니다.
- 람다 표현식 사용: 함수형 인터페이스는 람다 표현식과 함께 사용할 수 있습니다. 이는 함수형 프로그래밍 스타일을 Java에서도 사용할 수 있게 해줍니다.
함수형 인터페이스의 조건
- 인터페이스 내에 정확히 하나의 추상 메서드가 있어야 합니다.
- default 메서드나 static 메서드는 여러 개 있을 수 있습니다.
- java.lang.Object 클래스에 있는 메서드(equals, hashCode, toString 등)는 추상 메서드의 수에 포함되지 않습니다.
@FunctionalInterface 사용 시 주의사항
- @FunctionalInterface 어노테이션을 사용하지 않아도 해당 인터페이스가 하나의 추상 메서드만 가지면 함수형 인터페이스로 간주되어 람다 표현식으로 사용할 수 있습니다. 하지만, 어노테이션을 사용하면 코드의 의도를 명확히 하고, 실수를 줄일 수 있습니다.
- 두 개 이상의 추상 메서드를 정의하려고 하면 컴파일 오류가 발생합니다.
@FunctionalInterface
public interface MyFunctionalInterface {
// 단 하나의 추상 메서드
void perform();
// default 메서드 (추상 메서드가 아니므로 함수형 인터페이스 규칙을 깨지 않음)
default void printMessage() {
System.out.println("Hello from default method!");
}
// static 메서드 (추상 메서드가 아니므로 함수형 인터페이스 규칙을 깨지 않음)
static void printStaticMessage() {
System.out.println("Hello from static method!");
}
}
public class FunctionalInterfaceExample {
public static void main(String[] args) {
// 람다 표현식으로 함수형 인터페이스 구현
MyFunctionalInterface myFunc = () -> System.out.println("Performing action");
myFunc.perform(); // 출력: Performing action
myFunc.printMessage(); // 출력: Hello from default method!
MyFunctionalInterface.printStaticMessage(); // 출력: Hello from static method!
}
}
표준 함수형 인터페이스
Java 8에서는 java.util.function 패키지에 여러 가지 표준 함수형 인터페이스를 제공하고 있습니다. 이들 역시 @FunctionalInterface 어노테이션을 사용하고 있습니다. 대표적인 인터페이스로는 다음과 같습니다:
- Function<T, R>: 입력을 받아 출력으로 매핑하는 함수.
- a -> b
- apply 메서드 사용.
- Consumer<T>: 입력을 받아 소비(Consumer)하고 반환값이 없는 함수.
- a -> ()
- accept 메서드 사용.
- Supplier<T>: 입력 없이 값을 반환하는 함수.
- () -> a
- get 메서드 사용.
- Predicate<T>: 입력을 받아 논리값(boolean)을 반환하는 함수.
- a -> true/false
- test 메서드 사용.
- UnaryOperator<T>: 동일한 타입의 입력을 받아 동일한 타입의 출력을 반환하는 함수.
- a -> a
- function의 특수 타입으로 apply 사용
- BinaryOperator<T>: 동일한 타입의 두 개의 입력을 받아 동일한 타입의 출력을 반환하는 함수.
- a, a -> a
- function의 특수 타입으로 apply 사용
- BiConsumer
- a, b -> ()
- accept
- BiFunction
- a, b -> c
- apply
compose
Java에서 Compose는 주로 함수형 프로그래밍 패러다임에서 함수 합성을 의미합니다. 이는 두 개 이상의 함수를 결합하여 하나의 함수를 만드는 과정. 여러 단계를 차례대로 실행하는 파이프라인을 구축할 수 있다.
Java의 java.util.function.Function 인터페이스는 함수 합성을 쉽게 할 수 있도록 두 가지 메서드를 제공한다:
- compose(f): 함수 f를 먼저 실행한 후, 그 결과를 현재 함수에 전달. 즉, 뒤의 함수가 먼저 실행.
- andThen(f): 현재 함수를 먼저 실행한 후, 그 결과를 함수 f에 전달. 즉, 앞의 함수가 먼저 실행.
function 예시
import java.util.function.Function;
public class FunctionComposeExample {
public static void main(String[] args) {
// 두 개의 간단한 함수 정의
Function<Integer, Integer> multiplyByTwo = x -> x * 2;
Function<Integer, Integer> addThree = x -> x + 3;
// compose(): addThree를 먼저 실행하고, 그 결과에 multiplyByTwo 적용
Function<Integer, Integer> composedFunction = multiplyByTwo.compose(addThree);
System.out.println(composedFunction.apply(5)); // 출력: (5 + 3) * 2 = 16
// andThen(): multiplyByTwo를 먼저 실행하고, 그 결과에 addThree 적용
Function<Integer, Integer> andThenFunction = multiplyByTwo.andThen(addThree);
System.out.println(andThenFunction.apply(5)); // 출력: (5 * 2) + 3 = 13
}
}
predicate 예시
import java.util.function.Predicate;
public class PredicateComposeExample {
public static void main(String[] args) {
Predicate<Integer> isEven = x -> x % 2 == 0;
Predicate<Integer> isPositive = x -> x > 0;
// 두 Predicate를 and()로 합성
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
System.out.println(isEvenAndPositive.test(4)); // 출력: true
System.out.println(isEvenAndPositive.test(-4)); // 출력: false
// or()로 합성
Predicate<Integer> isEvenOrPositive = isEven.or(isPositive);
System.out.println(isEvenOrPositive.test(-3)); // 출력: false
System.out.println(isEvenOrPositive.test(4)); // 출력: true
// negate()로 부정
Predicate<Integer> isOdd = isEven.negate();
System.out.println(isOdd.test(3)); // 출력: true
}
}
consumer 예시
import java.util.function.Consumer;
public class ConsumerComposeExample {
public static void main(String[] args) {
Consumer<String> printUpperCase = s -> System.out.println(s.toUpperCase());
Consumer<String> printLowerCase = s -> System.out.println(s.toLowerCase());
// andThen()을 사용하여 두 Consumer를 합성
Consumer<String> combinedConsumer = printUpperCase.andThen(printLowerCase);
combinedConsumer.accept("Compose Example"); // 출력: COMPOSE EXAMPLE, compose example
}
}
supplier 예시
import java.util.function.Supplier;
import java.util.function.Function;
public class SupplierComposeExample {
public static void main(String[] args) {
Supplier<String> stringSupplier = () -> "Hello";
Function<String, Integer> stringLength = String::length;
// Supplier의 결과를 Function에 전달
int length = stringLength.apply(stringSupplier.get());
System.out.println(length); // 출력: 5
}
}
728x90
반응형
'개발 > java' 카테고리의 다른 글
[test] object mother (2) | 2024.09.26 |
---|---|
자바 버전별 특징 (0) | 2024.09.23 |
Runnable vs Callable (0) | 2024.09.20 |
[힙 덤프] 떠있는 프로세스 분석 (0) | 2024.09.19 |
불변객체 만들기 ImmutableCollections.class (0) | 2024.09.14 |