모던 자바인 액션 공부

Daniel_Yang·2023년 8월 5일
0

1장 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?

쉽게 말해 멀티코어, 병렬, 간결한 코드라는 요구사항
스트림 API를 도입해서 해결한다. 이는 동작 파라미터화와 인터페이스의 디폴트 메서드를 기반으로 가능
어떻게 병철처리? 쉽게 말하면, 라이브러리 내부에서 멀티 CPU를 이용해서 병렬 처리
걱정되는 부분: 데이터 가변성 문제 ⇒ 공유된 데이터에 접근 x

변한 사항

  • 자바 8부터는 조금 더 자연어에 더 가깝게, 그리고 병렬 실행을 새롭고 단순하게 지원!
  • 현대 요구사항에 맞게 변화
    • 멀티코어 CPU의 대중화와 하드웨어적인 변화
    • 고전적인 객체지향에서 함수형 프로그래밍으로 나아가고 있다.
    • 자바 8의 요구사항: 간결한 코드, 멀티코어 프로세서의 쉬운 활용
  • 자바 8 설계의 밑바탕을 이루는 세가지 프로그래밍 개념
    • 스트림, 메서드 코드 전달, 인터페이스의 디폴트 메서드
      • 스트림 API 덕분에 다른 두가지 기능이 존재 가능하다.

스트림(java.util.stream 패키지 스트림 api)

  • 스트림 api: 조립 라인처럼 어떤 항목을 연속으로 제공하는 어떤 기능이라 생각
  • 스트림이란 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임이다.
    • 이론적으로 프로그램은 입력 스트림에서 데이터를 한개씩 읽어 들이며 마찬가지로 출력 스트림으로 데이터를 한 개씩 기록한다. 즉, 어떤 프로그램의 출력 스트림은 다른 프로그램의 입력 스트림이 될 수 있다.
  • 우리가 하려는 작업을 고수준으로 추상화해서 일련의 스트림으로 만들어 처리할 수 있다.
    • 스레드라는 복잡한 작업을 사용하지않으면서 공짜로 병렬성을 얻을 수 있다.

스트림 도입 배경

  • 멀티 스레딩 모델은 순차적인 모델보다 다루기 어렵다. 공유된 변수 처리 문제 ⇒ 자바 8은 컬렉션을 처리하면서 발생하는 모호함과 반복적인 코드 문제 그리고 멀티코어 활용 어려움을 해결하기 위해 스트림 API를 도입했다.
  • 스트림 API는 자주 반복되는 패턴으로 주어진 조건에 따라 데이터를 필터링(filtering)하거나, 데이터를 추출(extracting), 데이터를 그룹화(grouping)하는 등의 기능을 제공한다. 또한 이러한 동작들을 쉽게 병렬화할 수 있다. 두 CPU를 가진 환경에서 리스트를 필터링할 때 한 CPU는 리스트의 앞부분을 처리하고, 다른 CPU는 리스트의 뒷부분을 처리하도록 요청할 수 있다.이 과정을 포킹 단계라고 한다. 그리고 각각의 CPU는 자신이 맡은 절반의 리스트를 처리하고, 마지막으로 하나의 CPU도 정리한다.

유닉스 예시를 통한 설명

// 파일의 단어를 소문자로 바꾼 다음, 사전순으로 단어를 정렬했을 때 마지막 세 단어 출력
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3
  • 유닉스에서는 여러 명령을 병렬로 실행한다.
  • cat이나 tr이 완료되지 않은 시점에서 sort가 행을 처리하기 시작할 수 있다. 기계적인 예로 자동차 생산 공장 라인에 비유할 수 있다.
    • 조립 라인은 자동차를 물리적인 순서로 한 개씩 운반하지만 각각의 작업장에서는 동시에 작업을 처리한다.
  • 유닉스 명령어로 복잡한 파이프라인을 구성했던 것처럼 스트림 API는 파이프라인을 만드는데 필요한 많은 메서드를 제공한다.
