Backend

모던 자바 인 액션 - 람다 탄생 과정

연_우리 2022. 5. 6. 22:39
반응형

목차

     

     

    문제상황과 람다 탄생 과정

    재고 관리 애플리케이션을 만드려고한다.

    농부는 녹색사과를 찾는 기능만 있으면 된다고했다.

    하루 지나니 150그램 이상인 사과를 찾는 기능도 있어야한다고한다.

    또 하루 지나니 150그램 이상이면서 빨간 사과를 찾는 기능이 있어야한다고한다.

    사용자의 요구에 유연한 애플리케이션을 만드려면 어떻게 해야할까?

     

     

     

    1번째 시도 : 계속 변화하는 메서드

    요청1) 녹색사과 필터링 메서드 만들어주세요

    응답1) 네~

    public List<Apple> filterGreenApples(List<Apple> inventory){
        List<Apple> result = new ArrayList<>();
    
        for (Apple apple : inventory) {
            if(GREEN.equals(apple.getColor())){
                result.add(apple);
            }
        }
    
        return result;
    }

    요청2) 빨간사과 필터링 메서드 만들어주세요

    응답2) 네~ (녹색사과 필터링 메서드를 복사&붙여넣고 GREEN을 RED로 바꿔서 만들어야겠다!)

    public List<Apple> filterRedApples(List<Apple> inventory){
        List<Apple> result = new ArrayList<>();
    
        for (Apple apple : inventory) {
            if(RED.equals(apple.getColor())){
                result.add(apple);
            }
        }
    
        return result;
    }

    요청3) 옅은 녹색 사과 필터링 메서드 만들어주세요

    응답3) 음.. 색이 변할때마다 필터링 메서드를 만들수는 없는데... 가만보니 코드도 red와 green이 큰 차이가 없네..

     

     

     

    2번째 시도 : 속성 파라미터화

    아! 색을 파라미터화 해야겠당~~~ filterApplesByColor 생성

    사용자 요청 시 : filterApplesByColor(RED) or filterApplesByColor(GREEN)....
    ----------------------------------------------------------------------------------
    public List<Apple> filterApplesByColor(List<Apple> inventory, Color color){
        List<Apple> result = new ArrayList<>();
    
        for (Apple apple : inventory) {
            if(apple.getColor().equals(color)){
                result.add(apple);
            }
        }
    
        return result;
    }

    요청4) 아.. 쓰다보니 무게로도 필터 기능이 있어야할 것 같아요

    응답4) 아..ㄴ..ㅔ..... filterApplesByWeight 생성

    사용자 요청 시 : filterApplesByWeight(100) or filterApplesByWeight(200)....
    ----------------------------------------------------------------------------------
    public List<Apple> filterApplesByWeight(List<Apple> inventory, int weight){
        List<Apple> result = new ArrayList<>();
    
        for (Apple apple : inventory) {
            if(apple.getWeight() > weight){
                result.add(apple);
            }
        }
    
        return result;
    }

     

     

     

    3번째 시도 : 모든 속성 파라미터화

    현재는.. 사과의 수많은 속성 중 색깔, 무게 2가지로만 필터링을 했지만 나중에 원산지, 당도 등으로 필터링 하게된다면 어떡하지?

    아! 사과의 모든 속성을 필터링할 수 있게 하나의 메서드로 합치면되나?!

    사용자 요청 시 : filterApples(inventory, GREEN, 0, true), filterApples(inventory, null, 150, false)...
    ------------------------------------------------------------------------------------------------------
    public List<Apple> filterApples(List<Apple> inventory, Color color, int weight, boolean flag){
        List<Apple> result = new ArrayList<>();
    
        for (Apple apple : inventory) {
            if( (apple.getWeight() > weight)||(apple.getColor() > color) ){
                result.add(apple);
            }
        }
    
        return result;
    }

    "filterApples(inventory, GREEN, 0, true)", "filterApples(inventory, null, 150, false)"

    => 코드를 다른사람이 봤을 때 어떤 의미인지 한눈에 알아볼 수 있을까? 절대 아니요..

    절대 절대 엔티티의 속성을 파라미터로한 메서드를 만들지 말자!

     

     

     

     

     

    4번째 시도 : 인터페이스와 조건구현클래스

    사용자가 필요한 속성이 시시각각 변하는 상황에서, 개발자가 속성을 지정하는 것은 한계가 있다.

    차라리 조건이 동작하는 메서드를 인터페이스로 만들어 구현하면 어떨까?

    // true, false를 반환하는 함수를 predicate(프레디케이트)라고 한다.
    public interface ApplePredicate{
        boolean test (Apple apple);
    }
    -------------------------------------------------------------------------------
    //ApplePredicate를 구현한 AppleWeightPredicate
    public class AppleWeightPredicate implements ApplePredicate{
        @Override
        public boolean test(Apple apple) {
            return apple.getWeight() > 150;
        }
    }
    -------------------------------------------------------------------------------
    //ApplePredicate를 매개변수로 받는 필터 메서드
    public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate){	
        List<Apple> result = new ArrayList<>();
     
        for(Apple apple : inventory){
        	if( predicate.test(apple) ){   //predicate로 사과를 검사한다.
            	result.add(apple);
            }
        }
        return result;
    }
    -------------------------------------------------------------------------------
    //필터 메서드 사용
    filterApples(inventory, new AppleWeightPredicate());

    ApplePredicate 인터페이스를 구현한 AppleWeightPredicate를 필터에 적용시켜서 유연한 코드를 만들 수 있다.

    이제 어떤 조건이 와도 ApplePredicate를 구현한 클래스를 만들면된다!

     

    하지만.. 조건이 100가지이면 ApplePredicate를 구현한 100가지 클래스를 만들어야되겠네?

     

     

     

    5번째 시도 : 익명클래스

    사실상 "코드"를 전달하는 것인데 불필요하게 ApplePredicate의 구현클래스를 생성하는 것은 아닐까??

    구현클래스를 계속 생성하지말고, 익명클래스를 사용해보자.

    // true, false를 반환하는 함수를 predicate(프레디케이트)라고 한다.
    public interface ApplePredicate{
        boolean test (Apple apple);
    }
    -------------------------------------------------------------------------------
    //ApplePredicate를 구현한 AppleWeightPredicate 삭제
    -------------------------------------------------------------------------------
    //ApplePredicate를 매개변수로 받는 필터 메서드
    public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate){	
        List<Apple> result = new ArrayList<>();
     
        for(Apple apple : inventory){
        	if( predicate.test(apple) ){   //predicate로 사과를 검사한다.
            	result.add(apple);
            }
        }
        return result;
    }
    -------------------------------------------------------------------------------
    //필터 메서드 사용
    filterApples(inventory, new ApplePredicate() {
    	public boolean test(Apple apple) {		//기존 AppleWeightPredicate의 test를 구현
            return apple.getWeight() > 150;
        }
    });

    기존의 AppleWeightPredicate를 구현하지 않고, 익명클래스로 구현해서 동작시킬수도 있다.

    하지만 익명클래스는 코드 읽기가 불편하다는 단점을 가지고 있다.

     

     

     

     

    6번째 시도 : 람다 표현식 사용

    드디어 람다를 사용한다! 6가지의 시도 중 제일 깔끔하고 코드 읽기가 편해졌다.

    //ApplePredicate가 아닌 자바8에서 기본적으로 제공하는 Predicate를 사용한다.
    //인터페이스명만 다를뿐이다.
    public interface Predicate<T> {
        boolean test(T t);
    }
    -------------------------------------------------------------------------------
    //ApplePredicate를 구현한 AppleWeightPredicate 삭제
    -------------------------------------------------------------------------------
    //ApplePredicate를 매개변수로 받는 필터 메서드
    public static List<Apple> filterApples(List<Apple> inventory, Predicate predicate){	
        List<Apple> result = new ArrayList<>();
     
        for(Apple apple : inventory){
        	if( predicate.test(apple) ){   //predicate로 사과를 검사한다.
            	result.add(apple);
            }
        }
        return result;
    }
    -------------------------------------------------------------------------------
    //필터 메서드 사용
    filterApples(inventory, (Apple apple) -> apple.getWeight() > 150);

     

     

    6가지 코드 경우의 수를 보면서 람다가 왜 탄생했는지 알게되었다.

    메서드의 인자로 코드를 전달하는 기법의 강력함을 알게되었다.

     

     

    실전예제로 Comparator를 구현해보자

    public interface Comparator<T>{
    	int compare(T o1, T o2);
    }
    
    //익명클래스 이용
    inventory.sort(new Comparator<Apple>() {
    	public int compare(Apple a1, Apple a2) {
    		return a1.getWeight().compareTo(a2.getWeight());
    	}
    });
    
    //람다 이용
    inventory.sort(
    	(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
    );

    익명클래스와 람다를 비교해보자. 어떤 코드가 한눈에 들어올까? 당연히 람다다!

    반응형
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기