본문 바로가기
Java

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

by hseong 2023. 6. 18.

1. 함수형 인터페이스

  • 함수형 인터페이스는 오직 하나의 추상 메서드만을 지정하는 인터페이스이다.
  • 이 때, 디폴트 메서드, 스태틱 메서드의 포함 여부는 상관없다.
  • 람다 표현식을 통해 함수형 인터페이스의 추상 메서드 구현을 전달할 수 있다. 그러므로 해당 표현식을 함수형 인터페이스의 인스턴스로 취급할 수 있다.
@FunctionalInterface
public interface Runnable {
  public abstract void run(); 
}
  • @FunctionalInterface 애노테이션을 통해 함수형 인터페이스임을 컴파일러에 알려줄 수 있다. 만일 두 개 이상의 함수형 인터페이스를 선언한다면 컴파일 에러가 발생한다.

1.1 익명 클래스

  • 자바는 클래스 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스라는 기법을 제공한다.
  • 말 그대로 이름이 없는 클래스이다. 이를 통해 필요한 구현을 즉석에서 만들어 사용할 수 있다.
public class Main {
  public static void main(String[] args) {
    Runnable hello = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello World"); 
        } 
    };
    hello.run(); 
  } 
}

2. 람다 표현식

  • 익명 클래스가 이름이 없는 클래스였다면, 람다 표현식은 이름이 없는 익명 함수라고 할 수 있다.
  • 이름은 없으나 파라미터 리스트, 바디, 반환 타입, 발생할 수 있는 예외 리스트를 가질 수 있다.

람다는 세 부분으로 이루어진다.

(Integer number) -> String.valueOf(number);

파라미터 리스트

  • String.valueOf() 메서드의 파라미터
  • 화살표*
  • 람다의 파라미터 리스트와 바디를 구분
  • 바디*
  • Integer 타입의 number를 받아서 문자열로 반환해준다.

람다는 문법에 따라 표현식 스타일과 블록 스타일 두 가지로 나뉜다.

(parameters) -> expression

(parameters) -> {
    statements;
}

람다 표현식을 이용하여 함수형 인터페이스의 간결한 구현이 가능하다.

public class Main {
    public static void main(String[] args) {
        Runnable hello = () -> System.out.println("Hello World");
        hello.run();
    }
}

2.1 다양한 함수형 인터페이스

java.util.function 패키지는 여러 가지 함수형 인터페이스를 제공한다.

Predicate

  • test() 라는 추상 메서드를 정의하며 제네릭 형식 T 타입 객체를 인수로 받아 boolean을 반환한다.
  • @FunctionalInterface public interface Predicate<T> { boolean test(T t); }

Consumer

  • 제네릭 형식 T 타입 객체를 받아 void를 반환하는 추상 메서드 accept() 를 정의한다.
  • T 타입의 객체를 가지고 어떠한 동작을 수행하고 할 때 사용할 수 있다.
  • @FunctionalInterface public interface Consumer<T> { void accept(T t); }

Supplier

  • T 타입의 객체를 반환하는 추상 메서드 get() 을 정의한다.
  • 이름 그대로 어떠한 값의 공급자 역할을 수행한다.
  • @FunctionalInterface public interface Supplier<T> { T get(); }

Function

  • 제네릭 형식 T 타입 객체를 받아 R 타입 객체를 반환하는 추상 메서드 apply() 를 정의한다.
  • 어떠한 입력을 다른 값으로 매핑하여 반환하는데 사용할 수 있다.
  • @FunctionalInterface public interface Function<T, R> { R apply(T t); }

기본형 특화

  • 오토 박싱으로 인한 비용을 줄이기 위해 기본형을 위한 함수형 인터페이스 또한 제공된다.
  • 다음은 기본형 int 변수를 받아 R 타입 객체를 반환한는 함수형 인터페이스이다.
  • @FunctionalInterface public interface IntFunction<R> { R apply(int value); }
  • 이 외에도 DoublePredicate, IntCounsumer와 같이 이름 앞에 형식명이 붙어 특정 형식을 입력으로 받는 다양한 함수형 인터페이스가 존재한다.
  • 또한 두 개의 값을 받는 함수형 인터페이스(BiFunction, BiConsmer, ...)도 제공한다.

2.2 형식 추론

  • 자바 컴파일러는 람다 표현식이 사용된 context를 파악하여 관련된 함수형 인터페이스를 추론한다.
  • 이를 통해 컴파일러는 람다의 시그니처를 추론할 수 있으므로 명시적으로 타입을 포함하지 않을 수 있다.

2.3 지역 변수

  • 람다는 파라미터로 넘겨진 변수 외에도 외부의 변수를 자유롭게 사용할 수 있다.
  • 단, 해당 변수는 final로 선언되어야 하거나 실질적으로 final로 선언된 변수와 동일하게 사용되어야 한다.

3. 메서드 참조

  • 특정 메서드를 호출하는 람다 표현식의 축약형이라고 할 수 있다.
  • 메서드명 앞에 구분자(::)를 붙이는 방식으로 메서드 참조를 사용할 수 있다.
  • IntFunction<String> function1 = (number) -> String.valueOf(number); IntFunction<String> function2 = String::valueOf;
  • 메서드 참조 유형*
  1. 정적 메서드 참조
  • String의 valueOf() 메서드는 String::valueOf로 표현 가능하다.
  1. 인스턴스 메서드 참조
  • String의 length() 메서드는 String::length로 표현 가능하다.
    IntFunction<String> function1 = (number) -> String.valueOf(number);
    IntFunction<String> function2 = String::valueOf;
  1. 기존 객체의 인스턴스 메서드 참조
  • 다음과 같이 지역 변수를 참조하는 경우 지역변수::메서드명()으로 표현 가능하다.
    String name = "java";
    IntSupplier s1 = () -> name.length();
    IntSupplier s2 = name::length;

3.1 생성자 참조

클래스명::new 처럼 클래스명과 new 키워드를 이용해서 생성자 참조를 만들 수 있다. 이를 이용하여 인스턴스화 하지 않고 생성자에 접근 하는 것도 가능하다.

'Java' 카테고리의 다른 글

Java의 데이터 입출력 스트림  (0) 2024.04.24
[오브젝트] 객체지향 프로그래밍  (0) 2023.07.26
문자열  (0) 2023.04.20