반응형

함수형 프로그래밍?

함수형 프로그래밍은 데이터상태 변화다는 순수 함수를 조합하여 문제를 해결하는 데 중점을 둡니다. 주로 순수 함수불변성을 기반으로 하며, 부작용(Side Effect)을 최소화하고 상태 변화 없이 데이터를 처리하는 것을 목표로 합니다.

 

@FunctionalInterface는 Java 8부터 도입된 어노테이션으로, 인터페이스가 함수형 인터페이스임을 명확하게 지정하기 위해 사용됩니다. 함수형 인터페이스는 정확히 하나의 추상 메서드를 가지는 인터페이스로, 람다 표현식이나 메서드 참조를 사용할 때 주로 사용됩니다.

@FunctionalInterface의 역할

  1. 명시적인 선언: @FunctionalInterface 어노테이션을 사용하면, 해당 인터페이스가 함수형 인터페이스로 사용되기를 의도하고 있음을 명확하게 나타낼 수 있습니다. 이는 코드의 가독성을 높이고, 개발자가 의도를 쉽게 파악할 수 있도록 돕습니다.
  2. 컴파일러 검증: 어노테이션이 붙은 인터페이스가 정확히 하나의 추상 메서드를 가지는지 컴파일러가 검사합니다. 만약 두 개 이상의 추상 메서드가 있다면 컴파일 오류가 발생합니다. 이를 통해 실수로 다른 메서드를 추가하여 함수형 인터페이스의 규칙을 깨는 것을 방지할 수 있습니다.
  3. 람다 표현식 사용: 함수형 인터페이스는 람다 표현식과 함께 사용할 수 있습니다. 이는 함수형 프로그래밍 스타일을 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 어노테이션을 사용하고 있습니다. 대표적인 인터페이스로는 다음과 같습니다:

  1. Function<T, R>: 입력을 받아 출력으로 매핑하는 함수.
    1. a -> b 
    2. apply 메서드 사용.
  2. Consumer<T>: 입력을 받아 소비(Consumer)하고 반환값이 없는 함수.
    1. a -> () 
    2. accept 메서드 사용.
  3. Supplier<T>: 입력 없이 값을 반환하는 함수.
    1. () -> a 
    2. get 메서드 사용.
  4. Predicate<T>: 입력을 받아 논리값(boolean)을 반환하는 함수.
    1. a -> true/false 
    2. test 메서드 사용.
  5. UnaryOperator<T>: 동일한 타입의 입력을 받아 동일한 타입의 출력을 반환하는 함수.
    1. a -> a 
    2. function의 특수 타입으로 apply 사용
  6. BinaryOperator<T>: 동일한 타입의 두 개의 입력을 받아 동일한 타입의 출력을 반환하는 함수.
    1. a, a -> a 
    2. function의 특수 타입으로 apply 사용
  7. BiConsumer
    1. a, b -> ()
    2. accept
  8. BiFunction
    1. a, b -> c
    2. 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

+ Recent posts