Dev Study/Java

Java ArrayList와 HashMap 완벽 정리: VS Code 디버깅으로 보는 내부 동작

parkhh98 2026. 2. 9. 00:28

Java로 실무 비즈니스 로직을 다루다 보면, 데이터의 개수가 가변적인 상황을 필연적으로 마주하게 된다. 배열(Array)처럼 고정된 크기의 그릇으로는 사용자 수나 주문량이 폭증하는 동적인 환경에 유연하게 대처하기 어렵다.

오늘은 Java Collections Framework가 어떻게 이 '크기의 제약'을 우아하게 해결하는지, 그리고 실무에서 가장 빈번하게 쓰이는 ArrayListHashMap의 내부 동작 원리를 파헤쳐 본다.

 

Environment: Windows 11, JDK 21, VS Code, Antigravity


1. 고정된 크기가 불러오는 재앙 (Bad Code)

신입 개발자가 흔히 저지르는 실수 중 하나는 "학생 수는 대략 30명이니까 30개짜리 배열을 만들면 되겠지?"라고 단정 짓는 것이다. 하지만 비즈니스 요구사항은 언제나 변한다. 31번째 학생이 전학을 오는 순간, 코드는 소리 없이 멈춘다.

다음은 배열의 크기를 프로그래머가 임의로 지정했을 때 발생하는 치명적인 문제를 시각화한 코드다.

 

 

 

public class ClassRoom {
    public static void main(String[] args) {
        // Bad: 개발자의 '감'으로 크기를 고정함 (최악의 선택)
        String[] students = new String[2];
        
        students[0] = "Kim";
        students[1] = "Lee";
        
        // 상황 발생: 예상치 못한 3번째 학생 등장
        // 결과: ArrayIndexOutOfBoundsException 발생 후 프로그램 종료
        students[2] = "Park"; 
    }
}

 

ArrayIndexOutOfBoundsException 발생 화면

 

이 방식의 가장 큰 문제는 확장성(Scalability)이 없다는 점이다. 배열의 크기를 늘리려면 더 큰 배열을 새로 만들고, 기존 데이터를 일일이 복사하는 System.arraycopy 같은 저수준 로직을 직접 구현해야 한다.

이는 비즈니스 로직에 집중해야 할 개발자의 리소스를 낭비하게 만든다. 결국, 변화하는 데이터에 적응하지 못하는 코드는 도태된다.

Insight: 배열의 고정된 크기는 실무의 가변성을 담아내지 못한다. 확장이 필요한 곳엔 반드시 Collection을 사용해야 한다.

2. 유연한 그릇으로의 전환 (ArrayList & HashMap)

Java Collections Framework는 이 문제를 해결하기 위해 동적 배열(Dynamic Array)키-값 매핑(Key-Value Mapping)이라는 강력한 도구를 제공한다. 우리는 이제 데이터의 개수에 신경 쓸 필요 없이, 오직 '데이터 그 자체'에만 집중하면 된다.

도구적 해결책: VS Code Debugger로 내부 들여다보기

단순히 문법만 아는 것은 반쪽짜리 지식이다. VS Code의 디버거를 활용하면 ArrayList가 내부적으로 어떻게 데이터를 관리하는지 실시간으로 확인할 수 있다.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SmartClassRoom {
    public static void main(String[] args) {
        // Solution 1: 순서가 있고 중복을 허용하는 목록 (List)
        // Diamond Operator (<>)를 사용하여 타입 추론 활용
        List<String> studentList = new ArrayList<>();
        
        studentList.add("Kim"); // 크기 0 -> 1
        studentList.add("Lee"); // 크기 1 -> 2
        studentList.add("Park"); // 크기 2 -> 3 (자동 확장)
        
        // Solution 2: 빠른 조회를 위한 성적표 (Map)
        // Key(이름)는 중복 불가, Value(점수)는 중복 가능
        Map<String, Integer> scoreMap = new HashMap<>();
        
        scoreMap.put("Kim", 90);
        scoreMap.put("Lee", 85);
        
        // Debug Point: 여기서 변수 상태를 확인한다.
        System.out.println("반 전체 인원: " + studentList.size());
    }
}

 

[IMAGE: step2-vscode-debugger-variables.png | Alt: VS Code 디버거 변수 확인 화면 | 가이드: 좌측 Variables 탭에서 studentList 내부의 elementData 배열이 확장된 모습을 강조 (저장경로: images/2026-02-07-java-collections-arraylist-hashmap/)]

 

VS Code에서 중단점(Breakpoint)을 찍고 Variables 뷰를 확인해보면, studentList 내부에 elementData라는 객체 배열이 존재함을 알 수 있다.

우리는 add() 메서드만 호출했지만, 내부적으로는 배열 관리 로직이 캡슐화되어 동작하고 있는 것이다. 이것이 바로 추상화의 힘이다.

Insight: 데이터의 '순서'가 중요하다면 List를, '검색'이 중요하다면 Map을 선택하는 것이 설계의 핵심이다.

3. 내부 동작 원리와 성능 (Deep Dive)

"편리함에는 대가가 따른다"는 격언은 프로그래밍에서도 유효하다. ArrayList는 편리하지만, 그 내부 동작을 이해하지 못하면 뜻밖의 성능 저하를 겪을 수 있다.

Growing Strategy: 1.5배의 법칙

ArrayList는 기본적으로 10개의 공간(Capacity)을 가진다. 만약 이 공간이 가득 차면, 자바는 새로운 배열을 생성하는데, 이때 크기는 기존 크기의 1.5배로 늘어난다.

  • Old Capacity: 10
  • New Capacity: 10 + (10 >> 1) = 15

이 과정에서 Arrays.copyOf가 호출되며 기존 데이터를 모두 새 배열로 복사한다. 즉, 데이터 추가가 빈번하게 일어나는 로직에서 초기 크기를 너무 작게 잡으면, 배열 복사 비용(Overhead) 때문에 성능이 급격히 저하될 수 있다.

따라서 데이터의 대략적인 규모를 안다면, new ArrayList<>(100)과 같이 초기 용량을 지정해주는 것이 실무적인 최적화 팁이다.

Insight: 초기 용량(Capacity) 설정 하나만으로도 불필요한 배열 복사 비용을 획기적으로 줄일 수 있다.

FAQ

Q. ArrayList와 LinkedList, 무엇을 써야 하나?
데이터의 중간 삽입/삭제가 빈번하다면 LinkedList가 유리하다. 하지만 실무에서는 대부분 조회(Get)가 빈번하기 때문에 ArrayList를 압도적으로 많이 사용한다.

특징 ArrayList LinkedList
내부 구조 연속된 배열 연결된 노드
조회 속도 매우 빠름 (인덱스) 느림 (순차 탐색)
실무 비중 90% 이상 특수 목적

Q. Map의 Key로 사용자가 만든 객체를 쓸 수 있나?
가능하다. 단, 반드시 hashCode()equals() 메서드를 올바르게 오버라이딩해야 한다. 그렇지 않으면 논리적으로 같은 객체임에도 불구하고 다른 Key로 인식하여 데이터를 찾지 못하는 심각한 버그가 발생한다.


마무리

  • 배열의 고정 크기 제약은 유연한 비즈니스 로직 구현을 방해하므로, 상황에 맞는 Collection을 선택해야 한다.
  • 순서가 중요하면 ArrayList, 빠른 검색이 중요하면 HashMap을 사용하는 것이 표준이다.
  • 편리함 뒤에 숨겨진 배열 복사 비용을 인지하고, 필요 시 초기 용량을 설정하는 습관을 들이자.