class LegacyTest {
  private static void groupTransaction() {
    // 그룹화된 트랜잭션을 더할 Map 생성
    Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
    // 트랜잭션의 리스트를 반복
    for (Transaction transcation : transactions) {
      // 고가의 트랜잭션을 필터링
      if (transaction.getPrice() > 1000) {
        // 트랜잭션의 통화 추출
        Currency currency = transaction.getCurrency();
        List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
        // 현재 통화의 그룹화된 맵에 항목이 없으면 새로 만든다.
        if (transactionsForCurrency == null) {
          transactionsForCurrency = new ArrayList<>();
          transactionsByCurrencies.put(currency, transactionsForCurrency);
        }
        // 현재 탐색된 트랜잭션을 같은 통화의 트랜잭션 리스트에 추가한다.
        transactionsForCurrency.add(transaction);
      }
    }
  }
}

----

import static java.util.stream.Collectors.toList; 

class LegacyTest {
  private static void groupTransaction() {
    Map<Currency, List<Transaction>> transactionsByCurrencies = 
        transactions.stream()
            .filter((Transaction t) -> t.getPrice() > 1000) // 고가의 트랜잭션 필터링
            .collection(groupingBy(Transaction::getCurrency)); //통화로 그룹화함
  }
}

컬렉션

  • for-each루프를 이용해 반복 과정을 직접 처리함. -> 외부 반복
  • 기본적으로 단일 CPU만 이용하여 순차적으로 처리한다.

스트림

  • 라이브러리 내부에서 수행함. -> 내부 반복
  • 기본적으로 멀티 CPU를 이용하여 병렬로 처리한다.

**메서드에 코드를 전달하는 기법(동작 파라미터화)(메서드 참조와 람다)**

data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==

  • 메서드를 다른 메서드의 인수로 넘겨주는 기능 제공
  • 말 그대로 동작을 파라미터화 시키는 작업이며, 객체를 인수(파라미터)로 전달하는 방식을 생각했다면 조금 다르다. 자바스크립트에서 많이 볼 수 있는 것으로 동작(메서드)를 인수로 전달하는 방법이다.
  • 스트림 API는 연산의 동작을 파라미터화할 수 있는 코드를 전달한다는 사상에 기초

병렬성과 공유 가변 데이터

  • 보통 다른 코드와 동시에 실행 하더라도 안전하게 실행 할 수 있는 코드를 만들려면 공유된 가변 데이터에 접근하지 않아야 한다. 이러한 함수를 순수 함수, 부작용 없는 함수, 상태 없는 함수라고 부른다.
    • 하지만 공유된 변수나 객체가 있으면 병렬성에 문제가 발생한다. synchronized를 이용해서 보호하는 규칙을 만들 수 있을 것이다.
    • 자바 8 스트림을 이용하면 기존의 자바 스레드 API보다 쉽게 병렬성을 활용할 수 있다.
  • 공유되지 않는 가변 데이터, 메서드, 함수 코드를 다른 메서드로 전달하는 두가지 기능은 함수형 프로그래밍의 개념이다.

자바의 병렬성과 공유되지 않은 가변 상태

  • 흔히 자바의 병렬성은 어렵고, synchronized는 쉽게 에러를 일으킨다고 생각한다.
  • 자바 8은 이를 해결하기 위해 두가지 방식을 사용한다.
    • 라이브러리에서 분할 처리를 한다. 큰 스트림을 병렬 처리할 수 있도록 작은 스트림으로 분할한다.
    • filter 같은 라이브러리 메서드로 전달된 메서드가 상호작용하지 않는다면 가변 공유 객체를 통해 병렬성을 누릴 수 있다.
      • 여러 스레드가 동시에 가변 객체에 접근하더라도, 메서드가 상호작용하지 않기 때문에 안전하지만, 만약 전달되는 메서드가 공유되는 가변 객체를 변경한다면 문제가 발생한다. 이런 경우에는 스레드 동기화 기법을 사용하여 해결해야 한다.
  • 함수형 프로그래밍에서 함수형이란 '함수를 일급 값으로 사용한다.'는 의미를 가지고 있지만 부가적으로 '프로그램이 실행되는 동안 컴포넌트 간에 상호작용이 일어나지 않는다' 라는 의미도 포함한다.

