해당 내용은 Java8 In Action 책을 요약 및 정리한 내용입니다.
좀 더 자세히 알고 싶으신 분들은 책을 사서 읽어보심을 추천드립니다.!
3.1 람다란 무엇인가
Entity
AppleEntity.java
package Part1.Chapter3.Chapter3_3_1.entity;
public class AppleEntity {
private String color;
private Integer weight;
public AppleEntity() {}
public AppleEntity(String color, Integer weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
}
Main
package Part1.Chapter3.Chapter3_3_1;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import Part1.Chapter3.Chapter3_3_1.entity.AppleEntity;
/*
* 3.1 람다란 무엇인가?
*
* 람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다.
* 람다 표현식은 이름은 없지만
* - 파라미터 리스트
* - 바디
* - 반환 형식
* - 발생할 수 있는 예외 리스트
* 는 가질 수 있다.
*
* 람다의 특징으로
* 익명
* 보통의 메서드와 달리 이름이 없으므로 익명이라 표현한다.
* 함수
* 람다는 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부른다.
* 하지만 메서드처럼 파라미터 리스트, 바디, 반환 형식, 가능한 예외 리스트를 포함한다.
* 전달
* 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다.
* 간결성
* 익명 클래스처럼 많은 코드를 구현할 필요가 없다.
*
* 람다(lambda)라는 용어는 람다 미적분학 학계에서 개발한 시스템에서 유래했다.
* 람다 표현식이 중요한 이유는 코드를 전달하는 과정에서 자질구레한 코드가 많이 생긴다.
* 그러나 람다를 이용하면 간결한 방식으로 코드를 전달할 수 있다.
*/
public class Main_3_1 {
public static void appleComparator(List<AppleEntity> inventory) {
/*
* 익명 클래스를 이용한 정렬.
*/
Comparator<AppleEntity> byWeight = new Comparator<AppleEntity>() {
@Override
public int compare(AppleEntity o1, AppleEntity o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
};
inventory.sort(byWeight);
inventory.stream()
.forEach((AppleEntity apple) -> System.out.println("[Anonymous Class - Ascending] color : " + apple.getColor()
+ ", weight : " + apple.getWeight() ));
/*
* 람다 표현식으로 이용한 정렬.
*
* 람다는 세 부분으로 이루어진다.
* (AppleEntity o1, AppleEntity o2) : 람다 파라미터
* -> : 화살표
* o2.getWeight().compareTo(o1.getWeight()) : 람다 바디
* - 파라미터 리스트
* Comparator의 compare 메서드의 파라미터
* - 화살표
* 화살표(->)는 람다의 파라미터 리스트와 바디를 구분한다.
* - 람다의 바디
* 두 사과의 무게를 비교한다. 람다의 반환값에 해당하는 표현식이다.
*
* 아래는 자바 8에서 지원하는 다섯 가지 람다 표현식 예제다.
* - (String s) -> s.length()
* String 형식의 파라미터 하나를 가지며 int를 반환한다.
* 람다 표현식에서는 return이 함축되어 있으므로 return 문을
* 명시적으로 사용하지 않아도 된다.
*
* - (AppleEntity apple) -> apple.getWeight() > 150
* AppleEntity 형식의 파라미터 하나를 가지며
* boolean(사과가 150그램보다 무거운지 결정)을 반환한다.
*
* - (int x, int y) -> {
* System.out.println("Result : ");
* System.out.println(x + y);
* }
* int 형식의 파라미터를 두 개 가지며 리턴값이 없다(정확히 void 리턴)
* 이 예제에서 볼 수 있듯이 람다 표현식은 여러 행의 문장을 포함할 수 있다.
*
* - () -> 42
* 파라미터가 없으며 int를 반환한다.
*
* - (AppleEntity o1, AppleEntity o2) -> o2.getWeight().compareTo(o1.getWeight())
* AppleEntity 형식의 파라미터를 두 개 가지며 int(두 사과의 무게 비교 결과)를 반환한다.
*
* 자바 설계자는 이미 C#이나 스칼라 같은 비슷한 기능을 가진 다른 언어와 비슷한 문법을
* 자바에 적용하기로 했다. 아래는 람다의 기본 문법이다.
* (parameters) -> expression
* 또는 다음처럼 표현할 수 있다.(중괄호 이용)
* (parameters) -> { expression }
*
* 아래는 람다 예제와 사용 사례 리스트를 보여준다.
* - 불린 표현식 : (List<String> list) -> list.isEmpty()
* - 객체 생성 : () -> new AppleEntity()
* - 객체에서 소비 :
* (AppleEntity apple) -> {
* System.out.println(apple.getWeight());
* }
* - 객채에서 선택/추출 : (String s) -> s.length();
* - 두 값을 조합 : (int a, int b) -> a * b
* - 두 객체 비교 :
* (AppleEntity o1, AppleEntity o2) -> o2.getWeight().compareTo(o1.getWeight())
*/
byWeight = (AppleEntity o1, AppleEntity o2) -> o2.getWeight().compareTo(o1.getWeight());
inventory.sort(byWeight);
inventory.stream()
.forEach((AppleEntity apple) -> System.out.println("[Lambda - Descending] color : " + apple.getColor()
+ ", weight : " + apple.getWeight() ));
}
public static void main(String[] args) {
List<AppleEntity> inventory = Arrays.asList(new AppleEntity("green", 100)
, new AppleEntity("red", 120)
, new AppleEntity("green", 150));
appleComparator(inventory);
}
}
3.2 어디에,어떻게 람다를 사용할까
Main
package Part1.Chapter3.Chapter3_3_2;
/*
* 3.2 어디에, 어떻게 람다를 사용할까?
*
* 함수형 인터페이스라는 문맥에서 람다 표현식을 사용할 수 있다.
*
*
* 3.2.1 함수형 인터페이스
* 앞 전에서 만든 Predicate<T>인터페이스로 필터 메서드를 파라미터화할 수 있음을 기억하는가?(못해도 한다고 하자.)
* 바로 Predicate<T>가 함수형 인터페이스이다. Predicate<T>는 오직 하나의 추상 메서드만 지정하기 때문이다.
*
* public interface Predicate<t> {
* boolean test(T t);
* }
*
* 간단히 말해 함수형 인터페이스는 "정확히 하나의 추상 메서드를 지정하는 인터페이스" 다.
* 지금까지 살펴본 자바 API의 함수형 인터페이스로 Comprator, Runnable 등이 있다.
* (아래에 나오는 인터페이스는 자바 API의 함수형 인터페이스다.)
*
* java.util.Comparator
* public interface Comparator<T> {
* int compare(T o1, T o2);
* }
*
* java.lang.Runnable
* public interface Runnable {
* void run();
* }
*
* java.awt.event.ActionListener
* public interface ActionListener exends EventLstener {
* void actionPerformed(ActionEvent e);
* }
*
* java.util.concureent.Callable
* public interface Callable<V> {
* V call();
* }
*
* java.security.PrivilegedAction
* public interface PrivilegedAction<V> {
* T run();
* }
*
* 함수형 인터페이스로 뭘 할 수 있을까?(많은걸 할수 있으니깐 만들어겠지..)
* 람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로
* 전체 표현식을 함수형 인터페이스의 인스턴스로 취급
* (기술적으로 따지면 함수형 인터페이스를 concrete 구현한 클래스의 인스턴스)
* 할 수 있다.
*
* Note
* 인터페이스는 디폴트 메서드(인터페이스의 메서드를 구현하지 않은 클래스를
* 고려해서 기본 구현을 제공하는 바디를 포함하는 메서드)를 포함할 수 있다.
* 여기서 많은 디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스다.
*/
public class Main_3_2 {
public static void process(Runnable r) {
r.run();
}
/*
* 3.2.1 함수형 인터페이스 예제
*
* 아래 예제의 코드들은 Runnable이 오직 하나의 추상 메서드 run을
* 정의하는 함수형 인터페이스이므로 올바른 코드다.
*/
static void runnable() {
// 람다 사용.
Runnable r1 = () -> System.out.println("hello hell 1?");
process(r1);
// 익명 클래스 사용.
process(new Runnable() {
@Override
public void run() {
System.out.println("hello hell 2?");
}
});
// 직접 전달된 람다 표현식.
process(() -> System.out.println("hello hell 3?"));
}
/*
* 3.2.2 함수 디스크립터
*
* 함수형 인터페이스의 추상 메서드 시그너처(signature)는 람다 표현식의 시그너처를 가리킨다.
* 람다 표현식의 시그너처를 서술하는 메서드를 함수 디스크립터(function descriptor)라고 한다.
* 예를 들어
* Runnable에 유일한 추상 메서드인 run메서드는 인수와 반환값이 없으므로
* Runnable 인터페이스는 인수와 반환값이 없는 시그너처로 생각할 수 있다.
*
* 람다 표현식의 유효성을 확인하는 방법은 일단 람다 표현식은 변수에 할당하거나 함수형 인터페이스를
* 인수로 받는 메서드로 전달할 수 있으며, 함수형 인터페이스의 추상 메서드와 같은 시그너처를 갖는다는
* 사실을 기억하자.
*
* 함수형 인터페이스에 추상 메서드로 전달 되는 람다 표현식은 해당 메서드의 반환값 혹은 타입, 인수 타입 혹은 갯수에
* 맞춰서 표현식을 전달 해야 한다.
* Runnable의 run 메서드는 반환값은 void이고 인수는 없다.
* 위 예제 코드에서
* process(() -> System.out.println("hello hell 3?"));
* 코드를 람다 표현식으로 직접 전달 하였다.
* 여기서 직접 전달된 람다 표현식을 분석 하자면 () 인수가 없고 메서드 바디 영역에서는 println메서드만 호출하고
* 반환값이 없다. 즉, run 함수 시그너처와 같은 인수가 없고 반환값이 없는 형태와 동일하므로 올바른 람다 표현식이다.
*/
public static void main(String[] args) {
runnable();
}
/*
* @FunctionalInterface는 무엇인가?
* 새로운 자바 API를 살펴보면 함수형 인터페이스에 @FunctionalInterface라는 어노테이션이 추가 되었다.
* @FunctionalInterface는 함수형 인터페이스임을 가리키는 어노테이션이다.
* @FunctionalInterface로 인터페이스를 선언했지만 실제로 함수형 인터페이스가 아니면 컴파일러가 에러를 발생시킨다.
*/
}
3.3 람다 활용-실행 어라운드 패턴
Interface
BufferedReaderProcessor.java
package Part1.Chapter3.Chapter3_3_3.buffer.inter;
import java.io.BufferedReader;
import java.io.IOException;
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
File
Main_3_3.txt
이거슨 첫 번째 라인 입니다.
이거슨 두 번재 라인 입니다.
Main
package Part1.Chapter3.Chapter3_3_3;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.function.Function;
import Part1.Chapter3.Chapter3_3_3.buffer.inter.BufferedReaderProcessor;
/*
* 3.3 람다 활용 : 실행 어라운드 패턴
*
* 자원 처리(예를 들면 데이터베이스의 파일 처리)에 사용하는 순환 패턴(recurrent pattern)은
* 자원을 열고,
* 처리한 다음에,
* 자원을 닫는
* 순서로 이루어진다. 설정(setup)과 정리(cleanup)과정은 대부분 비슷하다.
* 즉, 실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸는 형태를 갖는다.
*
* 초기화/준비 코드
* 작업 A
* 정리/마무리 코드
*
* 혹은
*
* 초기화/준비 코드
* 작업 B
* 정리/마무리 코드
*
* 와 같은 형식의 코드를 실행 어라운드 패턴(execute around pattern)이라고 한다.
*/
public class Main_3_3 {
public static FileReader getExmpFile() throws FileNotFoundException, UnsupportedEncodingException {
return new FileReader(URLDecoder.decode(Main_3_3.class.getResource("").getPath() + "Main_3_3.txt", "UTF-8"));
}
/*
* 3.3.1 1단계 : 동작 파라미터화를 기억하라.
*
* 아래 코드는 한 줄만 읽을 수 있다. 만약 한 번에 두 줄을 읽거나 가장 자주 사용되는 단어를 반환하려면 어떻게 해야 할까?
* 기존의 설정, 정리 과정은 재사용하고 badProcessFile 메서드만 다른 동작을 수행하도록 명령할 수 있다면 좋을 것이다.
* 이를 위해 badProcessFile의 동작을 파라미터화 하는 것이다.
*/
public static String badProcessFile() throws IOException {
/*
* 아래의 try 문법을 보면 다른 try랑 특이한데 해당 문법은 try-with-resources문법으로
* 자바 7에 추가된 새로운 기능이다. 이를 사용하면 자원을 명시적으로 닫을 필요가 없다.
* (즉, close 같은 메서드를 호출하지 않아도 된다.)
*/
try(BufferedReader br = new BufferedReader(getExmpFile()) ) {
return br.readLine();
}
}
/*
* 3.3.2 2단계 : 함수형 인터페이스를 이용해서 동작 전달.
*
* 함수형 인터페이스 자리에 람다를 사용할 수 있다. 따라서 BufferedReader -> String과 IOException을 던질(throw)수 있는
* 시그너처와 일치하는 함수형 인터페이스를 만들어야 한다.
* (BufferedReaderProcessor라는 함수형 인터페이스를 만들어서 인수로 받음.)
*/
public static String functionInterfaceProcessFile(BufferedReaderProcessor b) throws IOException {
try(BufferedReader br = new BufferedReader(getExmpFile()) ) {
return b.process(br);
}
}
public static void main(String[] args) throws IOException {
System.out.println("badProcessFile : " + badProcessFile());
/*
* 3.3.3 3단계 : 동작 실행!
*
* 이제 BufferReaderProcessor에 정의된 process 메서드의 시그너처(BufferedReader -> String)와
* 일치하는 람다를 전달할 수 있다.
* 람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으며 전달된 코드는
* "함수형 인터페이스의 인스턴스로 전달된 코드와 같은 방식으로 처리"
* 한다. 따라서 functionInterfaceProcessFile 바디 내에서 BufferedReaderProcessor 객체의 process를
* 호출할 수 있다.
*/
// 람다 표현식
System.out.println(functionInterfaceProcessFile((BufferedReader br)
-> "functionInterfaceProcessFile : " + br.readLine() + br.readLine()) );
// 익명 클래스
System.out.println(functionInterfaceProcessFile(new BufferedReaderProcessor() {
@Override
public String process(BufferedReader b) throws IOException {
return "익명 클래스 :" + b.readLine() + b.readLine();
}
}) );
/*
* 예외, 람다, 함수형 인터페이스의 관계
*
* 함수형 인터페이스는 확인된 예외를 던지는 동작을 허용하지 않는다. 즉, 예외를 던지는 람다 표현식을 만들려면
* 확인된 예외를 선언하는 함수형 인터페이스를 직접 정의하거나 람다를 try / catch 블록으로 감싸야 한다.
*
* 본 코드에서 사용 된 BufferedReaderProcessor에서 process 메서드 시그너처에 명시적으로 IOException을 선언.
*
* 그러나 우리는 BufferedReaderProcessor는 Function<T, R> 형식의 함수형 인터페이스를 기대하는 API를 사용하고 있으며
* 직접 함수형 인터페이스를 만들기 어려운 상황이다. 이런 상황에서는 아래 코드와 같이 명시적으로 확인된 예외를 잡을 수 있다.
*/
Function<BufferedReader, String> f = (BufferedReader br) -> {
try {
/*
* 강제로 Exception을 확인하고 싶다면 아래 return을 주석처리하고
* throw new IOException(); 부분을 주석을 해제 한다.
*/
// throw new IOException();
return br.readLine();
} catch (IOException e) {
System.out.println("!!!!! 강제로 Exception이 발생 하였습니다. !!!!!");
throw new RuntimeException(e);
}
};
System.out.println("try-catch Function : " + f.apply(new BufferedReader(getExmpFile()) ));
}
}
3.4 함수형 인터페이스 사용
Main
package Part1.Chapter3.Chapter3_3_4;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
/*
* 3.4 함수형 인터페이스 사용.
*
* 함수형 인터페이스는 오직 하나의 추상 메서드를 지정한다.
* 함수형 인터페이스의 추상 메서드는 람다 표현식의 시그너처를 묘사하고 추상 메서드 시그너처를 함수 디스크립터(function descriptor)라고 한다.
* 다양한 람다 표현식을 사용하려면 공통의 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요하다.
*/
public class Main_3_4 {
/*
* 3.4.1 Predicate
*
* java.util.function.Predicate<T> 인터페이스는 test라는 추상 메서드를 정의하며
* test는 제네릭 형식 T의 객체를 인수로 받아 boolean을 반환한다.
*/
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for(T s : list) {
if(p.test(s)) {
result.add(s);
}
}
return result;
}
/*
* 3.4.2 Consumer
*
* java.util.function.Consumer<T> 인터페이스는 제네릭 형식 T 객체를 받아서, void를 반환하는 accept라는 추상 메서드를 정의한다.
* T 형식의 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 Consumer 인터페이스를 사용할 수 있다.
*/
public static <T> void forEach(List<T> list, Consumer<T> c) {
for(T s : list) {
c.accept(s);
}
}
/*
* 3.4.3 Function
*
* java.util.function.Function<T, R>인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R 객체를 반환하는 Apple라는 추상 메서드를 정의한다.
* 입력을 출력으로 매핑하는 람다를 정의할 때 Function 인터페이스를 활용할 수 있다.
*
* 아래 예제는 String 리스트를 인수로 받아 각 String의 길이를 포함하는 Intger 리스트로 반환하는 예 이다.
*/
public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
List<R> result = new ArrayList<>();
for(T s : list) {
result.add(f.apply(s));
}
return result;
}
public static void main(String[] args) {
/*
* 3.4.1 Predicate
* Predicate의 test 메서드 시그너쳐는 인수로 제너릭 형식을 받고 반환값을 boolean으로 반환한다.
* 해당 시그너처와 동일하게
* (String s) - 인수는 제너릭 형식이기 때문에 어떤 형태로 인수를 전달하여도 상관없다.
* !s.isEmpty - boolean으로 반환값을 하였기 때문에 test 메서드 시그너처와 동일하다.
* 하였기 때문에 람다 표현식으로 올바른 표현식이다.
*/
Predicate<String> nonEmptyStrPredicate = (String s) -> !s.isEmpty();
System.out.println("Predicate : " + filter(Arrays.asList("kim", "", "sung", "wook", "") , nonEmptyStrPredicate));
/*
* 3.4.2 Consumer
*/
Consumer<String> nameStrConsumer = (String s) -> System.out.println("Consumer : " + s);
forEach(Arrays.asList("kim", "sung", "wook"), nameStrConsumer);
/*
* 3.4.3 Function
*/
Function<String, Integer> strLenFunction = (String s) -> s.length();
System.out.println("Function : "
+ map(Arrays.asList("kim", "sung", "wook", "kim sung", "kim sung wook"), strLenFunction));
/*
* 기본형 특화
*
* 자바의 모든 형식은 참조형(reference type - Byte, Integer, Object, List 등) 아니면 기본형(primitive type - int, double, byte, char 등)
* 에 해당한다. 하지만 제네릭 파라미터는 제네릭의 내부 구현 때문에 참조형만 사용할 수 있다.
* 자바에서는 기본형을 참조형으로 변환할 수 있는 기능을 제공한다. 이 기능을 박싱(boxing)이라고 한다.
* 반대로 참조형을 기본형으로 변환하는 반대 동작을 언박싱(unboxing)이라고 한다.
* 그리고 프로그래머가 편리하게 코드를 구현할 수 있도록 박싱과 언방식으로 자동으로 해주는 오토박싱(autoboxing)이라는 기능도 제공한다.
*/
/*
* int가 Integer로 박싱이 되는 예제이다.
* 하지만 이런 변환 과정은 비용이 소모된다. 박싱한 값은 기본형을 감싸는 래퍼며 힙에 저장된다.
* 따라서 박싱한 값은 메모리를 더 소비하며 기본형을 가져올 때도 메모리를 탐색하는 과정이 필요하다.
*/
List<Integer> intList = new ArrayList<>();
for(int i = 0; i < 5; i++) {
intList.add(i);
}
intList.stream()
.forEach((Integer i) -> System.out.println("int -> Integer Boxing : " + i));
/*
* 자바 8에서는 기본형을 입출력으로 사용하는 상황에서 오토박싱 동작을 피할 수 있도록
* 특별한 함수형 인터페이스를 제공한다.
*
* 아래 예제는 IntPredicate는 1000이라는 값을 박싱하지 않지만, Predicate<Integer> 중 인수가 1000인 값은
* Integer 객체로 박싱한다.
*/
IntPredicate evenNumbers = (int i) -> i % 2 == 0;
System.out.println("IntPredicate : " + evenNumbers.test(1000));
Predicate<Integer> oddNumbers = (Integer i) -> i % 2 == 1;
System.out.println("boxing - Predicate<Integer> : " + oddNumbers.test(1000));
System.out.println("not boxing - Predicate<Integer> : " + oddNumbers.test(new Integer(1001)));
/*
* 일반적으로 특정 형식을 입력으로 받는 함수형 인터페이스의 이름 앞에서는 DoublePredicate, IntConsumer,
* LongBinaryOperator, IntFunction처럼 형식명이 붙는다.
* Function 인터페이스는 ToIntFunction<T>, IntToDoubleFunction 등의 다양한 출력 형식 파라미터를 제공한다.
*
* 자바 8의 대표적인 함수형 인터페이스
* 함수형 인터페이스 : Predicate<T>
* 함수 디스크립터 : T -> boolean
* 기본형 특화 :
* IntPredicate, LongPredicate, DoublePredicate
*
* 함수형 인터페이스 : Consumer<T>
* 함수 디스크립터 : T -> void
* 기본형 특화 :
* IntConsumer, LongConsumer, DoubleConsumer
*
* 함수형 인터페이스 : Function<T, R>
* 함수 디스크립터 : T -> R
* 기본형 특화 :
* IntFunction<R>, IntToDoubleFunction, IntToLongFunction,
* LongFunction<R>, LongToDoubleFunction, LongToIntFunction,
* DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>,
* ToLongFunction<T>
*
* 함수형 인터페이스 : Supplier<T>
* 함수 디스크립터 : () -> T
* 기본형 특화 :
* BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
*
* 함수형 인터페이스 : UnaryOperator<T>
* 함수 디스크립터 : T -> T
* 기본형 특화 :
* IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
*
* 함수형 인터페이스 : BinaryOperator<T>
* 함수 디스크립터 : (T, T) -> T
* 기본형 특화 :
* IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
*
* 함수형 인터페이스 : BiPredicate<L, R>
* 함수 디스크립터 : (L, R) -> boolean
* 기본형 특화 :
*
* 함수형 인터페이스 : BiConsumer<T, U>
* 함수 디스크립터 : (T, U) -> void
* 기본형 특화 :
* ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T>
*
* 함수형 인터페이스 : BiFunction<T, U, R>
* 함수 디스크립터 : (T, U) -> R
* 기본형 특화 :
* ToIntBiFunction<T, U>, ToLongBiFunction<T, U>, ToDoubleBiFunction<T, U>
*
* 사용 사례, 람다 예제, 사용할 수 있는 함수형 인터페이스 등을 총체적으로 제공한다.
* 람다와 함수형 인터페이스 예제
* 사용 사례
* 불린 표현
* 람다 예제 : (List<String> list) -> list.isEmpty()
* 대응하는 함수형 인터페이스 : Predicate<List<String>>
* 객체 생성
* 람다 예제 : () -> new AppleEntity(10)
* 대응하는 함수형 인터페이스 : Supplier<AppleEntity>
* 객체에서 소비
* 람다 예제 : (AppleEntity appleEntity) -> System.out.println(a.getWeight())
* 대응하는 함수형 인터페이스 : Consumer<AppleEntity>
* 객체에서 선택/추출
* 람다 예제 : (String s) -> s.length()
* 대응하는 함수형 인터페이스 : Function<String, Integer> 또는 ToIntFunction<String>
* 두 값 조합
* 람다 예제 : (int a, int b) -> a * b
* 대응하는 함수형 인터페이스 : IntBinaryOperator
* 두 객체 비교
* 람다 예제 : (AppleEntity a1, AppleEntity a2) -> a1.getWeight().compareTo(a2.getWeight())
* 대응하는 함수형 인터페이스 : Comparator<AppleEntity> 또는
* BiFunction<AppleEntity, AppleEntity, Integer> 또는
* ToIntBiFunction<AppleEntity, AppleEntity>
*/
}
}
람다 표현식
- 동작 파라미터화를 이용해서 변화하는 요구사항에 효과적으로 대응하는 코드를 구현할 수 있음을 확인 했다.
- 또한 정의한 코드 블록을 다른 메서드로 전달할 수 있고,
- 정의한 코드 블록을 특정 이벤트(예를 들면 마우스 클릭)가 발생할 때 실행 되도록 설정 하거나
- 알고리즘의 일부(150그램 이상의 사과와 같은 프레디게이트)로 실행되도록 설정할 수 있다. 따라서 동작 파라미터화를 이용하면 더 유연하고 재사용할 수 있는 코드를 만들 수 있다.
- 익명 클래스로 다양한 동작을 구현할 수 있지만 만족할 만큼 코드가 깔끔하지 않다. 깔끔하지 않는 코드는 동작 파라미터를 실전에 적용하는 것을 막는 요소다.
- 람다 표현식은 익명 클래스처럼 이름이 없는 함수면서 메서드를 인수로 전달할 수 있으므로 일단은 람다 표현식이 익명 클래스와 비슷한 것이라고 생각하자.
요약
람다 표현식은 익명 함수의 일종이다. 이름은 없지만, 파라미터 리스트, 바디, 반환 형식을 가지며 예외를 던질 수 있다.
람다 표현식은 간결한 코드를 구현할 수 있다.
함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스다.
함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용할 수 있다.
람다 표현식을 이용해서 함수형 인터페이스의 추상 메서드를 즉석으로 제공할 수 있으며 "람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다."
java.util.function 패키지는
- Predicate
- Function<T, R>
- Supplier
- Consumer
- BinaryOperator
등을 포함해서 자주 사용하는 다양한 함수형 인터페이스를 제공한다.
자바 8은 Predicate와 Function<T, R>같은 제네릭 함수형 인터페이스와 관련한 박싱 동작을피할 수 있도록 IntPredicate, IntToLongFunction 등과 같은 기본형 특화 인터페이스도 제공한다.
실행 어라운드 패턴(예를 들면 자원 할당, 자원 정리 등 코드 중간에 실행해야 하는 메서드에 꼭 필요한 코드)을 람다와 활용하면 유연성과 재사용성을 추가로 얻을 수 있다.
람다 표현식의 기대 형식을 "대상 형식"이라고 한다.
메서드 레퍼런스를 이용하면 기존의 메서드 구현을 재사용하고 직접 전달할 수 있다.
Comparator, Predicate, Function 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공한다.