모던 자바 인 액션 파트1 - 동작 파라미터화 코드 전달하기
자바8은 간결한 코드, 멀티코어 프로세서의 쉬울 활용 이라는 두가지 요구사항을 기반으로 한다.
1. 스트림 처리
스트림이란 한번에 한개씩 만들어지는 연속적인 데이터 항목들의 모임이다.
java.util.stream 패키지에 스트림 api 추가되었다.
반복되는 패턴으로 주어진 조건에 따라
- 데이터를 필터링(무게가 100 이상인 사과만)
- 데이터를 추출(사과 무게 필드 추출)
- 데이터를 그룹화(초록색 사과와 빨간색 사과로 그룹화)
컬렉션을 필터링하는 가장 빠른방법은
컬렉션을 스트림으로 바꾸고 병렬로 처리 후 리스트로 다시 복원한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | import java.util.*; import java.util.stream.*; public class Main { public static class Apple { private int weight = 0; private String color = ""; public Apple(int weight, String color) { this.weight = weight; this.color = color; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } @SuppressWarnings("boxing") @Override public String toString() { return String.format("Apple{color='%s', weight=%d}", color, weight); } } private static final List<Apple> inventory = Arrays.asList( new Apple(80, "GREEN") , new Apple(155, "GREEN") , new Apple(120, "RED")); public static void main(String[] args) { List<Apple> heavyApples = inventory.stream() .filter((Apple a) -> a.getWeight() > 100) .collect(Collectors.toList()); System.out.println(heavyApples); // [Apple{color='green', weight=155}, Apple{color='red', weight=120}] } } | cs |
2. 디폴드 메서드 지원
예를 들어 Collections.sort(list)는 정렬 하는 메소드이다.
자바8 이전에는 List를 구현하는 모든 클래스가 sort를 구현해야 하는데
자바8 이후에는 디폴트 sort메서드가 나와 구현하지 않아도 된다.
3. 동작 파라미터화 코드 전달하기
계속 변경 되는 요구사항에 대응하기 위해 엔지니어링 비용이 가장 최소화 될수 있게 유지보수가 쉬워야한다.
동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.
동작 파라미터는 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록이다.
변화하는 요구사항에 대응하기 위해 다음과 같은 문제가 주어진다고 가정하자.
3.1 녹색사과 필터링하기
1 2 3 4 5 6 7 8 9 10 | // 초록색 사과만 추출 public static List<Apple> filterGreenApples(List<Apple> inventory) { List<Apple> result = new ArrayList<>(); for (Apple apple : inventory) { if (Color.GREEN.name().equals(apple.getColor())) { result.add(apple); } } return result; } | cs |
위 메소드는 녹색사과만 필터링 할수 있다.
만약 빨간 사과도 필터링 한다고 하면
색깔만 다른 동일한 메소드 내용의 다른 메소드명( fliterRedApples )으로 생성할수도 있지만
argument enum Color를 추가하여 값을 받아와 처리한다.
3.2 Color 인자 추가하기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // enum Color enum Color{ RED ,GREEN } // Color를 파라미터화 public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) { List<Apple> result = new ArrayList<>(); for (Apple apple : inventory) { if (color.name().equals(apple.getColor())) { result.add(apple); } } return result; } | cs |
Color라는 인자를 추가하여 메소드의 인자가 1개 늘었다.
하지만 색깔뿐만 아니라 필터로 과일의 무게, 적합한지 flag값도 인자로 받아본다고 가정해보자.
3.3 모든 속성을 메서드 파라미터로 추가하기
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 모든 속성을 메서드 파라미터로 추가 public static List<Apple> filterApplesAllAttr(List<Apple> inventory, Color color , int weight, boolean flag) { List<Apple> result = new ArrayList<>(); for (Apple apple : inventory) { if (color.name().equals(apple.getColor()) && flag == true) { if(apple.getWeight() > weight) { result.add(apple); } } } return result; } | cs |
인자가 총 4개가 되었다. 모든속성을 받아와
조건에 따라 적합한 데이터를 추출하여 뽑아오는것이 가능해졌지만
가독성도 떨어지고 앞으로 추가될 여러 인자(5개~10개)에 유연하게 대처할 수 없다.
참 또는 거짓을 반환하는 함수 predicate(프레디케이트를) 사용하여
선택조건을 결정하는 인터페이스를 정의해보자.
3.4 추상적 조건으로 필터링하기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // 사과 predicate 인터페이스 public interface ApplePredicate{ boolean test(Apple apple); } // 초록색 사과 prediate public static class AppleGreenColorPredicate implements ApplePredicate { public boolean test(Apple apple) { return (Main.Color.GREEN.name()).equals(apple.getColor()); } } // 무거운 사과 prediate public static class AppleHeavyWeightPredicate implements ApplePredicate { public boolean test(Apple apple) { return apple.getWeight() > 150; } } // predicate를 사용하여 추상적 조건으로 필터링하기 public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){ List<Apple> result = new ArrayList<>(); for(Apple apple: inventory) { if(p.test(apple)) { result.add(apple); } } return result; } | cs |
인터페이스 ApplePredicate를 구현하는 클래스는
모두 들어갈 수 있게 되어 좀 더 유연하게 파라미터를 넣을 수 있다.
메인에서 다음과 같이 선언 후 넣으면 각 조건에 맞게 추상적으로 데이터를 추출할 수 있다.
1 2 | filterApples(inventory, new AppleHeavyWeightPredicate()) filterApples(inventory, new AppleGreenColorPredicate()) | cs |
3.5 익명 클래스를 사용하여 클래스 선언과 인스턴스화를 동시해 해서 가져오기
1 2 3 4 5 6 | // 클래스 선언과 인스턴스화 동시 수행하는 익명 클래스 사용하기 static List<Apple> redApples = filterApples(inventory, new ApplePredicate() { public boolean test(Apple apple) { return Color.RED.name().equals(apple.getColor()); } }); | cs |
predicate와 익명클래스를 같이 사용하여 필요한 데이터를 추출해 올 수 도 있다.
위 코드는 자바8의 람다 표현식을 이용하여 간단하게 표현할 수도 있다.
1 2 | // 람다 표현식 사용 static List<Apple> result = filterApples(inventory, (Apple apple) -> Color.RED.name().equals(apple.getColor())); | cs |
정리하면
동작 파라미터화를 이용해 변화하는 요구사항에 잘 대응할 수 있는 코드를 구현할 수 있으며 엔지니어링 비용 또한 줄일 수 있다.
predicate 인터페이스를 생성하여 여러 클래스에서 predicate를 구현하여 같은 메소드명의 다른 내용인 메소드(오버라이드)를 구현 할 수 있다.
마지막으로 전체 소스코드를 첨부한다.