자바 함수

  • 프로그래밍 언어의 핵심은 값을 바꾸는 것이고 이를 일급 값(시민)이라 부른다.
    • 메서드, 클래스 등은 이급 자바 시민에 해당
  • 함수를 새로운 값의 형식으로 추가. 즉, 메서드를 값으로 취급할 수 있게! ⇒ 프로그래머들이 더 쉽게 프로그램을 구현할 수 있도록
    // 자바 8 이전
    File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
    	public boolean accept(File file) {
    		return file.isHidden(); // 숨겨진 파일 필터링
        }
    })
    
    // 자바 8 이후
    File[] hiddenFiles = new File(".").listFiles(File::isHidden);
    • 이미 준비된 함수인 isHidden을 listFiles에 전달해서 사용하라
    • 람다: 익명함수
      • 예를 들어 (int x) -> x + 1, 즉 'x 라는 인수로 호출하면 x + 1을 반환'하는 동작을 수행하도록 코드를 구현할 수 있다
      • 즉, 위에 있는 코드에서는 해당 isHidden 메서드 리턴값을 파라미터로 사용하게 되는 것
      • 1, 2번 사용할 정도의 간단한 경우

Predicate란?

filterApples는 Predicate을 파라미터로 받고 있다. 인수로 받아 true나 false를 반환하는 함수를 프레디케이트라고 한다.

인터페이스의 디폴트 메서드

  • 배경: 어떻게 기존의 구현을 고치지 않고도 이미 공개된 인터페이스를 변경할 수 있을까?
  • 상속 구조에서 본다면 인터페이스 내부에 특정 메소드를 추가한다면 모든 구현체에 이를 구현해주어야할 것이다. 이는 고통이다. 그래서 이를 대처할 내부의 default 메소드를 추가했지만 이는 다중 상속 문제를 일으켰다.
  • 자바 8에서는 인터페이스를 쉽게 바꿀 수 있도록 디폴트 메서드를 지원한다.
    • 디폴트 메서드는 특정 프로그램을 구현하는데 도움을 주는 기능이 아니라 미래에 프로그램이 쉽게 변화할 수 있는 환경을 제공하는 것이다.
List<Apple> heavyApples =
        inventory.stream()
                .filter((Apple a) -> a.getWeight() > 150)
                .collect(toList());

List<Apple> heavyApples =
        inventory
                .parallelStream()
                .filter((Apple a) -> a.getWeight() > 150)
                .collect(toList());

예를 들어 자바 8에서는 List에 직접 sort 메서드를 호출할 수 있다. 이는 자바 8의 List 인터페이스에 다음과 같은 디폴트 메서드 정의가 추가되었기 때문이다.

default void sort(Comparator<? super E> c) {
    Collections.sort(this, c);
}

따라서 자바 8 이전에는 List를 구현하는 모든 클래스가 sort를 구현해야했지만 자바 8부터는 디폴트 sort를 구현하지 않아도 된다.

번외1 : 자바8의 인터페이스 vs 추상 클래스

자바8 이전에는 인터페이스는 메소드 구현부를 갖지 못한다는 점에서 추상 클래스와 크게 차이가 있었는데, 디폴트 메서드 지원으로 인해 추상 클래스와 유사한 성격을 띄게 되었다. 이 둘을 어떻게 구분하여 사용해야할까?

https://yaboong.github.io/java/2018/09/25/interface-vs-abstract-in-java8/

결론만 간단히 이야기하면, 추상 클래스의 경우는 관련성이 높은 클래스들 간의 코드 공유가 필요한 경우 사용한다. 반대로 인터페이스는 Comparable과 같이 서로 관련성 없는 클래스들이 구현하게 되는 경우 사용된다. 또한, 자바는 추상클래스는 다중 상속(extends)이 안되기 때문에 다중 상속(implement)이 필요한 경우 인터페이스를 사용한다.


2장 동작 파라미터화 코드 전달하기

동작 파라미터화 ⇒ 추상화 이용

  • 동작 파라미터화란, 어떻게 실행할지 결정하지 않은 코드 블록을 의미합니다.
  • 이 코드블록은 나중에 호출되어 사용되어질 때, 실행됩니다.
  • 예를들어, 나중에 실행될 메서드의 인수로 코드블록을 전달할 수 있고, 결과적으로 코드블록에 메서드의 동작이 파라미터화 되어 전달됩니다.

특징

  • 동작 파라미터화는 변화하는 요구사항에 쉽게 대응할 수 있는 유용한 패턴이다.
  • 동작 파라미터화 패턴은 동작을 캡슐화한 다음에 메서드로 전달해서 메서드의 동작을 파라미터화한다.

추상화

  • 거의 비슷한 코드가 반복 존재한다면 코드를 추상화한다.

