Java List 초기화 가이드: 모범 사례, 흔한 오류 및 완전한 예제

1. Introduction

Java 프로그래밍에서 List는 가장 많이 사용되고 중요한 자료구조 중 하나입니다. List를 사용하면 여러 항목을 순서대로 저장하고 필요에 따라 추가, 삭제, 검색 등의 작업을 쉽게 수행할 수 있습니다.

하지만 List를 효과적으로 사용하려면 초기화 방법을 완전히 이해하는 것이 필수적입니다. 초기화가 올바르지 않으면 예상치 못한 오류나 버그가 발생하고 가독성 및 유지보수성에 큰 영향을 미칠 수 있습니다.

이 글에서는 “Java List 초기화”라는 주제에 초점을 맞추어 초보자도 이해하기 쉬운 기본 초기화 방법부터 실용적인 기법 및 흔히 발생하는 함정까지 모두 설명합니다. 또한 Java 버전별 차이점과 실제 코딩 상황에 기반한 모범 사례도 다룹니다.

Java를 이제 막 배우기 시작했든, 이미 List를 정기적으로 사용하고 있든, 다양한 초기화 패턴을 정리하고 복습할 좋은 기회가 될 것입니다.
마지막에는 흔히 묻는 질문과 문제 해결을 돕는 FAQ 섹션을 제공합니다.

2. Basic List Initialization Methods

Java에서 List를 사용하기 시작할 때 첫 번째 단계는 “빈 List”를 만드는 것으로, 즉 List를 초기화하는 것입니다. 여기서는 가장 일반적인 구현인 ArrayList를 사용한 기본 초기화 방법을 설명합니다.

2.1 Creating an Empty List with new ArrayList<>()

가장 흔히 사용되는 초기화 방법은 new ArrayList&lt;&gt;()이며, 다음과 같이 작성합니다:

List<String> list = new ArrayList<>();

이 코드는 요소가 전혀 없는 빈 List를 생성합니다.

핵심 포인트:

  • List는 인터페이스이므로 ArrayListLinkedList와 같은 구체 클래스를 인스턴스화합니다.
  • 유연성을 위해 변수 선언을 List 타입으로 하는 것이 일반적으로 권장됩니다.

2.2 Initializing with a Specified Initial Capacity

많은 데이터를 저장할 예정이거나 요소 개수를 이미 알고 있다면 초기 용량을 지정하면 효율성이 향상됩니다.

예시:

List<Integer> numbers = new ArrayList<>(100);

이 코드는 내부적으로 100개의 요소를 위한 공간을 미리 확보하여, 항목을 추가할 때 발생하는 리사이징 비용을 줄이고 성능을 개선합니다.

2.3 Initializing a LinkedList

필요에 따라 LinkedList를 사용할 수도 있습니다. 사용 방법은 거의 동일합니다:

List<String> linkedList = new LinkedList<>();

LinkedList는 요소가 자주 추가·삭제되는 상황에서 특히 효과적입니다.

Java에서는 new ArrayList&lt;&gt;() 또는 new LinkedList&lt;&gt;()를 사용해 빈 List를 쉽게 초기화할 수 있습니다.

3. Creating Lists with Initial Values

많은 경우 초기값을 이미 포함한 List를 만들고 싶을 때가 있습니다. 아래는 가장 흔히 쓰이는 초기화 패턴과 각각의 특징을 정리한 것입니다.

3.1 Using Arrays.asList()

Java에서 가장 많이 사용되는 메서드 중 하나가 Arrays.asList()입니다.

예시:

List<String> list = Arrays.asList("A", "B", "C");

이 코드는 초기값을 가진 List를 생성합니다.

중요 참고 사항:

  • 반환된 List는 고정 크기이며 길이를 변경할 수 없습니다. add()remove()를 호출하면 UnsupportedOperationException이 발생합니다.
  • 요소 교체(set())는 허용됩니다.

3.2 Using List.of() (Java 9+)

