배열은 크기가 고정돼서 답답했죠. 이번 장에서는 크기가 자유롭고 다루기 편한 자료구조 묶음(컬렉션)과, 담을 타입을 딱 고정해 주는 제네릭을 배워요. 실무에서 정말 자주 쓰는 핵심이에요.
컬렉션(Collection)은 "여러 개의 데이터를 담고 편하게 다루기 위한 표준 자료구조 묶음"이에요. 자바는 이런 자료구조들을 미리 잘 만들어서 컬렉션 프레임워크(Collection Framework)라는 이름으로 제공해요. 우리는 그걸 가져다 쓰기만 하면 됩니다.
왜 필요할까요? 이전에 배운 배열을 떠올려 봐요. 배열은 강력하지만 결정적인 한계가 있었어요.
int[] arr = new int[5]; 처럼 한 번 만들면 5칸에서 늘리거나 줄일 수 없어요. 데이터가 6개가 되면? 새 배열을 만들어 옮겨야 하죠.컬렉션은 성격에 따라 크게 세 종류로 나뉘어요. 이 셋만 알면 90%는 커버돼요.
List(리스트)는 데이터를 넣은 순서대로 저장하고, 같은 값이 여러 번 들어가는 것(중복)도 허용해요. 각 자리에는 인덱스(번호)가 0번부터 붙어서, 번호로 콕 집어 꺼낼 수 있어요. 대표 구현체가 바로 ArrayList예요.
import java.util.ArrayList;
import java.util.List;
public class ListExample {
public static void main(String[] args) {
// 문자열을 담는 List 생성 (제네릭은 곧 배웁니다)
List<String> fruits = new ArrayList<>();
// add : 뒤에 추가 (넣은 순서 유지)
fruits.add("사과");
fruits.add("바나나");
fruits.add("사과"); // 중복 허용! 같은 값이 또 들어감
// size : 개수 세기
System.out.println("개수: " + fruits.size()); // 3
// get(index) : 번호로 꺼내기 (0번부터)
System.out.println("첫 번째: " + fruits.get(0)); // 사과
System.out.println("두 번째: " + fruits.get(1)); // 바나나
// remove(index) : 번호로 삭제
fruits.remove(1); // 바나나 삭제
System.out.println("삭제 후 개수: " + fruits.size()); // 2
}
}
add(값) — 리스트 맨 뒤에 값을 추가해요.get(index) — index번째 값을 꺼내요. 0번부터 세는 것에 주의!size() — 지금 담긴 개수를 돌려줘요. (배열의 length와 비슷)remove(index) — index번째 값을 지워요. 뒤 요소는 자바가 알아서 당겨 줘요.
배열과 달리 size()는 괄호가 있는 메서드예요. 배열의 length는 괄호 없는 필드였죠. 헷갈리지 마세요!
add할 때마다 크기가 자동으로 늘어나요. 이게 컬렉션을 쓰는 가장 큰 이유예요. "몇 개가 들어올지 모를 때"는 거의 무조건 List가 편해요.Set(셋, 집합)은 같은 값을 두 번 담을 수 없어요. 이미 있는 값을 또 넣으려 하면 조용히 무시돼요. 그리고 순서를 보장하지 않아요 — 넣은 순서대로 나온다는 보장이 없죠. 대표 구현체는 HashSet이에요.
import java.util.HashSet;
import java.util.Set;
public class SetExample {
public static void main(String[] args) {
Set<String> names = new HashSet<>();
names.add("철수");
names.add("영희");
names.add("철수"); // 중복! → 무시됨
names.add("철수"); // 또 중복 → 무시됨
// 철수를 3번 넣었지만 실제로는 1개만 저장됨
System.out.println("총 인원: " + names.size()); // 2
// 이미 있는 값인지 확인
System.out.println(names.contains("영희")); // true
System.out.println(names.contains("민수")); // false
}
}
get(0) 같은 번호 접근이 불가능해요. "번호로 꺼내야 한다"면 List를, "중복만 없으면 된다"면 Set을 쓰세요.
LinkedHashSet이라는 형제도 있어요.)Map(맵)은 키(key)와 값(value)을 한 쌍으로 저장해요. 국어사전이 "단어 → 뜻"으로 되어 있듯, Map은 키로 값을 빠르게 찾아요. 여기서 키는 중복될 수 없어요(같은 키에 다시 넣으면 값이 덮어써져요). 대표 구현체는 HashMap이에요.
import java.util.HashMap;
import java.util.Map;
public class MapExample {
public static void main(String[] args) {
// 키:String(이름), 값:Integer(점수)
Map<String, Integer> scores = new HashMap<>();
// put(키, 값) : 저장
scores.put("철수", 90);
scores.put("영희", 85);
scores.put("철수", 100); // 같은 키! → 90이 100으로 덮어써짐
// get(키) : 키로 값 꺼내기
System.out.println("철수 점수: " + scores.get("철수")); // 100
System.out.println("영희 점수: " + scores.get("영희")); // 85
// containsKey(키) : 그 키가 있는지 확인
System.out.println(scores.containsKey("영희")); // true
System.out.println(scores.containsKey("민수")); // false
// 키가 없으면 get은 null을 돌려줘요
System.out.println(scores.get("민수")); // null
}
}
put(키, 값) — 키-값 쌍을 저장해요. 같은 키로 다시 put하면 값이 덮어써져요.get(키) — 키로 값을 꺼내요. 없는 키면 null을 돌려줘요.containsKey(키) — 그 키가 있는지 true/false로 알려줘요.
💡 "무언가를 이름표(키)로 찾고 싶다"면 Map을 떠올리세요. 학생 이름으로 점수 찾기, 상품 코드로 가격 찾기 — 전부 Map이 딱이에요.
<String>의 정체가 바로 이것! "타입 안전"의 핵심이에요.지금까지 List<String>, Map<String, Integer> 처럼 꺾쇠 < > 안에 타입을 적었죠? 이게 바로 제네릭(Generics)이에요. "이 컬렉션에는 이런 타입만 담겠다"고 미리 선언하는 거예요.
<T>. 나만의 제네릭 클래스·메서드를 직접 만들 때는, 타입 자리에 <T>(Type)·<E>(Element)·<K, V>(Key·Value) 같은 자리표시자(타입 파라미터)를 써요. 실제로 쓸 때 String처럼 구체적인 타입으로 채워집니다. 지금은 "타입을 나중에 정하는 빈칸" 정도로만 알아둬도 충분해요.// 제네릭 선언 : 이 리스트에는 String만 담는다!
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // ❌ 컴파일 에러! String이 아니라서 아예 못 넣음
String s = list.get(0); // 꺼낼 때 캐스팅 필요 없음! 자동으로 String
제네릭의 진짜 가치는 제네릭이 없을 때와 비교하면 확 와닿아요. 옛날 방식(제네릭 없이)에서는 아무 타입이나 담을 수 있어서 위험했어요.
// 🚫 제네릭이 없다면 (옛날 방식)
List list = new ArrayList(); // 타입 지정 안 함
list.add("hello");
list.add(123); // 문자열도, 숫자도 다 들어감 (섞임!)
// 꺼낼 때 Object로 나와서 직접 캐스팅해야 함
String s = (String) list.get(0); // 캐스팅 필요
String x = (String) list.get(1); // 💥 실행 중 에러! 123은 String이 아님
List<String>에 숫자를 넣으려 하면 실행하기도 전에 컴파일 에러가 나요. 버그를 미리 잡는 거죠.get()이 곧바로 그 타입으로 나와요. (String) 같은 형변환을 안 써도 돼요.
💡 이 둘을 합쳐 타입 안전(type safety)이라고 불러요. "엉뚱한 타입이 들어갈 걱정이 없고, 꺼낼 때도 안전하다"는 뜻이에요. 제네릭의 목적 = 타입 안전, 이 한 줄만 기억해도 돼요.
List<String> list = new ArrayList<>();List<String> — "String을 담는 List" 타입이에요.new ArrayList<>() — 오른쪽 꺾쇠는 비워도 돼요(다이아몬드 연산자). 왼쪽에서 String이라고 했으니 자바가 알아서 채워줘요.
Map은 키·값 두 개라서 Map<String, Integer>처럼 콤마로 두 타입을 적어요.
List<String> — String만 넣을 수 있고, 꺼내면 곧바로 String이에요.<타입>은 컬렉션에 담을 타입을 미리 고정해서, 잘못된 타입을 컴파일 때 차단하고 꺼낼 때 캐스팅을 없애줘요. 목적은 타입 안전!List<Integer>에 int를 넣을 수 있는 비밀이 여기 있어요.한 가지 규칙이 있어요. 컬렉션은 기본형(int, double, boolean 등)을 직접 담을 수 없어요. 오직 객체(참조형)만 담을 수 있죠. 그래서 기본형에는 각각 짝이 되는 래퍼 클래스(Wrapper Class)가 있어요.
int 등)을 객체로 감싼(포장한) 짝꿍 클래스예요(int → Integer). 컬렉션은 객체만 담을 수 있어서, 숫자를 담을 땐 이 래퍼가 필요해요. "wrap = 감싸다"에서 온 이름이에요.| 기본형 | 래퍼 클래스 |
|---|---|
int | Integer |
double | Double |
boolean | Boolean |
char | Character |
long | Long |
그래서 정수를 담을 때는 List<int>가 아니라 List<Integer>라고 써야 해요. 그런데 신기하게도, 여기에 그냥 int 값을 넣어도 잘 들어가요! 자바가 자동으로 변환해 주기 때문이에요.
import java.util.ArrayList;
import java.util.List;
public class BoxingExample {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
// 오토박싱: int(5) → Integer 로 자동 포장되어 저장됨
nums.add(5);
nums.add(10);
nums.add(3);
// 언박싱: Integer → int 로 자동 풀려서 계산됨
int first = nums.get(0); // Integer → int
int sum = nums.get(0) + nums.get(1); // 15
System.out.println("첫 값: " + first); // 5
System.out.println("합계: " + sum); // 15
}
}
int → Integer. (컬렉션에 넣을 때)Integer → int. (컬렉션에서 꺼내 계산할 때)
💡 "박스에 넣는다(박싱), 박스에서 꺼낸다(언박싱)"로 외우면 방향이 안 헷갈려요. 예전에는 Integer.valueOf(5)처럼 손수 변환했지만, 지금은 자바가 알아서 해 줘요(그래서 "오토").
int처럼 편하게 쓸 수 있어요. 겉보기엔 int, 속으로는 Integer인 셈이죠.| 구분 | List | Set | Map |
|---|---|---|---|
| 구조 | 값들의 나열 | 값들의 집합 | 키-값 쌍 |
| 순서 | 있음 (넣은 순서) | 보장 안 함 | 보장 안 함(HashMap) |
| 중복 | 허용 ✅ | 불가 ❌ | 키 불가 ❌ / 값 허용 ✅ |
| 인덱스 접근 | 가능 get(0) | 불가 | 키로 접근 get(키) |
| 대표 구현 | ArrayList | HashSet | HashMap |
| 이럴 때 | 순서·번호가 중요할 때 | 중복을 없앨 때 | 이름표로 값을 찾을 때 |
<타입>으로 못 박아 타입 안전하게! 이게 이번 장의 결론이에요.
get(0)). 대표 ArrayList. 메서드 add/get/size/remove.put/get/containsKey.<타입>: 담을 타입 고정 → 타입 안전(잘못된 타입 컴파일 차단 + 꺼낼 때 캐스팅 불필요).int↔Integer) 자동 변환. 컬렉션은 기본형을 직접 못 담아 래퍼 사용.