전략 패턴

  • 전략 패턴(Strategy Pattern)은 디자인 패턴 중 하나로,
  • 알고리즘을 정의하고 각각을 별도의 클래스로 캡슐화한 다음, 이들을 교체할 수 있게 만드는 패턴
  • 전략 패턴을 사용하면 알고리즘을 쉽게 교체하고 확장할 수 있으며, 알고리즘의 사용자와 구현체를 분리할 수 있다.
  • 용어
    • 알고리즘: 어떤 문제를 해결하기 위한 일련의 명령들의 집합. 사실상 각 전략이 알고리즘이다 ⇒ 특정 문제를 해결하는 방법 또는 특정 작업을 수행하는 방법을 정의
    • 행위: 실제로 알고리즘이나 작업을 수행하는 메서드. 여러 알고리즘 또는 전략을 캡슐화한 각 클래스가 이 행위를 구현 ex) 결제
    • 전략: 특정 행위를 구현하는 방법을 나타냅니다. 이것은 일반적으로 인터페이스 또는 추상 클래스로 정의되며, 각 알고리즘을 나타내는 구체적인 클래스가 이를 구현 ex) 결제방법(신용카드 결제, 비트코인 결제)

chatGPT 예시

예를 들어, 
EmailNotificationStrategy, TextNotificationStrategy, 
InAppNotificationStrategy 등의 클래스를 만들 수 있습니다. 
각 클래스는 동일한 NotificationStrategy 인터페이스를 구현하며, 
이 인터페이스는 알림을 보내는 send 메소드를 정의합니다. 

이를 통해, 웹 애플리케이션에서는 send 메소드를 호출함으로써 알림을 보낼 수 있으며,
이 메소드는 실행 시점에서 선택된 전략에 따라 서로 다른 방식으로 알림을 보낼 수 있다.

Predicate

  • 참 또는 거짓을 반환하는 함수를 predicate라고 한다.
  • 조건에 따라 filter가 다르게 동작하는데 이를 전략 디자인 패턴이라 한다.
    • 각 전략을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음 런타임에 알고리즘을 선택하는 것이다.
    • ApplePredicate는 사과를 선택하는 전략을 캡슐화하고 있고, 전략에 따라 AppleWeightPredicate, AppleColorPredicate를 구현하도록 되어 있다.
    • 이러한 전략 패턴은 객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 전략을 바꿔주기만 함으로써 행위를 유연하게 확장할 수 있도록 한다.

Predicate는 Java 8에서 도입된 함수형 인터페이스 중 하나로, 어떤 타입의 객체를 입력받아 boolean 값을 리턴하는 test()라는 메서드 하나를 정의합니다. Predicate 인터페이스는 주로 컬렉션의 항목에 대한 필터링, 검증, 조건적인 처리 등에 사용됩니다.

Predicate의 사용 이유와 장점은 다음과 같습니다:

  1. 코드의 간결성: Predicate를 사용하면, 특정 조건에 따른 데이터 필터링 등의 로직을 간결하고 읽기 쉬운 코드로 표현할 수 있습니다.
  2. 재사용성: Predicate를 통해 정의된 조건은 재사용이 가능하며, 다른 메서드에서도 쉽게 적용될 수 있습니다.
  3. 함수형 프로그래밍: Predicate는 람다 표현식과 함께 사용될 수 있으며, 이를 통해 함수형 프로그래밍의 장점을 활용할 수 있습니다. 예를 들어, 람다 표현식을 사용하여 코드를 더욱 간결하게 만들 수 있습니다.

예를 들어, 다음은 리스트에서 특정 조건을 만족하는 항목만 필터링하는 예시입니다:


List<String> names = Arrays.asList("John", "Jane", "Adam", "Eve");
Predicate<String> startsWithJ = name -> name.startsWith("J");
List<String> startsWithJNames = names.stream().filter(startsWithJ).collect(Collectors.toList());

위 코드에서 startsWithJPredicate를 통해 정의된 조건으로, 이를 filter() 메서드에 전달하여 "J"로 시작하는 이름만을 선택하도록 하였습니다.

필요성 예시 과정

  • 첫번째 시도: 한 곳에서 처리
  • 두번째 시도: 메서드 분리

⇒ 추가적인 필터 기준 요구

  • 세번째 시도: 추가적인 필터 기준을 위해 필터 기준 인수 추가
  • 네번째 시도: 중복코드를 하나의 메서드로 정리

