Contents
동적 파라미터를 예제를 통해서 배워보자(2)
   Aug 30, 2022     8 min read

Chapter2-1

복잡한 과정 간소화

filterApples 메서드로 새로운 동작을 전달하려면 ApplePredicate 인터페이스를 구현하는 여러 클래스를 정의한 다음에 인스턴스화해야 한다. 이는 상당히 번거로운 작업이며 시간 낭비다..

//무거운 사과 선택
public class AppleHeavyWeightPredicate implements ApplePredicate{
	public boolean test(Apple apple){
		return apple.getWeight() > 150;
	}
}

//녹색사과 선택
public class AppleGreenColorPredicate implements ApplePredicate{
	public boolean test(Apple apple){
		return GREEN.equals(apple.getColor());
	}
}

public class FilteringApples{
	public static void main(String[] args){
		List<Apple> inventory = Arrays.asList(new Apple(80, GREEN),
																				  new Apple(155, GREEN),
																					new Apple(120, RED));

		// 결과 리스트는 155 그램의 사과 한 개를 포함한다.
		List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate());

		// 결과 리스트는 녹색 사과 두 개를 포함한다.
	  List<Apple> greenApples = filterApples(inventory, new AppleGreenColorPredicate());



	}

	pubic static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){
		List<Apple> result = new ArrayList<>();
			for(Apple apple : inventory){
				if(p.test(apple)){
					result.add(appl);
				}
			}
return result;
	}
}

로직과 관련 없는 코드가 많이 추가 되었다.

이것을 개선하려면 어떻게 해야할까 ?

자바는 클래스 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스 를 제공한다.

익명클래스

익명클래스는 자바의 지역 클래스(local class, 블록 내부에 선언된 클래스)와 비슷한 개념이다. 익명 클래스는 말 그대로 이름이 없는 클래스다.익명 클래스를 사용하면 클래스 선언과 인스턴스화를 동시에 할 수 있다. 즉, 바로 필요한 구현을 만들어서 사용할 수 있다.

다섯 번째 시도 : 익명 클래스 사용

익명클래스를 이용해서 ApplePredicate를 구현하는 객체를 만드는 방법으로 필터링 예제를 다시 구현한 코드다.

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
	public boolean test(Apple apple){
		return RED.equals(apple.getColor());
	}
});
  • filterApples 메서드의 동작을 직접 파라미터화했다.

GUI 애플리케이션에서 이벤트 핸들러 객체를 구현할 때는 익명 클래스를 종종 사용한다. 예) swing, JavaFX

button.setOnAction(new EventHandler<ActionEvent>(){
	public void handle(ActionEvent event){
		System.out.println("Whooo a click!!");
	}
});

익명 클래스로도 아직 부족한 점이 있다.

첫째

아래 파란 글씨로 표현한 부분에서 알 수 있는 것처럼 익명 클래스는 여전히 많은 공간을 차지한다.

  • 파란 부분은 반복되어 있다.
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
	public boolean test(Apple apple){
		return RED.equals(apple.getColor());
	}
});

button.setOnAction(new EventHandler<ActionEvent>(){
	public void handle(ActionEvent event){
		System.out.println("Whooo a click!!");
	}
});

둘째

많은 프로그래머가 익명 클래스의 사용에 익숙하지 않다.

익명클래스로 인터페이스를 구현하는 여러 클래스를 선언하는 과정을 조금 줄일 수 있지만 여전히 만족스럽지 않다. 코드 조각(예를 들면 선택 기준을 가리키는 boolean 표현식)을 전달하는 과정에서 결국은 객체를 만들고 명시적으로 새로운 동작을 정의하는 메서드(Predicate의 test메서드나 EventHandler의 handle 메서드)를 구현해야 한다는 점은 변하지 않는다.

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

List<Apple> result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));

Untitled

일곱 번째 시도 : 리스트 형식으로 추상화

pubilc interface Predicate<T>{
	boolean test(T t);
}

public static <T> List<T> filter(List<T> list, Predicate<T> p) { //형식 파라미터 T 등장
	List<T> result = new ArrayList<>();
	for(T e : list){
		if(p.test(e)){
			result.add(e);
		}
	}
	return result;
}

이제 바나나, 오렌지, 정수, 문자열 등의 리스트에 필터 메서드를 사용할 수 있다. 다음은 람다 표현식을 사용한 예제다.

List<Apple> redApples = fiter(inventory, (Apple apple) -> RED.equals(apple.getColor()));

List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 ==0);

자바 API의 많은 메서드를 다양한 동작으로 파라미터화할 수 있다. 또한 이들 메서드를 익명 클래스와 자주 사용하기도 한다. 예제를 통해서 더 익혀보자.

Ex1) Comparator로 정렬하기

컬렉션 정렬은 반복되는 프로그래밍 작업이다.

예를들어 농부가 무게를 기준으로 목록에서 사과를 정렬하고 싶다고 말할 것이다. 하지만 다음날 마음이 바뀌어 색을 기준으로 사과를 정렬하고 싶어질 수 있다. 이와 같은 경우는 일상에서 흔히 발생하는 일이다. 개발자에게는 변화하는 요구사항에 쉽게 대응할 수 있는 다양한 정렬 동작을 수행할 수 있는 코드가 절실하다.

