JAVA

자바 컬렉션 — List·Set·Map, 데이터 담는 세 가지 그릇(COLLECTION)

PANCO_IT
#자바#JAVA#컬렉션#ArrayList#HashMap

List는 순서대로, Set은 중복 없이, Map은 키-값 짝으로 담는 세 가지 그릇 비교

담는 방식이 다른 세 가지 그릇입니다. 상황에 맞는 그릇을 고르는 것이 오늘의 목표입니다.

출석부 프로그램을 만든다고 해봅시다. 학생을 배열에 담으려니 첫 줄부터 막힙니다. new String[30] — 그런데 전학생이 와서 31명이 되면요? 배열은 크기를 한 번 정하면 끝이라 늘릴 수 없습니다. 더 큰 배열을 새로 만들어 옮겨 담는 수밖에 없죠. 매번 이 짓을 할 수는 없습니다. 자바가 준비해 둔 해결책이 오늘의 주제, 컬렉션입니다.

배열의 한계에서 출발합니다

JAVA2에서 배운 배열은 훌륭한 도구지만 두 가지가 불편합니다.

첫째, 방금 본 것처럼 크기가 고정입니다. 몇 개를 담을지 미리 알아야 하는데, 실전에서는 모르는 경우가 더 많습니다. 둘째, 중간을 지우면 빈칸이 남습니다. 3번 학생이 전학 가면 그 칸을 비워 두거나, 뒤의 학생들을 한 칸씩 직접 당겨 와야 합니다.

컬렉션(Collection)은 이 불편을 해결한 크기가 저절로 변하는 그릇입니다. java.util 패키지에 들어 있고, 담으면 늘어나고 지우면 알아서 당겨집니다.

배열과 컬렉션 비교 — 크기 고정 대 자동 증가

그림 1. 몇 개 담을지 모른다면 컬렉션이 답입니다.

한 가지 눈에 익은 문법이 보일 겁니다. 컬렉션은 전부 꺾쇠로 타입을 정합니다. ArrayList<String> — 지난 편에서 배운 제네릭이 바로 여기서 쓰입니다. 그릇은 하나인데 담을 재료를 쓸 때 정하는 것, 낯익은 구조죠. 그래서 기본형은 못 담고 int 대신 Integer 같은 래퍼 클래스를 쓴다는 규칙도 그대로 따라옵니다.

List — 순서대로 줄 세우는 그릇

가장 많이 쓰는 컬렉션입니다. 넣은 순서가 유지되고, 중복을 허용하며, 번호(인덱스)로 꺼냅니다. 배열과 성격이 가장 비슷해서 "늘어나는 배열"로 이해하면 됩니다. 대표 구현이 ArrayList입니다.

import java.util.ArrayList;

ArrayList<String> names = new ArrayList<>();
names.add("철수");            // 담기
names.add("영희");
names.add("철수");            // 중복도 그대로 들어감

System.out.println(names.get(0));     // 철수 (0번부터 셈)
System.out.println(names.size());     // 3
names.remove(0);                      // 0번 삭제 → 뒤가 알아서 당겨짐
System.out.println(names.get(0));     // 영희

배열과 문법을 비교해 보면, nums[0]get(0)으로, nums.lengthsize()로 바뀌었습니다. 대괄호 대신 메서드로 다룬다는 것이 컬렉션의 공통점입니다.

Set — 겹치면 안 되는 그릇

Set은 중복을 자동으로 걸러냅니다. 같은 값을 두 번 넣어도 한 번만 저장됩니다. 대신 순서를 기억하지 않습니다. 대표 구현이 HashSet입니다.

import java.util.HashSet;

HashSet<String> visitors = new HashSet<>();
visitors.add("철수");
visitors.add("영희");
visitors.add("철수");            // 이미 있음 → 조용히 무시

System.out.println(visitors.size());          // 2
System.out.println(visitors.contains("철수")); // true

"오늘 우리 매장에 다녀간 사람"을 세는 상황을 떠올려 보세요. 철수가 세 번 방문해도 방문자는 한 명입니다. 이렇게 "있냐 없냐"만 중요하고 몇 번째인지는 상관없을 때 Set을 씁니다. 번호로 꺼내는 get(0)이 아예 없다는 점도 특징입니다. 순서가 없으니 "0번째"라는 개념 자체가 없는 것이죠.

Map — 이름표를 붙여 담는 그릇

Map은 값 하나만 담지 않고 키(이름표)와 값을 짝으로 담습니다. 꺼낼 때는 번호가 아니라 이름표로 꺼냅니다. 대표 구현이 HashMap입니다.

import java.util.HashMap;

HashMap<String, Integer> scores = new HashMap<>();
scores.put("철수", 85);        // 키 "철수"에 값 85를 짝지어 담기
scores.put("영희", 92);

