Notice
Recent Posts
Recent Comments
Link
관리 메뉴

look-forest

함수형 인터페이스와 람다 본문

Java/모던 자바(Java 8+)

함수형 인터페이스와 람다

studyHub 2024. 11. 7. 22:49

함수형 인터페이스와 람다 표현식 소개

함수형 인터페이스 (Functional Interface)

  • 추상 메소드를 딱 하나만 가지고 있는 인터페이스
    static, default 메소드는 있어도 된다. ( abstract 생략 가능 )
  • @FunctionalInterface를 붙이면 abstract 메소드를 하나만 사용하도록 보장할 수 있다. 

 

람다 표현식 (Lambda Expressions)

  • 함수형 인터페이스의 인스턴스를 만드는 방법
RunSomeThing runSomeThing1 = new RunSomeThing() {
    @Override
    public void doIt() {
        System.out.println("Anonymous inner class");
    }
};
runSomeThing1.doIt();

RunSomeThing runSomeThing2 = () -> System.out.println("lambda expression");
runSomeThing2.doIt();

JAVA에서 제공하는 함수형 인터페이스

Function<T, R>

  • T 타입을 받아서 R 타입을 리턴하는 함수 인터페이스
    • R apply(T t)
  • 함수 조합용 메소드
    • andThen
    • compose

BiFunction<T, U, R>

  • 두 개의 값(T, U)를 받아서 R 타입을 리턴하는 함수 인터페이스
    • R apply(T t, U u)

Consumer<T>

  • T 타입을 받아서 아무값도 리턴하지 않는 함수 인터페이스
    • void accept(T t)
  • 함수 조합용 메소드
    • andThen

Supplier<T>

  • T 타입의 값을 제공하는 함수 인터페이스
    • T get()

Predicate<T>

  • T 타입을 받아서 boolean을 리턴하는 함수 인터페이스
    • boolean test(T t)
  • 함수 조합용 메소드
    • And
    • Or
    • Negate

UnaryOperator<T>

  • Function<T, R>의 특수한 형태로, 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스

 

BinaryOperator<T>

  • BiFunction<T, U, R>의 특수한 형태로, 동일한 타입의 입렵값 두개를 받아 리턴하는 함수 인터페이스
Function<Integer, Integer> plus10 = (input) -> input + 10;
Function<Integer, Integer> multiple10 = (input) -> input * 10;

System.out.println(plus10.compose(multiple10).apply(2) == 30);
System.out.println(plus10.andThen(multiple10).apply(2) == 120);

Consumer<Integer> print = (i) -> System.out.println("i = " + i);
print.accept(2);

Supplier<Integer> supply2 = () -> 2;
System.out.println(supply2.get());

Predicate<String> isStartWithA = s -> s.startsWith("a");
System.out.println("isStartWithA.test() = " + isStartWithA.test("pineapple"));


UnaryOperator<Integer> uPlus10 = input -> input + 10;
BinaryOperator<Integer> bMultiple10 = (input1, input2) -> input1 * input2;

람다 표현식

(인자 리스트) -> {바디}

 

인자 리스트

  • 인자가 없을 때: ()
  • 인자가 한개일 때: (one) 또는 one
  • 인자가 여러개 일 때: (one, two)
  • 인자의 타입은 생략 가능, 컴파일러가 추론(infer)하지만 명시할 수도 있다. (Integer one, Integer two)

바디

  • 함수 본문 정의
  • 여러 줄인 경우에 { }를 사용해서 묶는다.
  • 한 줄인 경우에 생략 가능, return생략 가능.

 

변수 캡처 (Variable Capture)

  • 로컬 변수 캡처
    • final이거나 effective final (사실상 final인 변수) 인 경우에만 참조할 수 있다.
    • 그렇지 않을 경우 concurrency 문제가 생길 수 있어서 컴파일가 방지한다.
    더보기

    만약 람다가 final이 아닌 변경 가능한 변수를 참조할 수 있다면, 람다식의 실행 시점에 그 변수의 값이 변경될 가능성이 생긴다. 이렇게 되면 람다의 실행 결과가 예측할 수 없게 되고, 특히 멀티스레드 환경에서는 경쟁 조건(Race Condition)이 발생할 수도 있다. 이러한 예측 불가능성과 스레드 안정성 문제를 방지하기 위해 final이나 effective final이 아닌 변수는 람다에서 사용할 수 없도록 제한을 두고 있다.

     

    람다에서 이렇게 유난을 떠는 이유는 람다식은 본질적으로 함수형 프로그래밍의 특징을 지향하기 때문이다.

    람다는 일반적인 메서드나 클래스가 아닌 순수한 함수형 프로그래밍 요소로 설계되었다. 함수형 프로그래밍에서는 함수를 변수처럼 다른 함수에 전달하거나, 결과를 예측할 수 있는 동작을 가진 순수 함수를 사용하여, 프로그램의 상태 변화나 부작용을 최소화한다.

  • 익명 클래스 구현체와 달리 ‘쉐도윙’하지 않는다. 
    익명 클래스는 새로 스콥을 만들지만, 람다는 람다를 감싸고 있는 스콥과 같다
private void run() {
    int baseNumber = 10;

    //Local class
    class LocalClass {
        void pintBaseNumber() {
            int baseNumber = 11;
            System.out.println("baseNumber = " + baseNumber); //11
        }
    }

    //Anonymous class
    IntConsumer integerConsumer = new IntConsumer() {
        public void accept(int baseNumber) {
            System.out.println("baseNumber = " + baseNumber); //11
        }
    };

    //Lambda
    IntConsumer intConsumer = i -> System.out.println(i + baseNumber); //i를 baseNumber로 변경 불가..
}

메소드 레퍼런스

람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현할 수 있다. 즉, 함수형 인터페이스의 구현체를 기존 메소드를 갖다 쓰는 것이다.

 

메소드 참조하는 방법

  • 메소드 또는 생성자의 매개변수로 람다의 입력값을 받는다.
  • 리턴값 또는 생성한 객체는 람다의 리턴값이다.
스태틱 메소드 참조 타입::스태틱 메소드
특정 객체의 인스턴스 메소드 참조 객체 레퍼런스::인스턴스 메소드
임의 객체의 인스턴스 메소드 참조 타입::인스턴스 메소드
생성자 참조 타입::new
public static void main(String[] args) {
    //스태틱 메서드 참조
    UnaryOperator<String> staticRef = Greeting::hi;
    staticRef.apply("jaeryang");

    //인스턴스 메소드 참조
    Greeting greeting = new Greeting();
    UnaryOperator<String> instanceRef = greeting::hello;
    instanceRef.apply("jaeryang");

    //생성자 참조
    Supplier<Greeting> greetingSupplier = Greeting::new;
    greetingSupplier.get();

    //인자가 있는 생성자
    Function<String, Greeting> greetingFunction = Greeting::new;
    greetingFunction.apply("jaeryang");

    //임의의 인스턴스의 메소드 참조
    String[] names = {"jaeryang", "jaelim", "janghwan"};
    // new Comparator<String>() compare 구현
    Arrays.sort(names, String::compareToIgnoreCase); //"jaeryang".compareToIgnoreCase("jaelim")
}

 

 


참고 자료 & 이미지 출처
더 자바, Java8 (백기선 님)

'Java > 모던 자바(Java 8+)' 카테고리의 다른 글

CompletableFuture  (0) 2024.11.09
Date와 Time  (0) 2024.11.09
Optional  (0) 2024.11.09
Stream  (0) 2024.11.09
인터페이스의 변화  (0) 2024.11.08