Java 9부터 List.of()를 사용하면 불변 List를 손쉽게 만들 수 있습니다:

List<String> list = List.of("A", "B", "C");

특징:

  • 완전 불변 List — add(), set(), remove() 모두 금지됩니다.
  • 가독성이 높으며 상수값을 정의할 때 이상적입니다.

3.4 Creating a Mutable List from Arrays.asList()

초기값을 가지고 시작하면서 이후에 수정도 가능한 List가 필요할 때 유용한 방법입니다:

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));

이 코드는 변경 가능한 List를 생성합니다.

  • add()remove()가 정상적으로 동작합니다.

3.4 Double‑Brace Initialization

익명 클래스를 활용하는 보다 고급 기술입니다:

List<String> list = new ArrayList<>() {{
    add("A");
    add("B");
    add("C");
}};

특징 및 경고:

  • 컴팩트한 코드를 생성하지만 익명 클래스를 도입하여 추가 오버헤드와 메모리 누수가 발생할 수 있습니다.
  • 빠른 데모나 테스트 코드에만 사용하세요; 프로덕션에서는 권장되지 않습니다.

이는 Java가 필요에 따라 초기값을 가진 List를 생성하는 다양한 방법을 제공한다는 것을 보여줍니다.

5. 비교 및 선택 기준

Java는 다양한 List 초기화 방법을 제공하며, 최적의 선택은 사용 사례에 따라 달라집니다. 이 섹션에서는 각 방법을 요약하고 언제 어떤 방법을 선택해야 하는지 설명합니다.

5.1 가변 리스트 vs 불변 리스트

  • 가변 리스트
  • 요소를 추가, 제거 또는 수정할 수 있습니다.
  • Examples: new ArrayList<>() , new ArrayList<>(Arrays.asList(...))
  • 동적 작업이나 루프에서 항목을 추가할 때 가장 적합합니다.
  • 불변 리스트
  • 추가, 삭제, 수정이 불가능합니다.
  • Examples: List.of(...) , Collections.singletonList(...) , Collections.nCopies(...)
  • 상수나 안전한 값 전달에 이상적입니다.

5.2 일반 메서드 비교 표

MethodMutabilityJava VersionCharacteristics / Use Cases
new ArrayList<>()MutableAll VersionsEmpty List; add elements freely
Arrays.asList(...)Fixed SizeAll VersionsHas initial values but size cannot change
new ArrayList<>(Arrays.asList(...))MutableAll VersionsInitial values + fully mutable; widely used
List.of(...)ImmutableJava 9+Clean immutable List; no modifications allowed
Collections.singletonList(...)ImmutableAll VersionsImmutable List with a single value
Collections.nCopies(n, obj)ImmutableAll VersionsInitialize with n identical values; useful for testing
Stream.generate(...).limit(n)MutableJava 8+Flexible pattern generation; good for random or sequential data

5.3 사용 사례별 권장 초기화 패턴

  • 빈 List만 필요할 때
  • new ArrayList<>()
  • 초기값이 필요하고 나중에 수정하고 싶을 때
  • new ArrayList<>(Arrays.asList(...))
  • 수정 없이 상수로 사용할 때
  • List.of(...) (Java 9+)
  • Collections.singletonList(...)
  • 동일한 값을 고정 개수만큼 원할 때
  • Collections.nCopies(n, value)
  • 값을 동적으로 생성해야 할 때
  • Stream.generate(...).limit(n).collect(Collectors.toList())

5.4 중요한 참고 사항

  • 불변 또는 고정 크기 List를 수정하려고 하면 예외가 발생합니다.
  • 필요한 가변성 및 Java 버전에 가장 적합한 방법을 선택하세요.

올바른 초기화 방법을 선택하면 예기치 않은 버그를 방지하고 가독성과 안전성을 향상시킵니다.

6. 일반 오류 및 해결 방법

