다음과 같이 공의 종류를 나타내는 enum 이 있다.
public enum Category {
농구공,축구공,배구공
}
그리고 특정 공의 가격과 종류를 대표하는 Ball 클래스가 있다.
Ball.classpublic 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());
}
}
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;
}
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과 로직만 작성하면 되는 것이다.
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;
}
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()));
[출처]
모던 자바 인 액션