ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Part1] Java8 In action - Chapter3 - 2
    Java8 In Action 2022. 7. 27. 11:17
    반응형

    해당 내용은 Java8 In Action 책을 요약 및 정리한 내용입니다.

    좀 더 자세히 알고 싶으신 분들은 책을 사서 읽어보심을 추천드립니다.!

    3.5 형식 검사,형식추론,제약

    Entity

    AppleEntity.java

    package Part1.Chapter3.Chapter3_3_5.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_5;
    
    import java.security.PrivilegedAction;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.List;
    import java.util.concurrent.Callable;
    import java.util.function.BiFunction;
    import java.util.function.Predicate;
    import java.util.function.ToIntBiFunction;
    
    import Part1.Chapter3.Chapter3_3_5.entity.AppleEntity;
    
    /*
     * 3.5 형식 검사, 형식 추론, 제약
     * 
     * 람다 표현식 자체에는 람다가 어떤 함수형 인터페이스를 구현하는지의 정보가 포함되어 있지 않다.
     * 따라서 람다 표현식을 더 제대로 이해하려면 람다의 실제 형식을 파악해야 한다. 
     */
    public class Main_3_5 {
    
        public static List<AppleEntity> filter(List<AppleEntity> inventory, Predicate<AppleEntity> p) {
            List<AppleEntity> result = new ArrayList<>();
    
            for(AppleEntity apple : inventory) {
                if(p.test(apple)) {
                    result.add(apple);
                }            
            }
    
            return result;
        }
    
        public static void main(String[] args) throws Exception {
            List<AppleEntity> inventory = Arrays.asList(new AppleEntity("red", 100)
                    , new AppleEntity("green", 200));
    
            /*
             * 3.5.1 형식 검사
             * 
             * 람다가 사용되는 콘텍스트(context)를 이용해서 람다의 형식(type)을 추론할 수 있다.
             * 어떤 콘텍스트(예를 들면 람다가 전달될 메서드 파라미터나 람다가 할당되는 변수 등)
             * 에서 기대되는 람다 표현식의 형식을 "대상 형식(target type)"이라고 부른다.
             * 
             * 위 람다 표현식을 사용할 때 다음과 같은 순서로 형식 확인 과정이 진행된다.
             * 1. filter 메서드의 선언을 확인.
             * 2. filter 메서드는 두 번째 파라미터로 Predicate<AppleEntity> 형식(대상 형식)을 기대한다.
             * 3. Predicate<AppleEntity>은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스이다.
             * 4. test 메서드는 AppleEntity을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
             * 5. filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다.
             * 
             * 위 예제에서 람다 표현식은 AppleEntity을 인수로 받아 boolean을 반환하므로 유효한 코드이다.
             * 람다 표현식이 예외를 던질 수 있다면 추상 메서드도 같은 예외를 던질 수 있도록 throws로 선언해야 한다.
             * 
             * 람다 표현식의 형식 검사 과정의 재구성
             * filter(inventory, (AppleEntity a) -> a.getWeight() > 150)    
             *         1. 람다가 사용된 콘텍스트는 무엇인가? 우선 filter의 정의를 확인한다.    
             * filter(inventory, Predicate<AppleEntity> p)    
             *         2. 대상 형식은 Predicate<AppleEntity>이다.    
             * 대상 형식
             *         3. Predicate<AppleEntity>인터페이스의 추상 메서드는 무엇인가?    
             * boolean test(AppleEntity appleEntity)    
             *         4. AppleEntity을 인수로 받아 boolean을 반환하는 test 메서드다.    
             * AppleEntity -> boolean    
             *         5. 함수 디스크립터는 AppleEntity -> boolean이므로 람다의 시그너처와 일치한다.
             *         람다도 AppleEntity을 인수로 받아 boolean을 반환하므로 코드 형식 검사가 완료된다.
             */
            filter(inventory
                    , (AppleEntity a) -> a.getWeight() > 150)
                .stream()
                .forEach((AppleEntity a) -> System.out.println("3.5.1 형식 검사 : " 
                    + a.getColor() +" apple - " + a.getWeight() + " weight." ));
            /*
             * 3.5.2 같은 람다, 다른 함수형 인터페이스
             * 
             * 대상 형식이라는 특징 때문에 같은 람다 표현식이더라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다.
             * 예를 들어 Callable과 PrivilegedAction 인터페이스는 () -> T와 같은 메서드 시그너처를 가지고 있다.
             * 따라서 아래 할당문은 모두 유효한 코드이다.
             */
            Callable<Integer> c = () -> 42;
            System.out.println("Callable : " + c.call());
            PrivilegedAction<Integer> p = () -> 52;
            System.out.println("PrivilegedAction : " + p.run());
    
            /* 아래 코드와 같이 하나의 람다 표현식을 다양한 함수형 인터페이스에 사용할 수 있다.*/
            Comparator<AppleEntity> c1 = (AppleEntity a1, AppleEntity a2) -> a1.getWeight().compareTo(a2.getWeight());
            System.out.println("Comparator : " + c1.compare(new AppleEntity("green", 200), new AppleEntity("red", 100)));
    
            ToIntBiFunction<AppleEntity, AppleEntity> c2 = (AppleEntity a1, AppleEntity a2) -> a1.getWeight().compareTo(a2.getWeight());
            System.out.println("ToIntBiFunction : " + c2.applyAsInt(new AppleEntity("green", 100), new AppleEntity("red", 200)));
    
            BiFunction<AppleEntity, AppleEntity, Integer> c3 = (AppleEntity a1, AppleEntity a2) -> a1.getWeight().compareTo(a2.getWeight());
            System.out.println("BiFunction : " + c3.apply(new AppleEntity("green", 100), new AppleEntity("red", 200)));;
    
            /*
             * 다이아몬드 연산자
             * 주어진 클래스 인스턴스 표현식을 두 개 이상의 다양한 콘텍스트에 사용할 수 있다.
             * 이때 인스턴스 표현식의 형식 인수는 콘텍스트에 의해 추론된다.
             *         List<String> listOfStrings = new ArrayList<>();
             *         List<Integer> listOfIntegers = new ArrayList<>();
             * 
             * 특별한 void 호환 규칙
             * 람다의 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환된다.(물론 파라미터 리스트도 호환되어야 함.)
             * 예를 들어 다음 두 행의 예제에서 List의 add 메서드는 Consumer 콘텍스트(T -> void)가 기대하는 void 대신 boolean을 반환하지만
             * 유효한 코드이다.
             *         // Predicate는 불린 반환값을 갖는다.
             *         Predicate<String> p = s -> list.add(s);
             *         // Consumer는 void 반환값을 갖는다.
             *         Consumer<String> b = s -> list.add(s);
             */
    
            /*
             * 3.5.3 형식 추론
             * 
             * 자바 컴파일러는 람다 표현식이 사용된 콘텍스트(대상 형식)을 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.
             * 즉, 대상 형식을 이용해서 함수 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그너처도 추론할 수 있다.
             * 결과적으로 컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서는 이를 생략할 수 있다.
             */
            filter(inventory, (a) -> "green".equals(a.getColor()) )
                .stream()
                .forEach((a) -> System.out.println("3.5.2 형식 추론 : " 
                        + a.getColor() +" apple - " + a.getWeight() + " weight." ));
    
            /*
             * 여러 파라미터를 포함하는 람다 표현식에서는 코드 가독성 향상이 두드러진다. 
             * 다음은 Comparator 객체를 만드는 코드다.
             */
            // 형식 추론을 하지 않음.
            Comparator<AppleEntity> notTypeResaoning = (AppleEntity a1, AppleEntity a2) -> a1.getWeight().compareTo(a2.getWeight());
            System.out.println("notTypeResaoning : " + notTypeResaoning.compare(new AppleEntity("red", 300), new AppleEntity("blue", 400)));
    
            // 형식 추론 함.
            Comparator<AppleEntity> typeResaoning = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
            System.out.println("typeResaoning : " + typeResaoning.compare(new AppleEntity("red", 500), new AppleEntity("blue", 400)));
    
            /*
             * 3.5.4 지역 변수 사용
             * 
             * 람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수(free variable - 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를
             * 활용할 수 있다. 이와 같은 동작을 람다 캡쳐링(capturing lambda)이라고 한다.
             * 아래 예는 portNumber 변수를 캡처하는 람다이다.
             */
            int portNumber = 80;
            Runnable r = () -> System.out.println("capturing lambda : " + portNumber);
            r.run();
    
            /*
             * 하지만 자유 변수에도 약간의 제약이 있다. 인스턴스 변수와 정적 변수를 캡처(자신의 바디에서 참조할 수 있도록)할 수 있다.
             * 하지만 그러려면 지역 변수는 명시적으로 final로 선언되어 있어야 하거나 실질적으로 final로 선언된 변수와 똑같이 사용되어야 한다.
             * 즉, 람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡처할 수 있다.(참고 : 인스턴스 변수 캡처는 final 지역 변수 this를 캡처하는 것과 마찬가지이다.)
             */
            int errorPortNumber = 81;
            Runnable r1 = () -> System.out.println("errorPortNumber lambda : " + errorPortNumber);
            /* 아래 변수의 재할당 하는 부분을 주석 해제 하면 위 람다 표현식에서 에러가 발생한다. */
            //errorPortNumber = 82;
            r1.run();
    
            /*
             * 지역 변수의 제약
             * 지역 변수의 이런 제약은 인스턴스 변수와 지역 변수는 태생이 다른데 부터 발생한다.
             * 인스턴스 변수는 힙에 저장되는 반면 지역 변수는 스택에 위치한다. 람다에서 지역 변수에 바로 접근할 수 있다는
             * 가정 하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었음에도 람다를 실행하는
             * 스레드에서는 해당 변수에 접근하려 할 수 있다.
             * 따라서 원래 변수에 접근을 허용하는 것이 아니라 자유 변수의 복사본을 제공한다.
             * 따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴 것이다.
             * 
             * 또한 지역 변수의 제약 때문에 외부 변수를 변화시키는 일반적인 명령형 프로그래밍 패턴(병렬화를 방해는 요소)에 제동을 걸 수 있다.
             */
    
            /*
             * 클로저
             * 원칙적으로 클로저란 함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스를 가리킨다.
             * 예를 들어 클로저를 다른 함수의 인수로 전달할 수 있고 클로저는 클로저 외부에 정의된 변수의 값에 접근하고 값을 바꿀 수 있다.
             * 자바 8의 람다와 익명 클래스는 클로저와 비슷한 동작을 수행한다.
             * 람다와 익명 클래스 모두 메서드의 인수로 전달될 수 있으며 자신의 외부 영역의 변수에 접근할 수 있다.
             * 다만 람다와 익명 클래스는 람다가 정의된 메서드의 지역 변수 값은 바꿀 수 없다.
             * 람다가 정의된 메서드의 지역 변수값은 final 변수여야 한다. 덕분에 람다는 변수가 아닌 값에 국한되어 어떤 동작을 수행한다는 사실이
             * 명확해진다.
             * 이전에도 설명한 것 처럼 지역 변수값은 스택에 존재하므로 자신을 정의한 스레드와 생존을 같이 해야 하며 따라서 지역 변수는 final이어야 한다.
             * 가변 지역 변수를 새로운 스레드에서 캡처할 수 있다면 안전하지 않는 동작을 수행할 가능성이 생긴다.(인스턴스 변수는 스레드가 공유하는 힙에
             * 존재하므로 특별한 제약이 없다.)
             */
        }
    
    }

    3.6 메서드 레퍼런스

    Entity

    Fruit.java

    package Part1.Chapter3.Chapter3_3_6.entity;
    
    public class Fruit {
        protected String color;
        protected Integer weight;
    
        public Fruit() {}
    
        public Fruit(Integer weight) {
            this.weight = weight;
        }
    
        public Fruit(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;
        }
    }

    AppleEntity.java

    package Part1.Chapter3.Chapter3_3_6.entity;
    
    public class AppleEntity extends Fruit {
    
        public AppleEntity() {}
    
        public AppleEntity(Integer weight) {
            this.weight = weight;
        }
    
        public AppleEntity(String color, Integer weight) {
            this.color = color;
            this.weight = weight;
        }
    }

    OrangeEntity.java

    package Part1.Chapter3.Chapter3_3_6.entity;
    
    public class OrangeEntity extends Fruit {
        public OrangeEntity() {}
    
        public OrangeEntity(Integer weight) {
            this.weight = weight;
        }
    
        public OrangeEntity(String color, Integer weight) {
            this.color = color;
            this.weight = weight;
        }
    }

    Main

    package Part1.Chapter3.Chapter3_3_6;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.function.BiFunction;
    import java.util.function.Function;
    import java.util.function.Supplier;
    
    import Part1.Chapter3.Chapter3_3_6.entity.AppleEntity;
    import Part1.Chapter3.Chapter3_3_6.entity.Fruit;
    import Part1.Chapter3.Chapter3_3_6.entity.OrangeEntity;
    
    /*
     * 3.6 메서드 레퍼런스
     * 
     * 메서드 레퍼런스를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있다.
     * 때로는 람다 표현식보다 메서드 레퍼런스를 사용하는 것이 더 가독성이 좋으며 자연스러울수 있다.
     * 
     * 3.6.1 요약
     * 메서드 레퍼런스는 특정 메서드만을 호출하는 람다의 축약형이라고 생각할 수 있다.
     * 예를 들어 람다가 '이 메서드를 직접 호출해'라고 명령한다면 메서드를 어떻게 호출해야 하는지 설명을
     * 참조하기보다는 메서드명을 직접 참조하는 것이 편리하다.
     * 실제로 메서드 레퍼런스를 이용하면 기존에 구현된 메서드를 람다 표현식을 만들수 있다.
     * 이때 명시적으로 메서드명을 참조함으로써 가독성을 높일수 있다.
     * 메서드 레퍼런스는 메서드명 앞에 구분자(::)를 붙이는 방식으로 메서드 레퍼런스를 활용할 수 있다.
     *  
     * 람다와 메서드 레퍼런스 단축 표현 예제
     * 람다 : (AppleEntity a) -> a.getWeight()
     * 메서드 레퍼런스 단축 표현 : AppleEntity::getWeight
     * 
     * 람다 : () -> Thread.currentThread().dumpStack()
     * 메서드 레퍼런스 단축 표현 : Thread.currentThread()::dumpStack
     * 
     * 람다 : (str, i) -> str.substring(i)
     * 메서드 레퍼런스 단축 표현 : String::substring
     * 
     * 람다 : (String s) -> System.out.println(s)
     * 메서드 레퍼런스 단축 표현 : System.out::println
     * 
     * 메서드 레퍼런스를 새로운 기능이 아니라 하나의 메서드를 참조하는 람다를 편리하게 표현할 수 있는 문법으로 간주할 수 있다.
     */
    public class Main_3_6 {
    
        public static Fruit giveMeFruit(Map<String, Function<Integer, Fruit>> map, String fruit, Integer weight) {
            return map.get(fruit.toLowerCase()).apply(weight);        
        }
    
        public static List<AppleEntity> map(List<Integer> list , Function<Integer, AppleEntity> f) {
            List<AppleEntity> result = new ArrayList<>();
    
            for(Integer e : list) {
                result.add(f.apply(e));
            }
    
            return result;
        }
    
        public static void main(String[] args) {
            List<AppleEntity> inventory = Arrays.asList(new AppleEntity("green", 100)
                    , new AppleEntity("red", 200)
                    , new AppleEntity("blue", 300));
    
            /*
             * 람다 표현식으로 정렬.
             */
            inventory.sort((a1, a2) -> a2.getWeight().compareTo(a1.getWeight()));
            inventory.stream().forEach((appleEntity) -> System.out.println("[Lambda - Descending] color : " 
                    + appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
    
            /*
             * java.util.Comparator.comparing와 메서드 레퍼런스를 이용한 정렬.
             */
            inventory.sort(Comparator.comparing(AppleEntity::getWeight));
            inventory.stream().forEach((appleEntity) -> System.out.println("[Comparator.comparing - Ascending] color : " 
                    + appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
    
            /*
             * 정적 메서드 레퍼런스
             */
            Function<String, Integer> f = (str) -> Integer.parseInt(str);
            System.out.println("정적 메서드 람다 표현식 : " + f.apply("1234"));
    
            f = Integer::parseInt;
            System.out.println("정적 메서드 레퍼런스 표현식: " + f.apply("7894"));
    
            /*
             * 다양한 형식의 인스턴스 메서드 레퍼런스
             */
            f = (str) -> str.length();
            System.out.println("인스턴스 메서드 람다 표현식 : " + f.apply("kim sung"));
    
            f = String::length;
            System.out.println("인스턴스 메서드 레퍼런스 표현식 : " + f.apply("kim sung wook"));
    
            /*
             * 기존 객체의 인스턴스 메서드 레퍼런스
             */    
            List<AppleEntity> appleInventory = new ArrayList<AppleEntity>() {
                private static final long serialVersionUID = 1L;
                {
                    add(new AppleEntity("blue1", 100));
                    add(new AppleEntity("blue2", 200));
                    add(new AppleEntity("blue3", 300));
                }
            };
    
            Function<AppleEntity, Boolean> appleEntityFunction = appleInventory::add;
            appleEntityFunction.apply(new AppleEntity("white2", 500));
    
            appleInventory.stream().forEach((appleEntity) -> System.out.println("기존 객체의 인스턴스 메서드 레퍼런스 : color : " 
                    + appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
    
            /*
             * 3.6.2 생성자 레퍼런스
             * 
             * ClassName::new처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 레퍼런스를 만들수 있다.
             * 이것은 정적 메서드의 레퍼런스를 만드는 방법과 비슷하다.
             */
    
            Supplier<AppleEntity> c1 = AppleEntity::new;
            AppleEntity a1 = c1.get();
            System.out.println("3.6.2 생성자 레퍼런스 Supplier(메서드 레퍼런스) : " + a1);
    
            /*
             * 위 예제는 아래 코드와 같다. 
             */
            c1 = () -> new AppleEntity();
            a1 = c1.get();
            System.out.println("3.6.2 생성자 레퍼런스 Supplier(람다 표현식) : " + a1);
    
            /*
             * AppleEntity(Integer weight)라는 시그너처를 갖는 생성자는 Function 인터페이스의 시그너처와 같다.
             * 따라서 다음과 같이 구현이 가능하다.
             */
            Function<Integer, AppleEntity> c2 = AppleEntity::new;
            AppleEntity a2 = c2.apply(110);
            System.out.println("3.6.2 생성자 레퍼런스 Function(메서드 레퍼런스) : " + a2);
    
            c2 = (Integer weight) -> new AppleEntity(weight);
            a2 = c2.apply(220);
            System.out.println("3.6.2 생성자 레퍼런스 Function(람다 표현식) : " + a2);
    
            /*
             * 다음 코드에서 Integer를 포함하는 리스트의 각 요소를 map 메서드를 이용해서 AppleEntity 생성자로 전달한다.
             * 결과적으로 다양한 무게를 포함하는 사과 리스트가 만들어진다.
             */
            List<Integer> weights = Arrays.asList(7, 3, 4, 10);
            List<AppleEntity> apples = map(weights, AppleEntity::new);
    
            apples.stream().forEach((AppleEntity a) -> System.out.println("3.6.2 생성자 레퍼런스 map 메서드 : weight = " 
                    + a.getWeight() ));
    
            /*
             * AppleEntity(String, Integer)처럼 두 인수를 갖는 생성자는 BiFunction인터페이스와 같은 시그너처를 가지므로
             * 다음처럼 할 수 있다.
             */
            BiFunction<String, Integer, AppleEntity> c3 = AppleEntity::new;
            AppleEntity a3 = c3.apply("red", 100);
            System.out.println("3.6.2 생성자 레퍼런스 BiFunction(메서드 레퍼런스) : " + a3);
    
            /*
             * 인스턴스화하지 않고도 생성자에 접근할 수 있는 기능을 다양한 상황에 응용할 수 있다.
             * 예를 들어 Map으로 생성자와 문자열값을 관련시킬 수 있다.
             * 그리고 String과 Integer가 주어졌을 때 다양한 무게를 갖는 여러 종류의 과일을 만드는 메서드를
             * 만들수 있다.
             */
            Map<String, Function<Integer, Fruit>> map = new HashMap<String, Function<Integer,Fruit>>(){
                private static final long serialVersionUID = 1L;
    
                {
                    put("apple", AppleEntity::new);
                    put("orange", OrangeEntity::new);
                }
            };
    
            System.out.println("giveMeFruit 메서드(AppleEntity 객체) : " + giveMeFruit(map, "apple", 100).getWeight());
            System.out.println("giveMeFruit 메서드(OrangeEntity 객체) : " + giveMeFruit(map, "orange", 200).getWeight());
    
        }
    
    }

    3.7 람다,메서드 레퍼런스 활용하기

    Entity

    AppleEntity.java

    package Part1.Chapter3.Chapter3_3_7.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;
        }
    }

    Implements

    AppleEntityComparator.java

    package Part1.Chapter3.Chapter3_3_7.impl;
    
    import java.util.Comparator;
    
    import Part1.Chapter3.Chapter3_3_7.entity.AppleEntity;
    
    public class AppleEntityComparator implements Comparator<AppleEntity>{
    
        @Override
        public int compare(AppleEntity o1, AppleEntity o2) {
            return o1.getWeight().compareTo(o2.getWeight());
        }
    
    }

    Main

    package Part1.Chapter3.Chapter3_3_7;
    
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.List;
    
    import Part1.Chapter3.Chapter3_3_7.entity.AppleEntity;
    import Part1.Chapter3.Chapter3_3_7.impl.AppleEntityComparator;
    
    /*
     * 3.7 람다, 메서드, 레퍼런스 활용하기!
     */
    public class Main_3_7 {
    
        public static void main(String[] args) {
            List<AppleEntity> inventory = Arrays.asList(new AppleEntity("red", 300)
                    , new AppleEntity("green", 120)
                    , new AppleEntity("blue", 100));
    
            /*
             * 3.7.1 1단계 : 코드 전달
             * 자바 8의 List API에서 제공하는 sort메서드에 정렬 전략을 전달. 
             */
            inventory.sort(new AppleEntityComparator());
            inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.1 코드 전달 : 색깔 = " 
                    + a.getColor() + ", 무게 = " + a.getWeight() ));
    
            /*
             * 3.7.2 2단계 : 익명 클래스 사용
             */
            inventory.sort(new Comparator<AppleEntity>() {
                @Override
                public int compare(AppleEntity o1, AppleEntity o2) {            
                    return o2.getWeight().compareTo(o1.getWeight());
                }
            });
            inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.2 익명 클래스 사용 : 색깔 = " 
                    + a.getColor() + ", 무게 = " + a.getWeight() ));
    
            /*
             * 3.7.3 3단계 : 람다 표현식 사용
             * 
             * 람다 표현식으로 경량화된 문법을 이용해서 코드를 전달.
             * 함수형 인터페이스를 기대하는 곳 어디에서나 람다 표현식을 이용할 수 있다.
             * 함수형 인터페이스란 오직 하나의 추상 메서드를 정의하는 인터페이스이다.
             * Comparator의 함수 디스크립터는 (T, T) -> int다.
             * 우리 같은 경우에는(AppleEntity, AppleEntity) -> int로 표현할 수 있다. 
             */
            /*
             *  람다 표현식이 사용된 콘텍스트를 활용해서 람다의 파라미터 형식을 추론할 수 있다.
             */
            inventory.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight() ));
            inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.3 람다 표현식 사용 : 색깔 = " 
                    + a.getColor() + ", 무게 = " + a.getWeight() ));
    
            inventory.sort(Comparator.comparing((AppleEntity a) -> a.getWeight() ));
            inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.3 람다 표현식 사용(Comparator.comparing) : 색깔 = " 
                    + a.getColor() + ", 무게 = " + a.getWeight() ));
    
            /*
             * 3.7.4 4단계 : 메서드 레퍼런스 사용 
             */
            inventory.sort(Comparator.comparing(AppleEntity::getWeight));
            inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.4 메서드 레퍼런스 사용 : 색깔 = " 
                    + a.getColor() + ", 무게 = " + a.getWeight() ));
        }
    
    }

    3.8 람다 표현식을 조합할 수 있는 유용한 메서드

    Entity

    AppleEntity.java

    package Part1.Chapter3.Chapter3_3_8.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;
        }
    }

    Process

    Letter.java

    package Part1.Chapter3.Chapter3_3_8.letter;
    
    public class Letter {
        public static String addHeader(String text) {
            return "From Raoul, Mario and Alan : " + text;
        }
    
        public static String addFooter(String text) {
            return text + " Kind regards";
        }
    
        public static String checkSpelling(String text) {
            return text.replaceAll("labda", "lambda");
        }
    }

    Main

    package Part1.Chapter3.Chapter3_3_8;
    
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.List;
    import java.util.function.Function;
    import java.util.function.Predicate;
    
    import Part1.Chapter3.Chapter3_3_8.entity.AppleEntity;
    import Part1.Chapter3.Chapter3_3_8.letter.Letter;
    
    /*
     * 3.8 람다 표현식을 조합할 수 있는 유용한 메서드
     * 
     * 자바 8 API의 몇몇 함수형 인터페이스는 다양한 유틸리티 메서드를 포함한다.
     * 간단한 여러 개의 람다 표현식을 조합해서 복잡한 람다 표현식을 만들 수 있다.
     * 예를 들어 두 프레디케이트를 조합해서 두 프레디케이트의 or 연산을 수행하는 커다란 프레디케이트를 만들수 있다.
     * 또한 한 함수의 결과가 다른 함수의 입력이 되도록 두 함수를 조합할 수도 있다.
     * 이와 같이 가능한 이유는 함수형 인터페이스의 디폴트 메서드(default method) 때문이다.
     * (디폴트 메서드는 추상 메서드가 아니므로 함수형 인터페이스 정의에 어긋나지 않는다.) 
     */
    public class Main_3_8 {
    
        public static void main(String[] args) {
            List<AppleEntity> inventory = Arrays.asList(new AppleEntity("green", 140)
                    , new AppleEntity("red", 200)
                    , new AppleEntity("green", 200)
                    , new AppleEntity("blue", 300)
                    , new AppleEntity("red", 100));
    
            /*
             * 3.8.1 Comparator 조합
             * 
             * 정적 메서드 Comparator.comparing을 이용해서 비교에 사용할 키를 추출하는 Function 기반의 Comparator를
             * 반환할 수 있다. 
             *         Comparator<AppleEntity> c = Comparator.comparing(AppleEntity::getWeight);
             */
            /* 
             * 역정렬
             * 사과의 무게를 내림차순으로 정렬하고 싶다면? 다른 Comparator 인스턴스를 만들 필요가 없다.
             * 인터페이스 자체에 주어진 비교자의 순서를 뒤바꾸는 reverse라는 디폴트 메서드를 제공하기 떄문이다.
             * 아래 코드와 같이 처음 비교자 구현을 그대로 재사용해서 사과의 무게를 기준으로 역정렬할 수 있다.
             */
            inventory.sort(Comparator.comparing(AppleEntity::getWeight).reversed());
            inventory.stream().forEach((appleEntity) -> System.out.println("3.8.1 Comparator 조합(reversed 메서드 사용) color : " 
                    + appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
    
            /*
             * Comparator 연결
             * 무게가 같다면 색깔로 사과를 정렬할 수 있다.
             * thenComparing 메서드로 두 번재 비교자를 만들수 있다.
             * thenComparing은 함수를 인수로 받아 첫 번째 비교자를 이용해서 두 객체가 같다고 판단되면
             * 두 번째 비교자에 객체를 전달한다.
             */
            inventory.sort(Comparator.comparing(AppleEntity::getWeight).reversed()
                .thenComparing(Comparator.comparing(AppleEntity::getColor)) );
            inventory.stream().forEach((appleEntity) -> System.out.println("3.8.1 Comparator 조합(thenComparing 메서드 사용) color : " 
                    + appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
    
            /*
             * 3.8.2 Predicate 조합
             * 
             * Predicate 인터페이스는 복잡한 프레디케이트를 만들수 있도록 negate, and, or 세 가지 메서드를 제공한다. 
             */
            inventory.stream().forEach((AppleEntity a) -> {
                Predicate<AppleEntity> redApple = (AppleEntity a1) -> "red".equals(a1.getColor());
                Predicate<AppleEntity> gram150Up = (AppleEntity a1) -> a1.getWeight() > 150;
    
                /*
                 * 예를 들어 "빨간색이 아닌 사과"처럼 특정 프레디케이트를 반전시킬 때 negate메서드를 사용할 수 있다.
                 */
                Predicate<AppleEntity> notRedApple = redApple.negate();
    
                if(notRedApple.test(a)) {
                    System.out.println("3.8.2 Predicate 조합(빨간색이 아닌 사과 - negate) color :" + a.getColor() + ", weight : " + a.getWeight());
                }
    
                /*
                 * and 메서드를 이용해서 "빨간색이 아니면서 무거운 사과"를 선택하도록 두 람다를 조합할 수 있다.
                 */
                Predicate<AppleEntity> notRedAndHeavyApple = notRedApple.and(gram150Up);
    
                if(notRedAndHeavyApple.test(a)) {
                    System.out.println("3.8.2 Predicate 조합(빨간색이 아니면서 무거운 사과 - and) color :" + a.getColor() + ", weight : " + a.getWeight());                
                }            
    
                /*
                 * or을 이용해서 "빨간색이면서 무거운(150그램이상) 사과 또는 그냥 녹색 사과"인 경우의 조합
                 * 왼쪽에서 오른쪽으로 연결된다.
                 */
                Predicate<AppleEntity> redAndHeavyAppleOrGreen = redApple
                        .and(gram150Up)
                        .or((AppleEntity a1) -> "green".equals(a1.getColor()));
    
                if(redAndHeavyAppleOrGreen.test(a)) {
                    System.out.println("3.8.2 Predicate 조합(빨간색이면서 무거운 사과 또는 녹색 사과 - or) color :" + a.getColor() + ", weight : " + a.getWeight());                
                }            
            } );
    
            /*
             * 3.8.3 Function 조합
             * 
             * Function 인터페이스는 Function 인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메서드를 제공한다.
             * addThen 메서드는 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환한다.
             * 예를 들어 숫자를 증가(x -> x + 1)시키는 f라는 함수가 있고, 숫자에 2를 곱하는 g라는 함수가 있다고 가정하면
             * f와 g를 조립해서 숫자를 증가시킨 뒤 결과에 2를 곱하는 h라는 함수를 만들 수 있다.
             */
            Function<Integer, Integer> f = (x) -> x + 1;
            Function<Integer, Integer> g = (x) -> x * 2;
            // 수학적으로 write g(f(x)) 또는 (g o f)(x)라고 표현.        
            System.out.println("3.8.3 Function 조합(andThen 메서드): " + f.andThen(g).apply(1));
    
            /*
             * compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공한다.
             * 즉, f.andThen(g)에서 andThen 대신에 compose를 사용하면 g(f(x))가 아니라 f(g(x))라는 수식이 된다.
             */
            // 수학적으로 write f(g(x)) 또는 (f o g)(x)라고 표현.
            System.out.println("3.8.3 Function 조합(andThen 메서드): " + f.compose(g).apply(1));
    
            /*
             * 아래 코드는 문자열로 구성된 편지 내용을 변환하는 다양한 유틸 메서드이다.
             * 여러 유틸 메서드를 조합해서 다양한 변환 파이프라인을 만들 수 있다.
             */
            Function<String, String> addHeader = Letter::addHeader;
    
            System.out.println("3.8.3 Function 조합 : " + addHeader
                    .andThen(Letter::checkSpelling)
                    .andThen(Letter::addFooter)
                    .apply("kim sungw wook - labda") );
    
            /*
             * 철자 검사는 빼고 헤더와 푸터만 추가하는 파이프라인도 만들 수 있다.
             */
            System.out.println("3.8.3 Function 조합 : " + addHeader
                .andThen(Letter::addFooter)
                .apply("kim sungw wook - labda") );
        }
    
    }

    람다 표현식

    • 동작 파라미터화를 이용해서 변화하는 요구사항에 효과적으로 대응하는 코드를 구현할 수 있음을 확인 했다.
      • 또한 정의한 코드 블록을 다른 메서드로 전달할 수 있고,
      • 정의한 코드 블록을 특정 이벤트(예를 들면 마우스 클릭)가 발생할 때 실행 되도록 설정 하거나
      • 알고리즘의 일부(150그램 이상의 사과와 같은 프레디게이트)로 실행되도록 설정할 수 있다. 따라서 동작 파라미터화를 이용하면 더 유연하고 재사용할 수 있는 코드를 만들 수 있다.
    • 익명 클래스로 다양한 동작을 구현할 수 있지만 만족할 만큼 코드가 깔끔하지 않다. 깔끔하지 않는 코드는 동작 파라미터를 실전에 적용하는 것을 막는 요소다.
    • 람다 표현식은 익명 클래스처럼 이름이 없는 함수면서 메서드를 인수로 전달할 수 있으므로 일단은 람다 표현식이 익명 클래스와 비슷한 것이라고 생각하자.

    요약

    • 람다 표현식은 익명 함수의 일종이다. 이름은 없지만, 파라미터 리스트, 바디, 반환 형식을 가지며 예외를 던질 수 있다.

    • 람다 표현식은 간결한 코드를 구현할 수 있다.

    • 함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스다.

    • 함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용할 수 있다.

    • 람다 표현식을 이용해서 함수형 인터페이스의 추상 메서드를 즉석으로 제공할 수 있으며 "람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다."

    • java.util.function 패키지는

      • Predicate
      • Function<T, R>
      • Supplier
      • Consumer
      • BinaryOperator

      등을 포함해서 자주 사용하는 다양한 함수형 인터페이스를 제공한다.

    • 자바 8은 Predicate와 Function<T, R>같은 제네릭 함수형 인터페이스와 관련한 박싱 동작을피할 수 있도록 IntPredicate, IntToLongFunction 등과 같은 기본형 특화 인터페이스도 제공한다.

    • 실행 어라운드 패턴(예를 들면 자원 할당, 자원 정리 등 코드 중간에 실행해야 하는 메서드에 꼭 필요한 코드)을 람다와 활용하면 유연성과 재사용성을 추가로 얻을 수 있다.

    • 람다 표현식의 기대 형식을 "대상 형식"이라고 한다.

    • 메서드 레퍼런스를 이용하면 기존의 메서드 구현을 재사용하고 직접 전달할 수 있다.

    • Comparator, Predicate, Function 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공한다.

    반응형

    댓글

Designed by Tistory.