개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

기초개념 › Java

List와 Map

List와 Map은 무엇을 담고, 어떻게 꺼내는가

값을 여러 개 담아 두는 그릇을 컬렉션이라고 부릅니다. List는 값을 넣은 순서대로 줄 세워 담고, 0부터 시작하는 번호로 꺼냅니다. Map은 키와 값을 짝지어 담고, 키로 꺼냅니다. 코드에서는 List<String> names = new ArrayList<>();처럼 왼쪽에 그릇의 종류(꺾쇠 안은 담을 값의 종류)를 적고, 오른쪽의 new로 빈 그릇을 만듭니다. 괄호에 크기를 적지 않아도 add나 put으로 담는 만큼 담긴 개수가 늘어납니다. 이 실습은 List와 Map을 바꿔 가며 같은 모양의 코드를 컴파일하고 실행해, 있는 것을 꺼낼 때, 없는 것을 꺼낼 때, 같은 것을 한 번 더 넣을 때 무엇이 출력되는지를 봅니다. (담긴 개수를 먼저 출력하는 것은, 꺼내다 멈춰도 그 개수를 볼 수 있게 하려는 것입니다.)

실행으로 대조 · JDK 21에서 여섯 가지 조합을 javax.tools로 컴파일하고 자식 java 프로세스로 실행해, 표준 출력·표준 오류·종료 코드를 대조

이 실습에 나오는 개념

List, Set, Map
자바 컬렉션의 세 가지 기본 유형이다. List는 순서가 있고 중복을 허용하며 인덱스로 접근한다. Set은 중복을 허용하지 않는다. Map은 키와 값의 쌍을 저장하며 키는 중복될 수 없다. 데이터의 성격(순서 필요 여부, 중복 허용 여부, 키로 찾는지)에 따라 고른다.
List의 add와 get
List는 값을 순서대로 줄 세워 담는 그릇이고, 담긴 값은 0부터 시작하는 정수 번호(인덱스)로 가리킨다. add(값)은 List의 맨 뒤에 값을 붙이고, get(번호)은 그 번호 자리의 값을 돌려준다. List는 같은 값을 여러 번 담을 수 있어서 같은 값을 add하면 번호만 다른 자리에 하나 더 담긴다. size()는 담긴 값의 개수를 돌려주며, 쓸 수 있는 번호는 0부터 size() - 1까지이다. ArrayList는 List 규칙대로 만든 클래스로, 담을수록 안에서 쓰는 배열의 크기를 스스로 늘리므로 만들 때 크기를 정하지 않아도 된다.
Map의 put과 get
Map은 키와 값을 짝지어 담는 그릇이고, 값은 번호가 아니라 키로 꺼낸다. 하나의 Map에는 같은 키가 둘 있을 수 없고, 키 하나는 값 하나와 짝지어진다. put(키, 값)은 키와 값을 짝지어 담는데, 그 키가 이미 담겨 있으면 새 짝을 만들지 않고 이전 값을 새 값으로 바꾸며 이전 값을 돌려준다. get(키)는 그 키와 짝지어진 값을 돌려주고, 짝지어진 값이 없으면 예외 없이 null을 돌려준다. 값으로 null을 담을 수도 있는 Map에서는 get이 null을 돌려주어도 그 키가 없다는 뜻이 아닐 수 있고, 키가 있는지는 containsKey로 따로 확인한다. HashMap은 Map 규칙대로 만든 클래스이며 꺼내는 순서를 보장하지 않는다.
IndexOutOfBoundsException
IndexOutOfBoundsException은 배열, 문자열, List처럼 번호로 가리키는 것에서 그 번호가 범위를 벗어났을 때 던져지는 예외이다. List의 get(번호)은 번호가 0보다 작거나 담긴 개수 이상이면 이 예외를 던진다. RuntimeException의 하위 클래스이다. 번호가 틀린 코드도 컴파일은 통과하고, 그 줄이 실행될 때 예외가 던져진다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. java Main으로 실행합니다. main의 첫 줄 List<String> names = new ArrayList<>(); 가 실행됩니다. 왼쪽의 List<String>은 "문자열(String)을 담는 List"라는 뜻으로, names 변수가 어떤 그릇을 가리키는지 정한 것입니다. 오른쪽의 new ArrayList<>()가 담긴 것이 하나도 없는 빈 List를 만들고, names가 그 List를 가리킵니다. List는 담는 규칙의 이름이고, ArrayList는 그 규칙대로 만든 실제 클래스입니다. 괄호 안에 크기는 적지 않았습니다. 담긴 개수는 0입니다.
  2. names.add("kim"); 줄이 실행됩니다. add는 List의 맨 뒤에 값을 붙입니다. 비어 있었으므로 "kim"은 번호 0 자리에 들어갑니다. 번호는 0부터 시작해, 담는 순서대로 매겨집니다. 담긴 개수는 1입니다.
  3. names.add("lee"); 줄이 실행됩니다. add는 List의 맨 뒤에 값을 붙입니다. "lee"는 "kim" 다음인 번호 1 자리에 들어갑니다. 담긴 개수는 2입니다.
  4. System.out.println(names.size()); 줄이 담긴 값의 개수를 출력합니다. 출력된 개수는 2입니다.
  5. System.out.println(names.get(0)); 줄에서 get(0) 호출이 번호 0 자리의 값을 돌려주고, println이 "kim"을 출력합니다. 꺼낼 번호는 괄호 안에 적은 값 0입니다. 꺼내도 List에서 값이 없어지지는 않아서, 담긴 개수는 그대로 2입니다.
  6. main의 마지막 줄까지 실행되어 프로그램이 끝납니다.