Java에서 List를 초기화하거나 사용할 때 자주 발생하는 오류가 있습니다. 여기 일반적인 예시와 해결책을 제시합니다.

6.1 UnsupportedOperationException

일반적인 상황:

  • Arrays.asList(...) 로 만든 List에 add() 또는 remove() 호출
  • List.of(...), Collections.singletonList(...), Collections.nCopies(...) 로 만든 List를 수정

예시:

List<String> list = Arrays.asList("A", "B", "C");
list.add("D"); // Throws UnsupportedOperationException

원인:

  • 이러한 메서드들은 크기를 변경할 수 없거나 완전히 불변인 List를 생성합니다.

해결책:

  • 가변 List로 감싸기: new ArrayList<>(Arrays.asList(...))

6.2 NullPointerException

일반적인 상황:

  • 초기화되지 않은 List에 접근

예시:

List<String> list = null;
list.add("A"); // NullPointerException

원인:

  • null 참조에 메서드를 호출했습니다.

해결책:

  • 사용하기 전에 항상 초기화하세요: List<String> list = new ArrayList<>();

6.3 타입 관련 문제

  • 제네릭 없이 List를 생성하면 런타임 타입 오류 위험이 증가합니다.

예시:

List list = Arrays.asList("A", "B", "C");
Integer i = (Integer) list.get(0); // ClassCastException

해결책:

  • 가능한 경우 항상 제네릭을 사용하세요.

이러한 일반적인 오류를 이해하면 List를 초기화하거나 사용할 때 문제를 피할 수 있습니다.

7. 요약

이 문서는 Java에서 다양한 List 초기화 방법과 적절한 선택 방법을 설명했습니다.

다음 내용을 다루었습니다:

  • 기본 빈 List 생성 new ArrayList<>()new LinkedList<>() 사용
  • 초기값이 있는 List Arrays.asList(), List.of(), new ArrayList<>(Arrays.asList(...)) 사용
  • 특수 초기화 패턴 Collections.singletonList(), Collections.nCopies(), Stream.generate()
  • 가변 List와 불변 List의 주요 차이점
  • 일반적인 함정 및 오류 처리

List 초기화는 간단해 보이지만, 이러한 변형들을 이해하고 올바른 방법을 선택하는 것이 안전하고 효율적인 코딩에 필수적입니다.

8. FAQ (자주 묻는 질문)

Q1: Arrays.asList() 로 만든 List에 요소를 추가할 수 있나요?
A1: 아니요. Arrays.asList()는 고정 크기의 List를 반환합니다. add()remove()를 호출하면 UnsupportedOperationException이 발생합니다. 가변 List가 필요하면 new ArrayList&lt;&gt;(Arrays.asList(...))를 사용하세요.

Q2: List.of()Arrays.asList()의 차이점은 무엇인가요?

  • List.of() (Java 9+) → 완전 불변; set()조차 허용되지 않음.
  • Arrays.asList() → 고정 크기이지만 set()은 허용됨.

Q3: Double-Brace 초기화를 사용해야 할까요?
A3: 권장되지 않습니다. 익명 클래스를 생성해 메모리 누수가 발생할 수 있기 때문입니다. 대신 표준 초기화를 사용하세요.

Q4: 초기 용량을 지정하면 어떤 이점이 있나요?
A4: 많은 요소를 추가할 때 내부 리사이징을 줄여 성능을 향상시킵니다.

Q5: List를 초기화할 때 항상 제네릭을 사용해야 할까요?
A5: 반드시 그렇습니다. 제네릭을 사용하면 타입 안전성이 향상되고 런타임 오류를 방지할 수 있습니다.

Q6: List를 초기화하지 않고 사용하면 어떻게 되나요?
A6: 어떤 메서드를 호출하더라도 NullPointerException이 발생합니다. 항상 먼저 초기화하세요.

Q7: List 초기화에 버전 차이가 있나요?
A7: 있습니다. List.of()는 Java 9 이상에서만 사용할 수 있습니다.