자바에 람다 표현식이 추가됨으로써 함수형 인터페이스, 메서드 참조가 등장
이를 바탕으로 스트림 API 탄생 및 컬렉션 기반의 기술을 편리하고 빠르게 처리할 수 있게 됨
람다 표현식은 자바 혁신의 핵심
다음과 같은 요소를 정확하게 이해할 필요가 있음
람다 표현식
람다 표현식 주요 문법
함수형 인터페이스
메서드 참조
람다 표현식이 필요한 이유
자바 기반의 프로그램이 비대해지면서 인터페이스 기반으로 개발을 많이하는데 결과적으로 많은 클래스 파일이 생성됨
이에 대응하기 위해 간단한 코드라면 중첩 클래스, 익명 클래스를 사용
하지만 많은 익명 클래스는 중복코드를 발생 → 코드가 비대해짐 → 가독성 저하
람다 표현식은 익명 클래스를 태체하는데 유용
public class BaseballPalyer implements Comparable < BaseballPlayer > {
private String playerName;
//...
@ Override
public int compareTo (BaseballPalyer baseballPlayer ) {
return playerName. compareTo (baseballPlayer. getPlayerName ());
}
//...
}
위 코드는 Comparable을 구현하여 비교하는 메서드를 재정의하는 방법
아래 코드는 외부에서 주입하는 방법
public static void main ( String [] args) {
List< BaseballPlayer > list = new ArrayList<>();
list. sort ( new Comparator< BaseballPlayer >() {
@ Override
public int compare (BaseballPlayer object1 , BaseballPlayer object2 ) {
return object1. getPlayerName (). compoareTo (object2. getPlayerName ());
}
});
}
자바 자체의 언어적인 제약으로 인해 익명클래스는 중복코드를 많이 발생시킴
메서드의 파라미터로 기본 데이터 타입과 객체형만 가능하기 때문
행동 자체를 전달할 수 있는 방법의 필요함으로 인해 람다 표현식을 메서드 인수로 전달할 수 있도록 지원됨
sort 예제를 람다 표현식으로 변경하면 다음과 같음
list. sort (
(BasepballPlayer object1, BasepballPlayer object2)
-> object. getPlayerName (). compareTo (object2. getPlayerName ())
)
축약된 형태로 변환되는것을 알 수 있음
람다 표현식은 다음과 같은 장점이 존재
이름 없는 함수를 선언가능
소스코드 분량을 획기적으로 줄일 수 있음
코드를 파라미터로 전달 가능
람다 표현식 이해하기
람다 표현식은 익명 클래스를 단순화하여 그 표현식을 메서드의 인수로 전달하거나 인터페이스의 객체를 생성할 수 있는 기능을 제공
람다 표현식의 특징
이름이 없음
종속되지 않음
값의 특징을 갖음
간단함
새로운 것이 아님
함수형 인터페이스 기본
한가지의 의문, 인터페이스는 통상적으로 여러개의 메서드를 포함하고 있는데 람다 표현식은 이름이 없고 단지 파라미터와 리턴타입만으로 식별하는데 어떻게 자바 컴파일러가 이를 인식하고 인터페이스의 구현체로 컴파일 할 수 있을까?
람파 표현식은 오직 public 메서드 하나만 가지고 있는 인터페이스
자바8 에서는 이러한 인터페이스를 함수형 인터페이스라고 부름
함수형 인터페이스에서 제공하는 단 하나의 추상 메서드를 함수형 메서드라 부름
자바8 이전 버전에서 만든 인터페이스도 람다 표현식으로 활용 가능
하지만 함수형 인터페이스 여부를 @FunctionalInterface 어노테이션이 선언을 통해 실수로 함수형 인터페이스에 메서드를 추가하거나 하는 등의 사전 문제를 예방
새로운 함수형 인터페이스를 추가할때는 @FunctionalInterface를 추가하는 것이 좋음
대표 함수형 인터페이스
Consumer
Function
Predicate
Supplier
Consumer 인터페이스
Consumer 인터페이스는 요청받는 내용을 소비
소비는 아무 리턴없이 요청을 처리한다는 것
public class ConsumerExample {
public static void executeConsumer (List< String > nameList , Consumer< String > consumer ) {
for (String name : nameList) {
consumer. accept (name);
}
}
public static void main ( String [] args ) {
List< String > nameList = new ArrayList<>();
// ...
ConsumerExample. executeConsumer (nameList, (String name) -> System.out. println (name));
}
}
람다 표현식은 그 자체로 실행되는 것이 아니라 함수형 인터페이스에 포함되어 있는 함수형 메서드의 내부 코드를 정의하는 역할
다음의 코드도 가능
public Consumer < String > getExpression () {
return (String name) -> System.out. println (name);
}
ConsumerExample. executeConsumer (nameList, getExpression ());
Function 인터페이스
2개의 제네릭 타입(인수 타입, 리턴 타입)을 정의
public class FunctionExample {
public static int executeFunction (String context , Function< String , Integer > function ) {
return function. apply (context);
}
public static void main ( String [] args ) {
FunctionExample. executeFunction ( "Hello! Welcome to Java World." , (String context) -> context. length ());
}
}
Predicate 인터페이스
public class PredicateExample {
public static boolean isValid (String name , Predicate< String > predicate ) {
return predicate. test (name);
}
public static void main ( String [] args ) {
PredicateExample. isValid ( "" , (String name) -> ! name. isEmpty ());
}
}
Supplier 인터페이스
입력없이 출력만 존재
파라미터 없이 리턴 타입만 있는 메서드는 주로 지정된 정보를 확인하거나 조회할 때 유용
public class SupplierExample {
public static String executeSupplier (Supplier< String > supplier ) {
return supplier. get ();
}
public static void main ( String [] args ) {
String version = "java upgrade book, version 0.1 BETA" ;
SupplierExample. executeSupplier (() -> { return version;});
}
}
책 저자는 네 가지 함수형 인터페이스를 잘 활용할 것을 권함
거의 표준적 용어로 사용되고 있기 때문
책 저자는 함수형 인터페이스를 이용해서 개발할 때 다음과 같은 세가지 기준을 적용
Function, Supplier, Consumer, Predicate를 표준 함수형 인터페이스로 설계에 반영
명시적 함수형 인터페이스의 기능을 정의하고 싶으면 자바에서 제공하는 함수형 인터페이스보다는 프로젝트에 맞게 함수형 인터페이스를 직접 만들어 사용
특별한 경우를 제외하고는 기본 데이터 타입을 지원하는 함수형 인터페이스는 가급적 사용하지 않음
처리해야하는 데이터가 숫자이거나 성능에 매우 민감하다면 기본 데이터 타입을 위한 함수형 인터페이스를 사용
함수형 인터페이스 응용
메서드 참조
함수를 메서드의 파라미터로 전달하는 것을 메서드 참조(method reference)라고 함
메서드 참조의 장점은 람다 표현식과 달리 코드를 여러 곳에서 재사용할 수 있고 자바의 기본 제공 메서드 뿐만아니라 직접 개발한 메서드도 사용 가능하다는 것
메서드 참조로 람다표현식을 한번더 축약 가능하고 가독성을 높일 수 있음
(String name) -> System.out. println (name) // 람다 표현식
System.out :: println // 메서드 참조
// 사용
list. stream (). forEach (System.out :: println);
메서드 참조의 구분
정적 메서드 참조
static 메서드에 대한 참조
Integer::parseInt
비한정적 메서드 참조
static 메서드 참조와 유사하게 보이지만 특정한 객체를 참조하기 위한 변수를 지정하지 않음
String::toUpperCase
toUpperCase는 String의 public이며 static이 아닌 메서드
한정적 메서드 참조
참조 메서드가 특정 객체의 변수로 제한
Calandar.getInstance()::getTime
생성자 참조
람다 표현식 조합
함수형 인터페이스를 통해 람다 표현식을 조합 가능
Consumer 조합
public static void main ( String [] args) {
Consumer< String > consumer =
(String text) -> System.out. println ( "Hello : " + text);
Consumer< String > consumerAndThen =
(String text) -> System.out. println ( "Text Length is : " + text. length ());
consumer. andThen (consumerAndThen). accept ( "Java" );
/*
실행결과
Hello : Java
Text Length is 4
*/
}
andThen을 사용해 두 개의 인터페이스를 조합
accept 사용해 조합된 메서드를 호출
여기서 조합의 의미는 첫 번째 람다 표현식의 결과를 두번째 람다 표현식의 입력으로 활용하는 것이 아닌 accept 메서드에 전달하는 파라미터를 각각 사용
Predicate 조합
Predicate 인터페이스는 참/거짓을 리턴하는 함수형 인터페이스
테스트 성공/실패, 데이터 검증 결과 여부 등을 판단하는데 사용
Function 조합
public static void main ( String [] args) {
Function< String , Integer > parseIntFunction =
(String str) -> Integer. parseInt (str) + 1 ;
Function< Integer , String > intToStrFunction =
(Integer str) -> "String : " + Integer. toString (i);
System.out. println (parseIntFunction. apply ( "1000" ));
System.out. println (intToStrFunction. apply ( 1000 ));
// Function 조합
System.out. println (parseIntFunction. andThen (intToStrFunction). apply ( 1000 ));
/*
실행결과
1001
String: 1000
String: 1001
*/
}
실행결과를 보면 알 수 있듯이 apply로 전달되는 파라미터가 첫번째 람다표현식부터 순차적으로 적용되며 실행
Consumer는 각각 실행되지만 Function은 순차적으로 실행
References