⇒ 동작 파라미터화 도입

  • 다섯번째 시도: 인터페이스 구현과 인스턴스화 따로
  • 여섯번째 시도: 익명 클래스
    • 구현, 인스턴스화 같이
  • 일곱번째 시도: 람다 표현식
    • 간결함

동작 파라미터화 간결화 과정

  • 인터페이스
  • 익명 클래스 구현
  • 람다 표현식

실전예제

  1. Comparator로 정렬하기
  2. Runnable로 코드 블록 실행하기
  3. ExcecutorService에서 태스크 처리하기
  4. GUI 이벤트 처리하기

Comparator로 정렬하기 예제

  • 인터페이스 자바 8의 List에는 sort메서드가 포함되어 있다. 다음과 같은 인터페이스를 갖는 java.util.Comparator 객체를 이용해서 sort의 동작을 파라미터화할 수 있다.
    // java.uitl.Comparator
    public interface Comparator<T> {
    	int compare(T o1, T o2);
    }
    
  • 구현 + 인스턴스화

  • 익명 클래스

    Comporator를 구현해서 sort 메서드의 동작을 다양화할 수 있다. 익명 클래스를 이용해서 무게가 적은 순서로 목록에서 사과를 정렬할 수 있다.

    inventory.sort(new Comparator<Apple>() {
    	public int compare(Apple a1, Apple a2) {
    		return a1.getWegiht().compareTo(a2.getWeight());
    	}
    });
    
  • 람다 표현식 람다 표현식을 이용하면 다음처럼 간단하게 코드를 구현할 수 있다.
    inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo)a2.getWeight()));
    

필요성 예시

  • 한 농부의 요구사항을 듣고 코딩을 해서 농부의 문제를 해결해준다고 생각해보자.
  • 요구사항: 녹색, 빨간색 이렇게 두 종류의 사과를 구분
//사과 클래스
class Apple {
    enum Color{
        GREEN, RED
    }
    public Color color;
		public int weight;

		public Apple(Color color, int weight){
        this.color = color;
        this.weight = weight;
    }
}

첫 번째 시도

  • 녹색 사과만 분류
public class Main {
    public static List<Apple> Apples = Arrays.asList(new Apple(Apple.Color.GREEN),new Apple(Apple.Color.RED));
    public static void main(String[] args) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : Apples){
            if (apple.color.equals(Apple.Color.GREEN)){
                result.add(apple);
            }
        }
        result.stream().forEach(apple -> System.out.println(apple.color));
    }
}

두 번째 시도

  • 추가된 요구사항: 원하는 색이 있을 때마다 분류
  • 메서드로 분리
public class Main {
    public static List<Apple> Apples = Arrays.asList(new Apple(Apple.Color.GREEN),new Apple(Apple.Color.RED));
    
    public static void main(String[] args) {
        appleColorFilter(Apples,Apple.Color.GREEN).stream().forEach(apple -> System.out.println(apple.color));
    }
    
    public static List<Apple> appleColorFilter(List<Apple> apples, Apple.Color color){
        List<Apple> result = new ArrayList<>();
        for (Apple apple : Apples){
            if (apple.color.equals(color)){
                result.add(apple);
            }
        }
        return result;
    }
}

세 번째 시도

  • 추가된 요구사항: 사과의 무게도 분류
  • 혹시 몰라서 무게도 인수화해서 유연하게 대처
  • 문제: appleColorFilter와 appleWeightFilter 는 근본적으로 같은 일을 하는 코드다
public static List<Apple> appleWeightFilter(List<Apple> apples, int weight){
    List<Apple> result = new ArrayList<>();
    for (Apple apple : Apples){
        if (apples.weigth >= weight){
            result.add(apple);
        }
    }
    return result;
}

네 번째 시도

  • 중복코드를 하나의 메서드로 정리
    • 하지만 만약에 color만 필터링하고 싶다면?
public static List<Apple> appleFilter(List<Apple> apples, Apple.Color color, int weight){
    List<Apple> result = new ArrayList<>();
    for (Apple apple : Apples){
        if (apple.color.equals(color) && apple.weight >= weight){
            result.add(apple);
        }
    }
    return result;
}