자바8의 List에는 sort 메서드가 포함되어 있다. (Collections.sort도 존재함)

  • Comparator 객체를 이용해서 sort의 동작을 파라미터화할 수 있다.
//java.util.Comparator
pubilc interface Comparator<T>{
	int compare(T o1, T o2);
}

Comparator를 구현해서 sort 메서드의 동작을 다양화할 수 있다.

예를 들어 익명 클래스를 이용해서 무게가 적은 순서로 목록에서 사과를 정렬할 수 있다.

inventory.sort(new Comparator<Apple>(){
	public int compare(Apple a1, Apple a2) {
		return a1.getWeight().compareTo(a2.getWeight());
	}
});
  • 농부의 요구사항이 바뀌면 새로운 요구사항에 맞는 Comparator를 만들어 sort 메서드에 전달할 수 있다.

실제 정렬 세부사항은 추상화되어 있으므로 신경 쓸 필요가 없다.

람다 표현식으로 표현

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

Ex2)Runnable로 코드 블록 실행하기

자바스레드를 이용하면 병렬로 코드 블록을 실행할 수 있다. 어떤 코드를 실행할 것인지를 스레드에게 알려줄 수 있을까 ?

여러 스레드가 각자 다른 코드를 실행할 수 있다. 나중에 실행할 수 있는 코드를 구현할 방법이 필요하다.

자바 8까지는 Thread 생성자에 객체만을 전달할 수 있었으므로 보통 결과를 반환하지 않는 void run 메소드를 포함하는 익명 클래스가 Runnable인터페이스를 구현하도록 하는 것이 일반적인 방법이었다.

  • 자바에서는 Runnable 인터페이스를 이용해서 실행할 코드 블록을 지정할 수 있다.
  • 아래 코드에서 코드블록을 실행한 결과는 void다.
//java.lang.Runnable
public interface Runnable{
	void run();
}
  • Runnable을 이용해서 다양한 동작을 스레드로 실행할 수 있다.
Thread t = new Thread(new Runnable){
		public void run(){
			System.out.println("Hello world");
		}
});

Ex3) Callable을 결과로 반환하기

자바5 부터 지원하는 ExecutorService 추상화 개념이 있다.

  • ExecutorService 인터페이스는 태스크 제출과 실행 과정의 과정의 연관성을 끊어준다.
  • ExecutorService를 이용하면 태스크를 스테드 풀로 보내고 Future로 저장할 수 있다는 점이 스레드와 Runnable을 이용하는 방식과 다르다.
  • Callable 인터페이스를 이용해 결과를 반환하는 태스크를 만든다. 이 방식은 Runnable의 업그레이드 버전이라 생각할 수 있다.
//java.util.concurrent.Callable
public interface Callabel<V>{
		V call();
}
  • 실행 서비스에 태스크를 제출해서 위 코드를 활용할 수 있다.
  • 예제는 태스크를 실행하는 스레드의 이름을 반환한다.
ExecutorService executorService = Executors.new CachedThreadPool();
Future<String> threadName = executorService.submit(new Callable<String>() {
		@Override
		public  String call() throws Exception{
			return Thread.currentThread().getName();
		}
});
  • 람다를 이용해서 표현하면 코드를 줄일 수 있다.
Future<String> threadName = executorService.submit() -> Thread.currentThread().getName());

Ex4)GUI 이벤트 처리하기

일반적으로 GUI 프로그래밍은 마우스 클릭이나 문자열 위로 이동하는 등의 이벤트에 대응하는 동작을 수행하는 식으로 동작한다.

  • 예를들면 사용자가 전송 버튼을 클릭하면 팝업을 표시하거나 동작 로그를 파일로 저장할 수 있다.
  • GUI 프로그래밍에서도 변화에 대응할 수 있는 유연한 코드가 필요하다. 모든 동작에 반응할 수 있어야 하기 때무이다.

자바FX에서는 setOnAction 메서드에 EventHandler를 전달함으로써 이벤트에 어떻게 반응할지 설정할 수 있다.

Button button = new Button("Send");
button.setOnAction(new EventHandler<ActionEvent>(){
	public void handle(ActionEvnet event) {
		label.setText("Sent!!");
	}
});

EventHandler는 setOnAction 메서드의 동작을 파라미터화한다.

  • 람다식 표현
button.setOnAction((ActionEvent event) -> label.setText("Sent!!"));

Chapter 2에서 배웠던 내용

  1. 동작 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달한다.
  2. 동작 파라미터화를 이용하면 변화하는 요구사항에 더 잘 대응할 수 있는 코드를 두현할 수 있으며 나중에 엔지니어링 비용을 줄일 수 있다.
  3. 코드 전달 기법을 이용하면 동작을 메서드의 인수로 전달할 수 있다. 하지만 자바8 이전에는 코드가 지저분하게 구현해야했다. 익명 클래스로도 어느 정도 코드를 깔끔하게 만들 수 있지만 자바8 에서는 인터페이스를 상속받아 여러 클래스를 구현해야하는 수고가 없을 수 있는 방법을 제공한다.
  4. 자바 API의 많은 메서드는 경렬, 스레드, GUI 처리 등을 포함한 다양한 동작으로 파라미터화 할 수 있다.