lambda (람다, 표현식, 함수형 인터페이스, default 메소드, 메소드 레퍼런스)

2021. 3. 21. 02:09 JAVA/Java

참조문서

1. 람다란?

람다란 코드블록이다. 기존에는 코드블록은 반드시 메서드 내에 존재해야 했다. 코드블록을 가지려면 메소드, 클래스를 선언해야 했다. 하지만 자바8부터는 람다를 통해 코드블록만 가질 수 있도록 한 것이다. (js function의 1급객체같은 느낌?) 또한 이것은 코드블록을 변수처럼 사용가능하게 한다.

2. 표현식

(int a, int b) -> {return a + b} // 매개변수 -> 함수 로직 (+@ 리턴)

  • 단순한 람다 구문의 경우, 람다 구분에 중괄호가 없을 수도 있다.
  • return 이 없을 수도 있다.
  • 매개변수에는 타입을 명시하지 않아도 된다.
  • 람다식 문법을 컴파일러가 익명 클래스로 변환한다. 즉, 함수형 인터페이스를 컴파일러가 구현하도록 위임하는 형태라 볼 수 있다

표현식 예제

() -> {}                     // No parameters; result is void
() -> 42                     // No parameters, expression body
() -> null                   // No parameters, expression body
() -> { return 42; }         // No parameters, block body with return
() -> { System.gc(); }       // No parameters, void block body
() -> {
    if (true) { return 12; }
    else { return 11; }
}                          // Complex block body with returns
(int x) -> x+1             // Single declared-type parameter
(int x) -> { return x+1; } // Single declared-type parameter
(x) -> x+1                 // Single inferred-type parameter
x -> x+1                   // Parens optional for single inferred-type case
(String s) -> s.length()   // Single declared-type parameter
(Thread t) -> { t.start(); } // Single declared-type parameter
s -> s.length()              // Single inferred-type parameter
t -> { t.start(); }          // Single inferred-type parameter
(int x, int y) -> x+y      // Multiple declared-type parameters
(x,y) -> x+y               // Multiple inferred-type parameters
(final int x) -> x+1       // Modified declared-type parameter
(x, final y) -> x+y        // Illegal: can't modify inferred-type parameters
(x, int y) -> x+y          // Illegal: can't mix inferred and declared types

3. 함수형 인터페이스 @FunctionalInterface

단 하나의 추상메소드를 갖고있는 인터페이스를 함수형 인터페이스라고 부를 수 있다. @FunctionalInterface 를 붙이면 컴파일러에게 명시적으로 함수형 인터페이스임을 알려주고, 해당 인터페이스가 함수형 인터페이스 명세를 어기면 컴파일러 에러를 발생시킨다. 람다는 이 인터페이스를 통해 타입추정이 가능해진다.

4. 함수형 인터페이스 API

주로 util.function 패키지에 있다. @FunctionalInterface 를 통해 사용자 정의 함수형 인터페이스를 만들어서 사용할 수도 있지만, java8 api에서 기본적으로 지원해준다.

인터페이스명 메소드명 설명
Runnable void run() 실행할 수 있는 인터페이스
Supplier T get() 제공할 수 있는 인터페이스
Consumer void accept(T t) 소비할 수 있는 인터페이스
Function<T, R> R apply (T t) 입력을 받아서 출력할 수 있는 인터페이스
Predicate Boolean test(T t) 입력을 받아 참, 거짓을 판단할 수 있는 인터페이스
UnaryOperator T apply(T t) 단항 연산할 수 있는 인터페이스

5. 인터페이스 내 default 메소드, static 메소드

자바 8이전에 인터페이스는 정적 상수와 추상메소드만을 가질 수 있었다. 하지만 함수형인터페이스 등이 도입되면서 큰 변화가 있었는데, 이젠 메소드 body까지 가진 default 메소드와 정적메소드를 넣을 수 있는 것이다. 이 메소드들은 오버라이딩할 필요가 없이, implements 해주면 그냥 사용할 수 있다.

이것은 스트림 도입, 컬렉션 API를 수정하면서 forEach 등 많은 메소드들이 인터페이스에 추가되었는데, 하위 자바버전(7이하)에서 깨지는 것을 방지하기 위해 도입된 것이다.

default 메소드 선언

public default 반환형 메소드명(파라미터) {
    // 비즈니스 로직
}

static 메소드 선언

public static 반환형 메소드명(파라미터) {
    // 비즈니스 로직
}

다중상속에서 충돌나면?

두 인터페이스를 상속받고, 각 인터페이스는 default 메소드명가 같다. 이럴 경우 충돌이 발생한다. 이럴 때는 해당 메소드를 오버라이드하거나, 하나의 인터페이스 메소드를 선택해서 super해야만 한다. (ex. return 하나의 인터페이스명.super.충돌난 메소드명();))

6. 메소드 직접 참조

예를들어 userList를 println으로 모두 찍어낸다고 하면, userList.forEach(user -> System.out.println(user)); 가 될 것이다. 이 코드의 콜스택을 보면 ArrayList.forEach -> Consumer.accept -> System.out.println 이다. 그런데 Consumer.accept는 사실 필요가 없으며, 콜스택만 깊어지게 한다.

이럴 때 사용하는 것이 메소드 레퍼런스이다. 적용하면 userList.forEach(System.out::println); 가 된다.

6-1. 메소드 레퍼런스의 유형

  1. 인스턴스::인스턴스메소드 : 인스턴스 메소드의 인자가 된다.
    • number -> System.out.println(number)
    • System.out.println(number)
  2. 클래스::정적메소드 : 정적메소드의 인자가 된다.
    • number -> Math.sqrt(number)
    • Math::sqrt
  3. 클래스::인스턴스메소드 : 첫번째 인자는 인스턴스가 되고, 두번째 인자부터는 첫번째 인자 인스턴스의 메소드의 파라미터로 넘어간다.
    • (a, b) -> a.compareTo(b)
    • Integer::compareTo

6-2. 생성자 레퍼런스

이와 비슷하게 생성자 레퍼런스도 있다. 클래스::new 이다. 이것은 함수형 인터페이스 구현 객체를 생성해준다.

// 에러 발생
MyObj myObj = MyObj::new; 

// 가능
Supplier<MyObj> myObjFactory = MyObj::new; 
MyObj myObj1 = myObjFactory.get();



출처: https://sjh836.tistory.com/160?category=679845 [빨간색코딩]