☕️ java

일급 컬렉션

beomsic 2023. 10. 24. 02:56

 

 

🧑🏻‍💻 이동욱님의 글을 보고 공부용으로 정리했습니다.

 

⭐ 일급 컬렉션


Collection을 Wrapping 하면서, 그 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 한다.

Map<String, String> map = new HashMap<>();
map.put("1", "A");
map.put("2", "B");
map.put("3", "C");

public class GameRanking {

    private Map<String, String> ranks;

    public GameRanking(Map<String, String> ranks) {
        this.ranks = ranks;
    }
}

 

🌟 Wrapping의 이점

  1. 비지니스에 종속적인 자료구조
  2. Collection의 불변성 보장
  3. 상태와 행위를 한 곳에서 관리
  4. 이름이 있는 컬렉션

 

1️⃣ 비지니스에 종속적인 자료구조

📖 숫자 야구의 2가지 조건

  • 3개의 번호로 이루어져있다.
  • 3개의 번호는 1 ~ 9 까지의 서로 다른 임의의 수이다.

 

🧑🏻‍💻 구현

public class BaseBallApp {

    private static final int DIGIT = 3;
    ...
    
    if(!InputValidator.isValidatedAnswer(DIGIT, inputAnswer)) {
         throw new IllegalArgumentException(
                String.format("%d자리 숫자가 아닌 잘못된 입력값 입니다. - %s", DIGIT, inputAnswer));
    }
    ...
}
  • 비지니스 로직을 서비스 부분에서 처리

 

⚠️ 문제

숫자 야구 번호가 필요한 곳에서 계속해서 검증로직이 들어가야 한다.

  • 모든 코드에 대해 알고 있지 않다면 문제가 발생할 수 있다.

 

💡 숫자 야구의 조건을 만족하는 자료구조를 직접 만들자.

⇒ 해당 조건으로만 생성할 수 있는 자료구조

 

public class BaseBallNumber {

    private static final int DIGIT = 3;
    private final int[] numbers;

    public BaseBallNumber(int[] numbers) {
        validationSize(DIGIT, numbers);
        validationDuplicated(numbers);
        this.numbers = numbers;
    }
    ...
}
  • 이런 클래스를 일급 컬렉션이라고 한다.

 

2️⃣ 불변

일급 컬렉션은 컬렉션의 불변을 보장한다.

 

📖 final 키워드

  • Java 에서의 final 은 불변을 만들어주는 것이 아니다 ❌, 재할당만 금지한다

 

🌟 불변 객체의 중요성

  • 객체의 값이 절대 바뀔일이 없다는 것이 보장되면 코드를 이해하고 수정하는 사이드 이펙트가 최소화된다.

Java에서는 final로 불변 객체를 만들 수 없기 때문에 일급 컬렉션과 래퍼 클래스등의 방법으로 해결해야 한다.

 

public class BaseBallNumber {

    private static final int DIGIT = 3;
    private final int[] numbers;

    public BaseBallNumber(int[] numbers) {
        validateSize(numbers);
        validateWithinRange(numbers);
        this.numbers = numbers;
    }

    public void calculate(BiConsumer<Integer,Integer> biConsumer){
        for(int i = 0; i < numbers.length; i++) {
            biConsumer.accept(numbers[i], i);
        }
    }
}

이렇게 값을 변경할 수 있는 메소드가 없는 컬렉션을 만들게 되면 해당 컬렉션은 불변 컬렉션이 된다.

 

3️⃣ 상태와 행위를 한 곳에서 관리

  • 값과 로직이 함께 존재

 

ex) 여러 Pay 방법들이 있을 때, NaverPay 금액의 합이 필요한 경우

List<Pay> pays = Arrays.asList(
    new Pay(NAVER_PAY, 1000),
    new Pay(KAKAO_PAY, 1500),
    new Pay(NAVER_PAY, 2000),
    new Pay(KAKAO_PAY, 2000));

Long naverPaySum = pays.stream()
        .filter(pay -> pay.getPayType().equals(NAVER_PAY))
        .mapToLong(Pay::getAmount)
        .sum();
  • Pay라는 컬렉션과 계산하는 로직에 관계가 존재한다.

 

⚠️ 문제

  • 똑같은 기능을 하는 메소드가 중복되어 생성될 수 있다.
  • 메소드를 누락할 수 있다.
  • 다른 결과가 필요한 경우 더 코드가 흩어질 수 있다.
public class PayGroups {
    private List<Pay> pays;

    public PayGroups(List<Pay> pays) {
        this.pays = pays;
    }

    public Long getNaverPaySum() {
        return pays.stream()
                .filter(pay -> PayType.isNaverPay(pay.getPayType()))
                .mapToLong(Pay::getAmount)
                .sum();
    }

		public Long getKakaoPaySum() {
        return getFilteredPays(pay -> PayType.isKakaoPay(pay.getPayType()));
    }

    private Long getFilteredPays(Predicate<Pay> predicate) {
        return pays.stream()
                .filter(predicate)
                .mapToLong(Pay::getAmount)
                .sum();
    }
}
  • PayGroups 라는 일급 컬렉션을 통해 상태와 로직이 한곳에서 관리될 수 있게 되었다.

 

List<Pay> pays = Arrays.asList(
    new Pay(NAVER_PAY, 1000),
    new Pay(KAKAO_PAY, 1500),
    new Pay(NAVER_PAY, 2000),
    new Pay(KAKAO_PAY, 2000));

PayGroups payGroups = new PayGroups(pays);

Long naverPaySum = payGroups.getNaverPaySum();

 

4️⃣ 이름있는 컬렉션

컬렉션에 이름을 붙일 수 있다.

  • 일급 컬렉션을 만들면 해당 컬렉션을 기반으로 용어 사용과 검색을 하면 된다.

 

📖 참고자료


일급 컬렉션 (First Class Collection)의 소개와 써야할 이유