Java8 In Action

[Part1] Java8 In action - Chapter3 - 1

신나게개발썰 2022. 7. 27. 11:13
반응형

해당 내용은 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 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공한다.

반응형