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

  • 추상 메소드를 1개만 가지고 있는 인터페이스
  • SAM (Single Abstract Method) 인터페이스
@FunctionalInterface    // 자바가 제공해주는 functionalInterface (인터페이스를 견고하게 관리)
public interface RunSomthing {

    void doIt();    // 하나만 있으면 함수형 인터페이스 (2개이상부터 X)
}

@FunctionalInterface 어노테이션을 추가하면 함수형 인터페이스로 선언해줍니다. (추상 메소드를 하나 더 추가하려하면 개발자가 잘 못된 것을 알려줍니다.)  (이를통해, 디버깅 전에 에러를 확인할 수 있도록합니다.)

추상 메소드를 2개이상하는 경우 에러 표시

 

 

 

람다 표현식 (Lamda Expressions)

  • 함수형 인터페이스의 인스턴스를 만드는 방법으로 쓰일 수 있다.
  • 코드를 간결하게 할 수 있다.
  • 메소드 매개변수, 리턴 타입, 변수로 만들어 사용할 수 있다.
        // 익명 내부 클래스 (함수형 인터페이스가 아닌 경우)
        RunSomthing runSomthing = new RunSomthing() {
            @Override
            public void doIt() {

            }
        };

        // 함수형 인터페이스의 경우 java 8 부터 람다로 변경 가능
        RunSomthing runSomthingLamda = () -> System.out.println("Lamda");

함수형 인터페이스의 경우 람다 표현식으로 이용해 코드를 훨씬 간결하게 만들 수 있는 것을 볼 수 있다. 

 

 

 

자바에서 함수형 프로그래밍

  • 함수를 First class object로 사용할 수 있다.
  • 순수 함수 (Pure Function)
    • 사이드 이팩트가 없다. (함수 밖에 있는 값을 변경하지 않는다.) (같은 값을 넣으면 같은 값으로 결과가 나와야함)
    • 상태가 없다. (함수 밖에 있는 값을 사용하지 않는다.)
  • 고차 함수 (Higher-Order Function)
    • 함수가 함수를 매개변수로 받을 수 있고 함수를 리턴할 수도 있다.
  • 불변성

 

 

Java에서 제공하는 기본 함수형 인터페이스

 

java.util.function (Java Platform SE 8 )

Interface Summary  Interface Description BiConsumer Represents an operation that accepts two input arguments and returns no result. BiFunction Represents a function that accepts two arguments and produces a result. BinaryOperator Represents an operation u

docs.oracle.com

  1. Function<T,R>
    • T 타입을 받아서 R 타입을 리턴하는 함수 인터페이스
      • R apply(T t)
      • Function<Integer, Integer> plus10 = (i) -> i+10;  과 같이 람다를 이용해 쉽게사용할 수 있다.
    • 함수 조합용 메소드
      • andThen
      • compose
  2. BiFunction<T,U,R>
    • 두 개의 값 (T, U)를 받아서 R 타입을 리턴하는 함수 인터페이스 (Function에서 파라미터만 추가 된 느낌)
      • R apply(T t, U u)
  3. Consumer<T>
    • T 타입을 받기만 하는 함수 인터페이스 (리턴이 없다.)
      • void accept(T t)
  4. Suplier<T>
    • T 타입의 값을 제공하는 함수 인터페이스
      • T get()
  5. Predicate<T>
    • T 타입을 받아서 boolean을 리턴하는 함수 인터페이스
      • boolean test(T t)
  6. UnaryOperator<T>
    • Function<T,R>의 특수한 형태로 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스 (Function<T,T> 느낌)
  7. BinaryOperator<T>
    • BiFunction<T,R,U>의 특수한 형태로 동일한 타입의 입력값 두개를 받아 리턴하는 함수 인터페이스 (BiFunction<T,T,T> 느낌)
  8. ..... 등등 다양한 함수형 인터페이스들을 제공해 주고있다.

 

 

 

 

람다 표현식의 Scope

private void run() {
    final int baseNumber = 10;

    // 로컬 클래스
    class LocalClass {
        void printBaseNumber() {
            System.out.println(baseNumber);
        }
    }

    // 익명 클래스
    Consumer<Integer> integerConsumer = new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) {
            System.out.println(baseNumber);
        }
    };

    // 람다
    IntConsumer printInt = (i) -> {
        System.out.println(i+baseNumber);
    };

    printInt.accept(10);
}

run 메소드에서 로컬클래스, 익명클래스, 람다를 통해 run 메소드에서 정의한 변수를 사용하면 정상적으로 사용하는 것을 볼 수 있었다. 하지만 로컬클래스, 익명클래스와 람다는 다른 점이 하나 있다. 로컬클래스와 익명클래스는 쉐도잉이 되는 반면, 람다는 쉐도잉이 되지 않는다는 것이다. 그렇다면 쉐도잉이 무엇일까??

말하자면 그림과 같다. 위의 코드를 보면 run 에서 baseNumber이라는 변수를 선언했다. 그 후 baseNumber라는 변수를 로컬클래스, 익명클래스에서 사용한다면 새로운 값으로 대체할 수 있다. 하지만, 람다의 경우에는 같은 변수명을 사용할 수 없다는 에러가 발생한다. 

실제 람다에서 변수를 선언했을 때 나는 에러

그렇다면 이유는 무엇일까?? 이유는 로컬클래스와 익명클래스는 Scope이 선언해주는 곳과 같지 않기때문이고, 람다의 경우는 Scope이 선언해주는 곳과 같기 때문이다.

 

 

 

메소드 레퍼런스

람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현할 수 있다.

메소드 참조하는 방법

스태틱 메소드 참조 타입::스태틱 메소드
특정 객체의 인스턴스 메소드 참조 객체 레퍼런스::인스턴스 메소드
임의 객체의 인스턴스 메소드 참조 타입::인스턴스 메소드
생성자 참조 타입::new
  • 메소드 또는 생성자의 매개변수로 람다의 입력값을 받는다.
  • 리턴값 또는 생성한 객체는 람다의 리턴값이다.

예를들어 보겠다. 먼저 아래와 같은 Greeting이라는 클래스가 있다.

package me.qazyj.java8;

public class Greeting {

    private String name;

    public Greeting() {}

    public Greeting(String name){
        this.name = name;
    }

    public String hello(String name) {
        return "hello "+ name;
    }

    public static String hi(String name){
        return "hi " + name;
    }
}

사용한 예시를 보면 아래와 같다.

    public static void main(String[] args) {
        // static 메소드
        UnaryOperator<String> hi = Greeting::hi;

        // non-static 메소드
        //UnaryOperator<String> hi = Greeting::hello; (불가)
        Greeting greeting = new Greeting();
        UnaryOperator<String> hello = greeting::hello;

        // 임의 객체의 인스턴스 메소드 참조
        String[] names = {"k", "y", "j"};
        Arrays.sort(names, String::compareToIgnoreCase);//타입::인스턴스 메소드

        // 입력값이 없는 생성자로 생성
        Supplier<Greeting> newGre = Greeting::new;

        // 입력값이 있는 생성자로 생성
        Function<String, Greeting> kyjGree = Greeting::new;
        Greeting kyj = kyjGree.apply("kyj");
    }

 

'JAVA' 카테고리의 다른 글

[Java 8] Optional  (0) 2022.10.07
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06
ListIterator  (0) 2022.09.23
SnakeGame  (0) 2022.07.16

+ Recent posts