[Java] List를 감싸는 순간 달라지는 코드 — 일급 컬렉션으로 리팩토링해보기

WonJun Jeon·2025년 10월 23일
1

이 글은 각 컬렉션별로 구체적인 특징보다 컬렉션이 무엇이고, 일급 컬렉션을 사용했을 때 코드에 어떤 차이가 있는지 알아보는 글입니다.

컬렉션

일급 컬렉션을 이해하기 위해서는 우선 컬렉션이 뭔지 알고 넘어가야 합니다.
컬렉션이란 요소들의 그룹으로 정의합니다.
처음에는 요소들의 그룹이라고 정의했길래 헷갈렸는데 흔히들 사용되는 List, Set, Map, Queue, Stack 등이 있습니다.

컬렉션의 종류


사진출처: [IT 기술 면접] Java의 Collection Framework 이란?[무작정 개발]
컬렉션은 ArrayList, LinkedList, Vector, HashSet, TreeSet, HashMap, TreeMap 등이 있으며, 크게 아래와 같은 자료구조 유형에 따라 구분됩니다.

종류특징
List이름과 같이 목록처럼 데이터를 순서에 따라 관리
Set중복이 허용되지 않는 데이터를 관리
Map데이터를 {Key, Value}로 짝을 이루어 관리, Key값은 중복 허용 X
Queue데이터 In/Out 순서를 FIFO 방식으로 관리(FIFO: First In First Out)
Stack데이터 In/Out 순서를 LIFO 방식으로 관리(LIFO: Last In First Out)

컬렉션의 특징

컬렉션 클래스들이 데이터를 다룰 때 그 데이터는 기본적으로 객체만 가능합니다.
즉, char, int, float와 같은 기본형 대신 Character, Integer, Float과 같은 Wrapper클래스를 사용해야합니다.


일급 컬렉션

일급 컬렉션(First-Class Conllection)은 하나의 컬렉션을 감싸는 클래스를 만들고, 해당 클래스에서 컬렉션과 관련된 비즈니스 로직을 관리하는 패턴입니다.

간다하게 설명드리자면, 아래의 코드를

// 일반 컬렉션 사용
List<Double> numbers = new ArrayList<>();
numbers.add(21.0);
numbers.add(2.0);
numbers.add(5.0);

아래와 같이 Wrapping 하는 겁니다.

public class Calculators {

    private final List<Double> numbers;

    public Calculators(List<Double> numbers) {
        this.numbers = new ArrayList<>(numbers);
    }
}

Collection을 Wrapping하면서 그 외 다른 멤버 변수를 가지지 않는 상태가 일급 컬렉션입니다.
즉, 다른 멤버 변수를 가지지 않는 컬렉션을 wrapping하는 클래스라고 해석할 수 있습니다.

WHY? 왜 일급컬렉션을 사용할까?

계산기는 int범위 안에서만 계산할 수 있다는 조건이 있다고 가정하고, 각 합 요소를 담은 List 컬렉션을 구현해보겠습니다.

public class CalculatorService {

    public double sum(List<Double> numbers) {
        double result = 0;
        for (double number : numbers) {
            if (number > Integer.MAX_VALUE) {   // ⚠ 검증 로직이 서비스 로직에 섞여 있음
                throw new IllegalArgumentException("int 범위를 초과했습니다.");
            }
            result += number;
        }
        return result;
    }
}

계산기는 여러 번 더할 수 있기 때문에 각 계산과정마다 위와 같이 유효성 검증 코드를 추가해야합니다. 그로 인해 여기저기 비즈니스 로직이 흩어지고, 이로인해 중복 코드가 발생할 수 있습니다.

SOLUTION

컬렉션을 클래스로 한 번 감싸 일급 컬렉션으로 만들어 이 문제를 해결해보겠습니다.

public class Calculators {

    private final List<Double> numbers = new ArrayList<>();

    public void add(double number) {
        validate(number);
        numbers.add(number);
    }

    public double sum() {
        return numbers.stream()
                .mapToDouble(Double::doubleValue)
                .sum();
    }

    private void validate(double number) {
        if (number > Integer.MAX_VALUE) {
            throw new IllegalArgumentException("int 범위를 초과했습니다.");
        }
    }
}

사용은 아래와 같이 합니다.

Calculators calculators = new Calculators();
calculators.add(100.0);
calculators.add(200.0);

System.out.println(calculators.sum());

여기저기 흩어져있는 비즈니스 로직이 일급컬렉션으로 응집되고, 중복 코드가 낮아지게 되었습니다. 또한 여러 역할과 책임을 일급 컬렉션에게 위임하여 좀 더 능동적인 객체로 만들 수 있습니다.

마무리

컬렉션과 일급 컬렉션의 기본적인 차이를 알아보았습니다!
일급 컬렉션을 사용하는게 확실히 책임 및 비즈니스 로직 분리 측면에서 좋은 방법같습니다.
저는 중복코드가 발생하지 않게 일급 컬렉션을 사용하는 법에 대해 집중해보았습니다.
아래 참고 사이트에서 이외에 일급 컬렉션의 장점을 알아가보면 좋을 것 같습니다.
감사합니다!

참고 사이트

기억보단 기록을 - 일급 컬렉션의 소개와 써야할 이유

profile
안녕하세요. 앞으로 나아가는 개발자, 전원준입니다.

0개의 댓글