해당 내용은 Java8 In Action 책을 요약 및 정리한 내용입니다.
좀 더 자세히 알고 싶으신 분들은 책을 사서 읽어보심을 추천드립니다.!
2.3 복잡한 과정 간소화
자바는 클래스의 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스(anonymous class)라는 기법을 제공한다.
Entity
AppleEntity.java
package Part1.Chapter2.Chapter2_2_3.entity;
public class AppleEntity {
private String color;
private int weight;
public AppleEntity() {}
public AppleEntity(String color, int weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
Interface
ApplePredicate.java
package Part1.Chapter2.Chapter2_2_3.filters.inter;
import Part1.Chapter2.Chapter2_2_3.entity.AppleEntity;
public interface ApplePredicate {
boolean test(AppleEntity appleEntity);
}
Predicate.java
package Part1.Chapter2.Chapter2_2_3.filters.inter;
public interface Predicate<T> {
boolean test(T t);
}
Main
package Part1.Chapter2.Chapter2_2_3;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import Part1.Chapter2.Chapter2_2_3.entity.AppleEntity;
import Part1.Chapter2.Chapter2_2_3.filters.inter.ApplePredicate;
import Part1.Chapter2.Chapter2_2_3.filters.inter.Predicate;
/*
* 2.3.1 익명 클래스
*
* 익명 클래스는 자바의 지역 클래스(local class)와 비슷한 개념이다.
* 말그대로 이름이 없는 클래스다.
* 익명 클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할 수 있다.
* 즉, 즉석에서 필요한 구현을 만들어서 사용할 수 있다.
*/
public class Main_2_3 {
public static List<AppleEntity> filterApples(List<AppleEntity> inventory, ApplePredicate p) {
List<AppleEntity> result = new ArrayList<>();
for(AppleEntity apple : inventory) {
if(p.test(apple)) {
result.add(apple);
}
}
return result;
}
/*
* 이게 진짜 끝판왕이다. 자세한 설명은 생략..이 아니라 main 메서드를 참고하도록 한다.
*/
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;
}
public static void main(String[] args) {
List<AppleEntity> inventory = Arrays.asList(new AppleEntity("green", 100)
, new AppleEntity("red", 120)
, new AppleEntity("green", 150));
/*
* 2.3.2 다섯 번째 시도 : 익명 클래스 사용(이 정도까지 했으면 그냥 내가 사장 하는게 나을듯 싶다...)
*
* 익명 클래스로도 아직 부족한 점이 있다.(저자는 언제쯤 만족할건지 궁금하다..)
* 1. 객체 생성 구문과 메서드 작성 부분 때문에 코드가 많은 공간을 차지한다.
* 2. 많은 프로그래머가 익명 클래스의 사용에 익숙하지 않다.
*
* 코드의 장황함(verbosity)은 나쁜 특성이다.
* 장황한 코드는 구현하고 유지보수하는데 시간이 오래 걸린다.
* 한눈에 이해할 수 있는 코드가 좋은 코드다.(당연한 말인거 같은데..)
*/
filterApples(inventory, new ApplePredicate() { // ApplePredicate는 인터페이스이다.
@Override
public boolean test(AppleEntity appleEntity) {
return "red".equals(appleEntity.getColor());
}
})
.stream()
.forEach((AppleEntity apple) -> System.out.println("[Anonymous Class] apple Color : " + apple.getColor()
+ ", apple weight : " + apple.getWeight() ));
/*
* 2.3.3 여섯 번째 시도 : 람다 표현식 사용
*
* 람다 표현식을 사용하면 아래와 같이 간단(?)하게 재구현 할 수 있다.
*/
filterApples(inventory, (AppleEntity apple) -> "red".equals(apple.getColor()) )
.stream()
.forEach((AppleEntity apple) -> System.out.println("[Lambda] apple Color : " + apple.getColor()
+ ", apple weight : " + apple.getWeight() ));
/*
* 2.3.4 일곱 번째 시도 : 리스트 형식으로 추상화(여기까지 온거면 볼 장 다 본거 같다..)
*
* 현재 filterApples는 Apple과 관련된 동작만 수행한다.
* 하지만 Apple 이외의 다양한 물건에서 필터링이 작동하도록 리스트 형식을 추상화할 수 있다.
* (왠지 느낌이 이번이 진짜 끝판왕 같다.. 레알루다가..)
*/
filter(inventory, (AppleEntity apple) -> "red".equals(apple.getColor()) )
.stream()
.forEach((AppleEntity apple) -> System.out.println("[Last Boss] apple Color : " + apple.getColor()
+ ", apple weight : " + apple.getWeight() ));
filter(Arrays.asList(0, 1, 2, 3, 4, 5), (Integer i) -> i % 2 == 0 )
.stream()
.forEach((Integer i) -> System.out.println("[Last Boss] even number : " + i));
}
}
2.4 실전 예제
- 지금까지 동작 파라미터화가 변화하는 요구사항에 쉽게 적응하는 유용한 패턴임을 확인했다. 동작 파라미터화 패턴은 동작을(한 조각의 코드로) 캡슐화한 다음에 메서드로 전달해서 메서드의 동작을 파라미터화한다.
- 자바 API의 많은 메서드를 다양한 동작으로 파라미터화 할 수 있다. 또한 이들 메서드를 익명 클래스와 자주 사용하기도 한다.
Entity
AppleEntity.java
package Part1.Chapter2.Chapter2_2_4.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(int weight) {
this.weight = weight;
}
}
Main
package Part1.Chapter2.Chapter2_2_4;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.IntStream;
import Part1.Chapter2.Chapter2_2_4.entity.AppleEntity;
public class Main_2_4 {
/*
* 2.4.1 Comparator로 정렬하기.
*
* 우리의 갑님 농부님께서 무게를 기준으로 목록에서 정렬하고 싶다고 하셨다.
* 그러나 갈대 같은 마음을 가진 농부님께서는 색을 기준으로 사과를 정렬하고 싶다고 하실거 같기도 하다.
* 일상에서 흔히 일어나는 일이지 않는가?(....뒤..ㅈ...)
*
* 그러나 걱정하지 말아라 자바에서는 java.util.Comparator 객체를 이용해서 sort의
* 동작을 파라미터화할 수 있다.
*/
static void appleCompare(List<AppleEntity> inventory) {
/*
* 아래와 같이 Comparator를 구현해서 sort 메서드의 동작을 다양화할 수 있다.
* 아래 코드는 익명 클래스를 이용해서 무게가 적은 순으로 목록에서 사과를 정렬한다.
*/
inventory.sort(new Comparator<AppleEntity>() {
@Override
public int compare(AppleEntity o1, AppleEntity o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
inventory.stream()
.forEach((AppleEntity apple) -> System.out.println("[Anonymous Class - Ascending] color : " + apple.getColor()
+ ", weight : " + apple.getWeight() ));
/*
* 아래 코드는 람다표현식으로 무게가 큰 순으로 목록에서 사과를 정렬한다.
*/
inventory.sort((AppleEntity a1, AppleEntity a2) -> a2.getWeight().compareTo(a1.getWeight()) );
inventory.stream()
.forEach((AppleEntity apple) -> System.out.println("[Lambda - Descending] color : " + apple.getColor()
+ ", weight : " + apple.getWeight() ));
}
/*
* 2.4.2 Runnable로 코드 블록 실행하기
*
* 자신만의 코드 블록을 수행한다는 점에서 스레드 동작은 경량 프로세스와 비슷하다.
* 각각의 스레드는 각기 다른 코드를 실행할 수 있다.
* 나중에 실행될 코드 조각을 어떻게 표현할 수 있는가가 문제다.
*
* 자바에서는 Runnable 인터페이스를 이용해서 실행할 코드 블록을 지정할 수 있다.
*/
static void runnable() {
// 익명 클래스.
new Thread(new Runnable() {
@Override
public void run() {
/*
* for(int i = 0; i < 100; i++) {
* System.out.println("Anonymous Class [" + i + "] hello gab nim");
* }
* 를 람다 형식으로 하면 아래와 같다.
*/
IntStream.range(0, 100).forEach((int i) -> System.out.println("Anonymous Class [" + i + "] hello gab nim") );
}
}).start();
// 람다 표현식.
new Thread(() -> IntStream.range(0, 100).forEach((int i) -> System.out.println("Lambda [" + i + "] hello gab nim") )).start();
}
static public void main(String[] args) {
// 2.4.1 Comparator로 정렬하기.
appleCompare(Arrays.asList(new AppleEntity("green", 90)
, new AppleEntity("green", 150)
, new AppleEntity("red", 120)));
// 2.4.2 Runnable로 코드 블록 실행하기.
runnable();
}
}
동작 파라미터화 코드 전달하기
동작 파라미터화(behavior parameterization)란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미.
이 코드 블록은 나중에 프로그램에서 호출한다. 즉, 코드 블록의 실행은 나중으로 미뤄진다. 예로 컬렉션을 처리할 때 다음과 같은 메서드를 구현한다고 가정하자.
- 리스트의 모든 요소에 '어떤 동작'을 수행할 수 있음.
- 리스트 관련 작업을 끝낸 다음에 '어떤 다른 동작'을 수행할 수 있음.
- 에러가 발생하면 '정해진 어떤 다른 동작'을 수행할 수 있음
같이 동작 파라미터화로 위 따옴표 영역에 다양한 기능을 수행할 수 있다.
요약
- 동작 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달한다.
- 동작 파라미터화를 이용하면 변화하는 요구사항에 더 잘 대응할 수 있는 코드를 구현할 수 있으며 나중에 엔지니어링 비용을 줄일 수 있다.
- 코드 전달 기법을 이용하면 동작을 메서드의 인수로 전달할 수 있다. 익명 클래스로도 어느 정도 코드를 깔끔하게 만들 수 있지만 자바 8에서는 인터페이스를 상속받아 여러 클래스를 구현해야 하는 수고를 없앨 수 있는 방법을 제공한다.
- 자바 API의 많은 메서드는 정렬, 스레드, GUI 처리등을 포함한 다양한 동작으로 파라미터화할 수 있다.