System.out.println(scores.get("영희"));   // 92 — 이름표로 바로 꺼냄
scores.put("철수", 90);                   // 같은 키에 다시 put → 덮어씀
System.out.println(scores.get("철수"));   // 90

전화번호부가 정확히 이 구조입니다. 이름(키)으로 번호(값)를 찾지, "37번째 연락처"로 찾지 않죠. 그리고 꺾쇠 안에 타입이 두 개인 것을 눈여겨보세요. HashMap<String, Integer> — 지난 편 마지막에 본 Couple<M, W>처럼 타입 자리가 두 개인 제네릭입니다. 키는 중복될 수 없어서, 같은 키로 다시 넣으면 새 값으로 덮어씁니다.

어느 그릇을 골라야 하나요?

세 가지를 배우고 나면 반드시 이 질문이 옵니다. 고르는 기준은 의외로 단순합니다.

상황그릇이유
순서가 중요하다, 번호로 꺼낸다List넣은 순서 유지, get(번호)
중복이 있으면 안 된다Set겹치는 값 자동 제거
이름표로 찾고 싶다Map키로 값을 바로 꺼냄

출석부처럼 순서가 필요하면 List, 방문자 집계처럼 중복만 거르면 Set, 성적표처럼 "누구의 점수"를 찾으면 Map입니다. 셋을 섞어 쓰는 경우도 흔합니다. 아래 실행 예제가 바로 그 상황입니다.

직접 실행해 보기

한 반의 출석 기록으로 세 그릇을 한 번에 써 봅니다. 마지막 줄에서 없는 이름표를 꺼내면 무엇이 나오는지 꼭 확인해 보세요.

출석부로 익히는 세 그릇
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;

public class Main {
  public static void main(String[] args) {
      // List: 찍힌 순서 그대로 (지각 확인용, 중복 OK)
      ArrayList<String> tapLog = new ArrayList<>();
      tapLog.add("철수"); tapLog.add("영희");
      tapLog.add("철수");                       // 두 번 찍음
      System.out.println("출입 기록: " + tapLog);

      // Set: 실제 출석한 사람 (중복 자동 제거)
      HashSet<String> attended = new HashSet<>(tapLog);
      System.out.println("출석 인원: " + attended.size() + "명");

      // Map: 이름 → 찍은 횟수
      HashMap<String, Integer> count = new HashMap<>();
      for (String name : tapLog) {
          Integer c = count.get(name);          // 처음이면 null
          count.put(name, c == null ? 1 : c + 1);
      }
      System.out.println("철수 횟수: " + count.get("철수"));

      System.out.println("민수 횟수: " + count.get("민수")); // 없는 키 → ?
  }
}

📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.

new HashSet<>(tapLog)처럼 List를 통째로 넣어 중복만 걸러내는 것도 실무에서 자주 쓰는 요령입니다.

단골 함정 — 없는 키를 꺼내면 null이 옵니다

방금 실행에서 봤듯, Map에서 없는 키를 get하면 오류가 나는 게 아니라 null이 조용히 돌아옵니다. 이 null을 모르고 계산에 쓰는 순간 NullPointerException이 터집니다. 1편에서 배운 그 예외가 여기서 가장 자주 나옵니다.

Integer c = count.get("민수");   // null
int total = c + 1;               // 실행 중 NullPointerException!

그래서 Map을 읽을 때는 위 예제처럼 null 확인을 먼저 하거나, count.getOrDefault("민수", 0)처럼 없을 때 쓸 기본값을 함께 주는 습관을 들입니다.

하나 더. Set과 Map은 출력 순서가 넣은 순서와 다를 수 있습니다. [철수, 영희]로 넣었는데 [영희, 철수]로 나와도 고장이 아닙니다. 애초에 순서를 기억하지 않는 그릇이니, 순서가 필요하면 처음부터 List를 골라야 합니다.

한 장 정리

항목ListSetMap
담는 방식순서대로중복 없이키-값 짝으로
중복허용자동 제거키는 불가(덮어씀)
꺼내기get(번호)contains(값)get(키)
대표 구현ArrayListHashSetHashMap
쓰는 상황출석부·목록방문자 집계성적표·전화번호부

이것만 기억하면 됩니다. 컬렉션은 크기가 저절로 변하는 그릇이고, 고르는 기준은 세 마디입니다. 순서가 필요하면 List, 중복을 거르려면 Set, 이름표로 찾으려면 Map. 그리고 Map의 get은 없는 키에 null을 돌려준다는 것, 이것이 오늘의 함정이었습니다.

미니 퀴즈

개념을 제대로 잡았는지 직접 풀어 봅니다. 정답은 눌러야 나옵니다.

미니 퀴즈

OX 2문제 + 4지선다 3문제


포켓코딩(JAVA3) 4편 · 다음 편: 래퍼 클래스와 날짜 — 기본형을 감싸고 시간을 다루기.

PANCO_IT

IT 지식을 정리하는 개발자

관련 글