동작 파라미터와 람다 기초

박진영·2023년 4월 29일
0

모던자바

목록 보기
1/2

데이터 예시

다음과 같이 공의 종류를 나타내는 enum 이 있다.

public enum Category {
    농구공,축구공,배구공
}

그리고 특정 공의 가격과 종류를 대표하는 Ball 클래스가 있다.

Ball.class
public class Ball {
  Integer cost;
  Category category;
  public Ball(Integer cost, Category name) {
      this.cost=cost;
      this.category=name;
  }

  public void setCategory(Category category) {
      this.category = category;
  }
  public void setCost(Integer cost) {
      this.cost = cost;
  }

  public Category getCategory() {
      return category;
  }
  public Integer getCost() {
      return cost;
  }
}




상점에 축구공3개, 배구공 1개가 들어왔다고 생각하자.

List<Ball> ballList = Arrays.asList(new Ball(1000, Category.축구공),
                                    new Ball(3000, Category.축구공),
                                    new Ball(2000, Category.축구공),
                                    new Ball(20000,Category.배구공));

그리고 상점에서 재고파악을 위해 공종류별로 개수가 몇개인지 필요하기 때문에
먼저 아래처럼 '축구공' 만 가져올 수 있는 메서드를 만들었다.

약간 고전적(?)인 방식

public static List<Ball> filterSoccerBalls(List<Ball> ballList){//1. ball 리스트를 받아와서
        List<Ball> result = new ArrayList<>();
        for(Ball ball : ballList){ // 2. ball을 반복하면서
            if(Category.축구공.equals(ball.getCategory())){ // 3. 축구공이라면
                result.add(ball); // 4. 새로 생성된 result에 하나씩 담는다
            }
        }
        return result;
    }


공마다 메소드를 하나씩 만들어서 ball을 반복해서 검사하고 추출해야 하는걸까?
filterSoccerBalls(), filterBasketBalls(), filterXXXBals() ... 등등

공종류가 아닌 공의 가격에 따라 추출하는 경우도 만들어야 하면 상당히 많은 코드가 작성되어야 한다.

그럼 저 무수한 많은 메서드 -> ( filterSoccerBalls(), filterBasketBalls(), filterXXXBals(), ...)
를 통합해서 하나의 메소드만 사용해서 원하는 공을 반환하도록 만들어보자.


여기서 Predicate 가 필요하다. (Predicate는 보통 참과 거짓을 반환한다)

public interface BallPredicate {
    public boolean test(Ball ball);
}

그리고 일단 축구공이면 참을 반환하는 Predicate 구현체를 만들었다.

public class BallSoccerCategoryPredicate implements BallPredicate {
    @Override
    public boolean test(Ball ball) {
        return Category.축구공.equals(ball.getCategory());
    }
}

그럼 이제 하나의 메소드만 사용해서 특정 공을 추출할 준비가 되었따.

Predicate를 사용하여 메소드 통합 (동작 파라미터화)

public static List<Ball> filterBalls(List<Ball> inventory, BallPredicate p){
        List<Ball> result = new ArrayList<>();
        for (Ball ball : inventory){
            if(p.test(ball)){
                result.add(ball);
            }
        }
        return result;
    }

위의 filterBalls() 메소드는 공 전체가 포함된 List를 받아 어떤 Predicate가 오냐에 따라서 다른 공을 반환할 것이다. 위에서 우리가 만든 BallSoccerCategoryPredicate 는 축구공을 찾아 true를 반환하기 때문에 축구공만 가져오게 된다
List<Ball> SoccerBalls = filterBalls(ballList,new BallSoccerCategoryPredicate());

[결과]



그럼 Predicate 를 사용하면 다음과 같이 10000원 이상의 공도 꺼낼 수 있다.

public class BallHighPricePredicate implements BallPredicate {
    public boolean test(Ball ball){
        return ball.getCost() > 10000;
    }
}
List<Ball> ExpensiveBalls = filterBalls(ballList,new BallHighPricePredicate());

[결과]




요구사항이 올 때 마다 조건만 다르게해서 Predicate 를 구현하면 filterBalls() 메소드는 그대로 쓸 수 있게 되었다.

filterBalls(List, Predicate 구현체)
위의 2번째 매개 변수 Predicate 구현체에 따라 동작이 달라지게 되는 것이다.
이것이 바로 동작 파라미터화의 장점이다.




그런데 요구 사항이 늘어나면 filterBalls() 메소드는 그대로지만, Predicate 를 하나씩 만들어서 관리해야 하는 단점이 있다.

익명함수를 사용하여 바로 로직 작성

List<Ball> greenBalls = filterBalls(ballList,new BallPredicate(){
            @Override
            public boolean test(Ball ball) {
                return Category.축구공.equals(ball.getCategory());
            }
        });

익명함수를 사용하여 filterBalls() 에 Predicate 인터페이스를 바로 구현할 수 있다.
클래스를 따로 안만들어도 되서 어느정도 편해졌다. 하지만 자바8의 람다를 사용하면 이러한 코드도 줄일 수 있다.


람다를 사용하여 불필요한 코드 제거

List<Ball> soccerBalls = 
	filterBalls(ballList,(Ball ball) -> Category.축구공.equals(ball.getCategory()));

왜 이렇게 되었는지는 뒤에서 배우겠지만 상당히 편해졌다.

어차피 filterBalls() 메소드의 두번째는 'Predicate' 만 들어가야 하고,
Predicate는 'test()' 만 구현하면 되니깐
너무 당연한 코드는 제거되고, Predicate 구현에 필요한 Ball과 로직만 작성하면 되는 것이다.


여기까지 정리한다면
1. 참과 거짓을 반환하는 Predicate 를 만든다.
2. filterBalls() 메소드 내부에서는 Predicate 조건에 따라 새로운 리스트를 만들어간다.
public static List<Ball> filterBalls(List<Ball> inventory, BallPredicate p){
        List<Ball> result = new ArrayList<>();
        for (Ball ball : inventory){
            if(p.test(ball)){
                result.add(ball);
            }
        }
        return result;
    }

3. filterBalls()를 사용할 때 매개변수에 바로 Predicate를 구현한다.
List<Ball> soccerBalls = filterBalls(ballList,(Ball ball) -> Category.축구공.equals(ball.getCategory()));



결론

자바의 제네릭을 사용하면 Ball 이 아니더라도 다양한 객체를 비교하고 추출할 수 있다.
그리고 이미 Predicate도 제네릭을 사용하여 기본 라이브러리에 포함되어 있다.
조건에 따라 반복작업이 필요하면 다음과 같이하나만 작성하면 된다.

public static <T> List<T> filter(List<T> list, Predicate<T> p){
        List<T> result = new ArrayList<>();
        for(T e : list){
            if (p.test(e)){
                result.add(e);
            }
        }
        return result;
    }
List<Ball> filterList = filter(ballList,(Ball ball) -> Category.축구공.equals(ball.getCategory()));

[출처]
모던 자바 인 액션

profile
안녕하세요

0개의 댓글