함수형 인터페이스
자바에는 Runnable, Comparator 등 코드 블록을 캡슐화하는 수많은 기존 인터페이스가 있다.
람다는 이러한 기존 인터페이스와 호환된다.
단일 추상메서드(single abstract method)를 갖춘 인터페이스의 객체를 기대할 때 람다 표현식을 사용할 수 있다.
그리고 이러한 인터페이스를 함수형인터페이스(functional interface)라고 한다.
함수형 인터페이스가 단일 추상 메서드를 포함해야하는 이유는?
인터페이스에 있는 모든 메서드는 추상 메서드이다. 실제로 언제나 인터페이스에서 toString이나 clone 같은 Object 클래스의 메서드를 재선언할 수 있었고, 이러한 재선언은 해당 메서드를 추상메서드로 만들지 않는다.
자바8에서는 인터페이스에 비추상 메서드를 정의할 수 있다는 점이다.
함수형 인터페이스로 변환을 설명하기 위해 Arrays.sort 메서드를 보자.
이 메서드의 두번째 파라미터는 단일 메서드를 갖춘 인터페이스인 Comparator의 인스턴스를 요구한다.
Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length())); |
내부적으로 Arrays.sort 메서드는 Comparator<String>을 구현하는 어떤 클래스의 객체를 받는다.
전달받은 객체의 compare 메서드를 호출하면 람다 표현식의 몸체를 실행한다.
이러한 객체와 클래스의 관리는 순전히 구현체의 몫이며, 전통적으로 사용해온 이너 클래스(내부 클래스)inner class방식보다 훨씬 효율적일 수 있다. 람다 표현식은 객체가 아니라 함수로 생각하고, 함수형 인터페이스에 전달할 수 있다고 인식하는 것이 좋다.
이렇게 인터페이스로 변환되는 점이 람다 표현식을 강력하게 만들어주는 요인이다.
문법은 짧고 단순하다
button.setOnAction(event -> System.out.println("Thanks for clicking!")); |
이너 클래스를 사용하는 방식보다 훨씬 읽기 쉽다.
사실, 함수형 인터페이스로 변환이 자바에서 람다 표현식을 이용해 할 수 있는 유일한 일이다.
함수 리터럴(function literal)을 지원하는 다른 프로그래밍 언어에서는 (String, String) -> int 같은 함수 타입을 선언하고, 이 타입의 변수를 선언해서 해당 변수를 함수 표현식(function expression)을 저장하는데 사용할수 있다.
하지만 자바 설계자들은 언어에 함수 타입을 추가하는 대신 인터페이스라는 친숙한 개념을 고수하기로 결정했다.
Object 타입 변수에도 람다 표현식을 대입할 수 없다. Object는 함수형 인터페이스가 아니기 때문이다.
자바 API는 java.util.function 패키지에 다수의 아주 범용적인 함수형 인터페이스를 정의하고 있다.
이러한 인터페이스중 하나인 BiFunction<T, U, R>은 파라미터 타입이 T와 U고, 리턴타입이 R인 함수를 나타낸다.
문자열 비교 람다를 BiFunction 타입 변수에 저장할 수 있다.
BiFunction<String, String, Interger> comp = (first, second) -> Integer.compare(first.length(), second.length()); |
하지만 이 변수는 정렬에 도움이 되지 않는다. BiFunction을 요구하는 Arrays.sort 메서드는 없기 때문이다.
이전에 함수형 프로그래밍 언어를 사용한 경험이 있다면 이 부분을 이상하다고 생각할 것이다.
하지만 자바 프로그래머에게는 아주 자연스러운 결과다.
Comparator 같은 인터페이스는 단순히 주어진 파라미터와 린턴 타입이 있는 메서드에 그치지 않고 특정한 목적이 있다.
자바8은 이러한 특징을 유지한다.
람다 표현식을 이용해 무언가 하고자 할 때, 여전히 해당 표현식의 목적을 염두에 두고 이를 위한 특정 함수형 인터페이스를 갖추게 된다.
자바8의 여러 API에서 java.util.function 패키지에 있는 인터페이스를 사용하므로 앞으로 다른곳에서도 접하게 될 것이다.
하지만 현재 사용하고 있는 어떤 API에 속한 함수형 인터페이스로도 람다 표현식을 동일하게 잘 변환할 수 있다는 점을 명심하기 바란다.
함수형 인터페이스에 @FunctionalInterface 애노테이션을 붙일수 있다.
이렇게 하면 두 가지 장점이 있다.
1) 컴파일러에서 애노테이션이 붙은 엔티티가 단일 추상메서드를 갖춘 인터페이스인지 검사한다.
2) javadoc 패키지에서 해당 인터페이스가 함수형 인터페이스임을 알리는 문장을 포함한다.
애노테이션을 반드시 사용해야 하는 것은 아니다. 정의에 따르면 단일 추상 메서드를 갖춘 모든 인터페이스가 곧 함수형 인터페이스이다.
그럼에도 @Functionalinterface 애노테이션을 사용하는 것은 좋은 생각이다.
람다표현식이 함수형 인터페이스의 인스턴스로 변환될때 검사예외 checked exception가 문제가 된다는 점을 유의하기 바란다.
람다 표현식의 몸체에서 검사 예외를 던질수 있는 경우, 해당 예외가 대상 인터페이스의 추상 메서드에 선언되어 있어야 한다.
예) 오류
Runnable sleeper = () -> { System.out.println("Zzz"); Thread.sleep(1000); };
Runnable.run 메서드는 예외를 던질 수 없기 때문에, 위의 대입은 잘못된것이다.
이 오류를 바로잡는데는 두가지 선택이 있다.
먼저 람다 표현식의 몸체에서 예외를 잡을 수 있다.
예)
Runnable sleeper = () -> {System.out.println("Zzz1"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }}; |
그렇지 않으면 해당 예외를 던질 수 있는 단일 추상메서드를 갖춘 인터페이스에 람다를 대입한다.
Callable 인터페이스의 call 메서드는 어떤 예외든 던질 수 있다.
따라서 (return null 문장을 추가한다면) 위의 람다 표현식을 Callable<Void>에 대입할 수 있다.
예)
Callable<Void> caller = () -> {System.out.println("Zzz2"); return null; }; |
'JAVA > JAVA 8' 카테고리의 다른 글
자바 8 #스트림(Stream) 관련 메서드 (0) | 2018.02.26 |
---|---|
자바 8 #스트림(Stream) (0) | 2018.02.23 |
자바8 #인터페이스 디폴트 메서드와 정적메서드 (0) | 2018.02.23 |
자바8 람다 #레퍼런스 (0) | 2018.02.22 |
자바8 람다 (0) | 2018.02.18 |