실행 결과

코드

// 컬렉션 클래스들이 들어 있는 java.util을 가져다 쓴다
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // List: 넣은 순서대로 줄 세워 담는 그릇. 처음에는 비어 있다
        List<String> names = new ArrayList<>();
        // 담기: add는 맨 뒤에 붙인다. 번호는 0부터 매겨진다
        names.add("kim");
        names.add("lee");
        // 개수: size는 담긴 값의 개수를 돌려준다
        System.out.println(names.size());
        // 꺼내기: get은 그 번호 자리의 값을 돌려준다
        System.out.println(names.get(0));
    }
}

이 모형의 전제

컴파일러와 실행 환경은 JDK 21.0.12.1(javac 21)입니다. 여섯 가지 조합을 모두, 화면의 코드와 같은 소스(줄 단위로 같은 글)를 javax.tools로 컴파일해 모두 컴파일되는 것을 확인했고, 컴파일된 Main을 이 JDK의 java를 자식 프로세스로 띄워 실제로 실행해 표준 출력, 표준 오류, 종료 코드를 잡은 결과와 대조했습니다. 대조한 것은 컴파일 성공 여부, 표준 출력의 줄들(담긴 개수와 꺼낸 값), 종료 코드와 표준 오류의 첫 줄(예외가 난 조합은 종료 코드 1에 첫 줄이 Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 5 out of bounds for length 2였고 표준 오류에 Main.main이 있는 스택 추적이 이어졌으며, 나머지는 종료 코드 0에 표준 오류가 비어 있었습니다)입니다. 단계 문장이 기대는 사실은 별도의 소스를 같은 방식으로 실행해 확인했습니다. List는 "kim"과 "lee"가 번호 0과 1 자리에 담기고(indexOf 결과 0, 1), get으로 꺼내도 담긴 개수가 그대로 2이며, 같은 "kim"을 또 담으면 번호 2 자리에 따로 담깁니다(get(2)가 kim, 개수 3). Map은 같은 키로 put하면 이전 값 20을 돌려주고 값이 25로 바뀌며 짝은 2개 그대로이고, 담지 않은 키 park는 get이 null이고 containsKey가 false입니다. 문서는 Java SE 21 API 문서를 직접 열어 확인했습니다. List에서 번호가 0부터 시작하고 같은 값을 여러 번 담을 수 있다는 것, add가 맨 뒤에 붙이고 get이 범위를 벗어난 번호에 IndexOutOfBoundsException을 던진다는 것은 java.util.List와 java.util.ArrayList를, ArrayList가 담을수록 용량을 스스로 늘린다는 것은 java.util.ArrayList를, Map에 같은 키가 둘 있을 수 없다는 것과 get이 짝이 없으면 null을 돌려준다는 것과 put이 이전 값을 바꾸고 돌려준다는 것은 java.util.Map과 java.util.HashMap을, IndexOutOfBoundsException이 RuntimeException의 하위 클래스라는 것은 java.lang.IndexOutOfBoundsException을 따랐고, 배열의 길이가 만든 뒤 바뀌지 않는다는 것은 Java Language Specification(Java SE 21) 10.2절을 따랐습니다. 이 문서들은 내용을 요약해 돌려주는 방식으로 열었으므로 원문 전체를 눈으로 읽은 것은 아닙니다. 번호가 틀려도 컴파일이 통과하고 실행할 때 예외가 난다는 것, 종료 코드 1과 표준 오류에 나온 글은 문서가 아니라 이 java에서 관찰한 것입니다. 화면의 코드는 java Main으로 실행하는 모양 그대로이고 패키지는 없습니다. 담긴 개수를 먼저 출력하고 꺼내는 줄을 뒤에 둔 것은, 꺼내다 멈춘 조합에서도 담긴 개수를 출력으로 확인하려는 배치입니다. 그림의 점선 칸(번호 5, 키 park)은 담겨 있지 않은 자리를 가리키려고 그린 것이고, 실제 List나 Map에 그런 칸이 있다는 뜻이 아닙니다. List와 Map의 이 밖의 메서드(remove, contains 등), 반복해서 훑는 방법, Set, 꺼내는 순서가 정해지는 방식, null을 값으로 담는 경우는 다루지 않았습니다. ArrayList와 LinkedList의 차이는 "세부 동작 실습: ArrayList와 LinkedList", HashMap이 키를 찾는 방식은 "세부 동작 실습: HashMap 내부", Set과 Map의 종류별 순서는 "세부 동작 실습: Set과 Map의 종류"에서 다룹니다.

앞 실습: 제네릭 · 다음 실습: 람다

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요