// 이렇듯 의미없는 0이 들어 가버린다.
appleFilter(apples, [Apple.Color.Green](http://apple.color.green/), 0)

⇒ 아마 여태까지 많은 이들이 이렇게 진행했을 것이다.

생성자 vs 빌더와도 같다. 위 코드를 보고있자면 이게 무엇을 의미하는지 바로바로 알 수가 없어서 불편하다.

다섯번째 시도

  • **동작 파라미터화 도입**
  • Predicate를 통해 각 필터 기준마다 구현하게끔
// 필터에 따른 선택 **조건을 결정하는 인터페이스**를 정의
interface ApplePredicate{
    public boolean filter(Apple apple);
}

/*컬러 필터*/
class AppleColorFilter implements ApplePredicate{
    @Override
    public boolean filter(Apple apple) {
        return apple.color.equals(Apple.Color.GREEN);
    }
}
/*무게 필터*/
class AppleWeightFilter implements ApplePredicate{
    @Override
    public boolean filter(Apple apple) {
        return apple.weight >= 150;
    }
}
  • 필터 적용하는 메서드는 컬러, 무게 인수 대신 필터 기준 인수 하나만 갖게되었다.
// 메서드는 객체만 인수로 받으므로 메서드를 ApplePredicate 객체로 감싸서 전달
public static List<Apple> appleFilter(List<Apple> apples, 
													ApplePredicate p){
    List<Apple> result = new ArrayList<>();
    for (Apple apple : Apples){
        if (p.filter(apple)){
            result.add(apple);
        }
    }
    return result;
}
  • 적용
    • 파라미터화를 통해 명시적인 코딩, 간결한 코드
public static void main(String[] args) {
    appleFilter(Apples,new AppleColorFilter())
            .stream().forEach(apple -> System.out.println(apple.color));
    appleFilter(Apples,new AppleWeightFilter())
            .stream().forEach(apple -> System.out.println(apple.color));
}

이처럼 각 항목에 적용할 동작을 분리할 수 있는다는 것은 동작 파라미터화의 강점이다.

⇒ 왜냐하면 어떻게 실행할 지 결정하지 않았기에 추상화된 것을 가지고 하나로 통일 가능

⇒ 유연한 API를 만들 때 동작 파라미터화가 중요한 역할을 한다.

여섯번째 시도

  • 동작을 추상화해서 변화하는 요구사항에 대응할 수 있는 코드 작성 했으나,
    • 여러 클래스를 구현해서 인스턴스화하는 과정이 번거로우며
      • 이 부분

- 로직과 관련없는 코드도 많이 추가되기에 시간 낭비 초래
  • 익명 클래스
    • 클래스 선언과 인스턴스화를 동시에
    • 익명 객체를 이용한 파라미터화
interface ApplePredicate{
    public boolean filter(Apple apple);
}
public static List<Apple> appleFilter(List<Apple> apples, ApplePredicate p){
    List<Apple> result = new ArrayList<>();
    for (Apple apple : Apples){
        if (p.filter(apple)){
            result.add(apple);
        }
    }
    return result;
}
public static void main(String[] args) {
    appleFilter(Apples, new ApplePredicate() { // 익명 객체 활용
        @Override
        public boolean filter(Apple apple) {
            return apple.color.equals(Apple.Color.GREEN);
        }
    }).stream().forEach(apple -> System.out.println(apple.color));
    
    
    appleFilter(Apples,new AppleWeightFilter()) // 상속 활용
            .stream().forEach(apple -> System.out.println(apple.color));
}

일곱번째 시도

  • 위에 있는 익명 클래스 사용은 코드 가독성을 떨어뜨린다.
  • 어차피 많이 사용할 것이 아니라면…
  • 람다 표현식
public static void main(String[] args) {
		appleFilter(Apples,(Apple apple) -> Apple.Color.GREEN.equals(apple.color));

}

또한 추상화를 통하여 문제를 해결 할 수 있다.

  • 변화하는 요구사항: 적용 대상, 필터 적용 기준도 가능!

추상화를 통한 문제해결

interface ItemPredicate<T>{
    public boolean filter(T item);
}

---- 

public static <T> List<T> filter(List<T> list, ItemPredicate<T> p){
    List<T> result = new ArrayList<>();
    for (T item : list){
        if (p.filter(item)){
            result.add(item);
        }
    }
    return result;
}

1개의 댓글

comment-user-thumbnail
2023년 8월 5일

이런 유용한 정보를 나눠주셔서 감사합니다.

답글 달기