예전에는 자료구조를 이용할 때 같은 역할이나 목적이 같지만 구현체의 메서드명이 달라 어려움을 느꼈다.(Vector, Array, HashTable)
즉, 확장이 쉽지 않았으며 표준 인터페이스를 구현하지 않았다.
다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화된 방법을 제공하는 클래스의 집합. 간단히 생각하면 자료구조 모음이라고 생각하면 된다.
크게 Iterable을 상속한 인터페이스와 Map을 상속한 인터페이스로 나눌 수 있다. Iterable이 포함된 인터페이스는 List, Queue, Set이다.


출처 : Java Collection FrameWork(JCF)란?
Java 컬렉션 프레임워크에서 Iterable 과 Iterator 는 각각 “반복을 제공하는 객체”와 “실제 반복을 수행하는 도구”를 나타냅니다. 간단히 요약하면:
Iterable: “반복 가능한(순회할 수 있는)” 객체를 의미하며,Iterator<T> iterator() 메서드를 통해 반복자를 생성할 수 있는 인터페이스 Iterator: “실제 반복을 수행”하는 인터페이스이며,hasNext(), next(), remove() 등의 메서드를 통해 컬렉션 요소를 하나씩 순회합니다.아래에서 구체적으로 살펴보겠습니다.
Iterable 인터페이스정의
public interface Iterable<T> {
Iterator<T> iterator();
}
Iterable을 구현한 클래스(예: Collection 인터페이스를 구현한 대부분의 컬렉션)는 iterator() 메서드를 제공해야 합니다.for-each (향상된 for문) 구문이 가능해지려면 해당 클래스가 Iterable을 구현하고 있어야 합니다.역할
List, Set 등 대부분의 컬렉션 클래스가 Iterable을 구현하고 있습니다.Iterator 인터페이스정의
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() { ... }
...
}
Iterator는 실제로 컬렉션 요소를 하나씩 꺼내며 순회할 수 있도록 하는 메서드를 제공합니다.주요 메서드
hasNext(): 다음 요소가 존재하는지 확인 (존재하면 true, 없으면 false) next(): 다음 요소를 반환하고, 내부 포인터를 한 칸 이동 remove(): 현재 순회 중인 요소를 컬렉션에서 제거 (필요에 따라 사용)역할
Iterable로부터 획득한 반복자(Iterator) 객체를 통해 실제 컬렉션을 순회 while (iterator.hasNext()) { ... } 와 같은 방식으로 요소를 하나씩 가져올 수 있음| 구분 | Iterable | Iterator |
|---|---|---|
| 역할 | “반복 가능한 객체”임을 명시 | “실제 반복을 수행하는 객체” |
| 주요 메서드 | Iterator<T> iterator() | hasNext(), next(), (optional) remove() |
| 사용 패턴 | 컬렉션이 Iterable을 구현하면,for-each 구문 사용 가능 | 컬렉션을 순회할 때,while (it.hasNext()) { ... } 식으로 사용 |
| 예시 | Collection, List, Set, Map (내부의 keySet 등) | HashMap의 entrySet().iterator()ArrayList.iterator() 등 |
Iterable 은 “이 객체는 순회할 수 있다”는 선언과 함께, 구체적인 반복자(Iterator)를 생성하는 iterator() 메서드만 제공합니다. Iterator 는 “어떻게 순회할 것인가”에 대한 구체적인 메서드(hasNext(), next())를 가지며, 실제 순회를 제어합니다.Iterable 은 “반복자(Iterator)를 만들 수 있는” 객체를 표현하고, Iterator 는 “반복을 수행”하는 객체입니다.따라서 Iterable 을 구현한 컬렉션(예: ArrayList, HashSet 등)은 iterator()를 통해 Iterator 를 반환하고,
개발자는 반환된 Iterator 로 hasNext()와 next()를 호출하며 데이터를 하나씩 순회할 수 있게 됩니다.
Java에서 Collection과 Collections는 이름이 비슷하지만 전혀 다른 개념입니다.
Collection 인터페이스정의
Collection은 자바 컬렉션 프레임워크의 최상위 인터페이스 중 하나입니다. 주요 특징
List, Set, Queue 등 다양한 하위 인터페이스를 통해 구체적인 자료구조를 정의합니다. ArrayList, HashSet, LinkedList 등)는 Collection이나 그 하위 인터페이스를 구현합니다.Iterator iterator()와 같은 메서드를 통해 요소들을 순회할 수 있습니다.예시
// Collection 인터페이스를 직접 사용하기보다는
// 이를 구현한 구체 클래스(예: ArrayList)를 활용
Collection<String> collection = new ArrayList<>();
collection.add("Apple");
collection.add("Banana");
Collections 클래스정의
Collections는 java.util 패키지에 포함된 유틸리티(utility) 클래스입니다.주요 기능
Collections.sort(list) Collections.binarySearch(list, key) Collections.min(collection), Collections.max(collection) Collections.unmodifiableList(list), Collections.unmodifiableSet(set) Collections.synchronizedList(list), Collections.synchronizedMap(map) 예시
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 정렬
Collections.sort(list); // ["Apple", "Banana", "Cherry"]
// 반전
Collections.reverse(list); // ["Cherry", "Banana", "Apple"]
| 구분 | Collection | Collections |
|---|---|---|
| 역할 | 여러 요소를 모은 자료구조를 추상화하는 인터페이스 | 컬렉션 관련 유틸리티 메서드를 모아둔 클래스 |
| 형태 | 인터페이스 | 클래스 (final 아님) |
| 주요 기능 | 요소 추가/삭제, 반복(iterator) 등 구현체마다 다름 | 정렬, 검색, 동기화, 불변 컬렉션 생성 등 모두 정적 메서드로 제공 |
| 사용 예 | List, Set, Queue 등의 근간 | Collections.sort(…), Collections.shuffle(…) 등 |
Collection 은 “List, Set, Queue 등 모든 컬렉션 계열의 뿌리가 되는 추상화”이고, Collections 는 “이러한 컬렉션들을 편리하게 조작하기 위한 정적 메서드 모음 클래스”입니다.add(E element): 요소 추가get(int index): 특정 인덱스의 요소 반환set(int index, E element): 특정 인덱스의 요소 변경remove(int index): 특정 인덱스의 요소 제거size(): 리스트의 크기 반환contains(Object o): 특정 요소가 포함되어 있는지 확인indexOf(Object o): 특정 요소의 첫 번째 인덱스 반환내부적으로 배열을 사용하여 요소를 관리합니다.
특징:
인덱스를 기반으로 한 랜덤 접근 속도가 빠름.
연속적인 데이터(증간에 빈공간x)의 리스트
요소의 삽입 및 삭제는 느릴 수 있음(중간 요소가 빈 공간을 없애기 위해 이동해야 할 수도 있기 때문).
데이터 추가, 삭제시 내부에서 동적으로 배열 길이 조절함
내부적으로 Object[] 배열을 이용해 요소를 저장함
크기가 고정되어 있는 배열과 달리 데이터의 크기에 따라 공간을 늘리거나 줄임
1. 초기 용량
- new ArrayList<>()로 생성할 경우, 내부적으로 기본 용량(default capacity) 10짜리 배열을 생성합니다.(Java 버전에 따라 조금씩 다를 수 있으나, 일반적으로 10으로 알려져 있습니다.)
- 만약 생성자에 용량(capacity)을 지정했다면, 해당 용량의 배열을 생성합니다.
2. 요소 추가(ensureCapacity)- add() 메서드 등을 통해 새로운 요소를 추가하려고 할 때, 현재 배열이 가득 차 있지 않다면 그냥 배열에 요소를 저장하고 끝납니다.
- 현재 배열이 가득 차 있어 추가 공간이 필요한 경우, 내부적으로 ensureCapacity()라는 메서드를 호출하여 새 배열을 준비합니다.
3. ensureCapacity()- 새 용량(newCapacity) 계산 : newCapacity=oldCapacity+(oldCapacity>>1)
- 새 배열 할당 후 기존 배열 요소 복사 : Arrays.copyOf(...) 등의 메서드를 사용해 새 배열을 생성하고 기존 요소를 전부 복사합니다.
- 내부 배열 참조 교체 : 복사가 끝나면 내부적으로 참조하던 배열을 새로 생성된 배열로 교체합니다.
배열 공간이 꽉찰 때마다 배열을 복사하는 방식으로 늘리는데, 이때마다 지연
스레드 안전이 필요한 경우, Collections.synchronizedList()를 사용하여 동기화된 리스트로 변환해야 합니다.
사용 예 : 데이터의 삽입/삭제가 빈번하지 않고, 읽기 작업이 많은 경우.
Java의 Set 인터페이스는 “중복을 허용하지 않는” 자료구조를 정의합니다. 리스트(List)가 인덱스를 통해 순서를 보장하고 중복을 허용하는 것과 달리, 집합(Set)은 동일한 요소가 한 번만 존재해야 한다는 특징이 있습니다. 또한, 구현 클래스에 따라 정렬 순서나 삽입 순서가 유지되는지 여부가 달라집니다.
Set 인터페이스의 특징HashSet은 요소의 순서를 보장하지 않음 LinkedHashSet은 삽입 순서를 유지 TreeSet은 정렬 순서(오름차순 등)를 유지HashSetLinkedHashSetHashSet을 상속받아 동작하며, LinkedList 같은 이중 연결 구조를 추가로 이용해 삽입 순서를 유지.HashSet과 거의 유사하나, 연결 구조 관리 오버헤드가 약간 존재.TreeSetComparable 혹은 Comparator를 통해 정렬 기준을 사용.HashSet에서의 중복 검사hashCode())와 동등성 검사(equals())를 통해 중복을 판별합니다.hashCode() 값에 대응하는 해시 버킷(슬롯)에 저장합니다.hashCode()가 나온다면 충돌(collision) 이 발생할 수 있습니다.equals()로 실제 동등성을 비교하여 같은 객체인지 확인합니다.equals()가 true를 반환하면, 이미 존재하는 객체로 간주되어 추가되지 않습니다.즉,
hashCode()가 같고equals()도true이면 “동일 객체”로 판정하여 중복을 허용하지 않습니다.
TreeSet에서의 중복 검사TreeSet은 해시가 아닌 정렬 트리 구조를 사용하므로, compareTo() (또는 Comparator.compare()) 메서드를 통해 중복 여부를 판단합니다.LinkedHashSet에서의 중복 검사HashSet과 동일합니다. Map은 키(key)와 값(value)의 쌍으로 데이터를 저장하는 자료구조를 정의하는 인터페이스입니다. 키를 통해 값을 빠르게 조회할 수 있고, 키는 중복될 수 없으나, 값은 중복될 수 있습니다. (즉, 한 Map 안에서 동일한 키는 한 번만 존재)
키-값(key-value) 쌍으로 저장
map.put(key, value) 형태로 저장하며, map.get(key)를 통해 조회합니다.키는 중복 불가, 값은 중복 가능
Null 허용 여부
HashMap은 null 키와 null 값을 허용합니다. Hashtable은 null을 허용하지 않습니다.순서 보장 여부
HashMap은 순서를 보장하지 않음. LinkedHashMap은 삽입 순서를 보장. TreeMap은 정렬된 순서(오름차순 등)를 보장.Collection 인터페이스 상속받지 않음
HashMaphashCode())put 또는 get으로 입력되면, 우선적으로 키 객체의 hashCode()를 호출하여 해시값을 구합니다.HashMap 내부의 배열(bucket array)의 인덱스와 매핑하여, 해당 인덱스 위치(버킷)에 키-값 쌍(Entry) 을 저장하게 됩니다.HashMap은 내부적으로 배열 + 연결 구조(Java 8 이후엔 트리 구조 병합)로 이루어진 버킷을 사용합니다. (1) 연결 리스트 (Chaining)
next 포인터로 이어지는 연결 리스트 형태.(2) 트리(Tree) 구조
equals()로 최종 동등성 비교equals() 메서드를 통해 실제 동일 키인지를 판별합니다.equals()가 true를 반환하는 노드를 찾아내면 해당 키로 간주하고, put은 값을 수정하고, get은 값을 반환합니다.LinkedHashMapHashMap을 상속받은 클래스이며, 해시 테이블 외에 이중 연결 리스트를 활용하여 삽입 순서를 유지합니다.HashMap 대비 살짝 오버헤드가 있지만, 동일한 해시 성능 특성을 가집니다.TreeMapComparator를 사용해 사용자 정의 정렬도 가능.HashtableHashMap과 거의 동일한 해시 테이블 구조.ConcurrentHashMap 등)을 씁니다.ConcurrentHashMapHashtable 보다 성능이 뛰어남.hashCode() & equals() (해시 기반 Map인 HashMap / LinkedHashMap 등)
hashCode(), equals() 오버라이딩이 잘못되어 있으면, 정상적인 중복 판별이 안 될 수 있습니다.compareTo() or Comparator (TreeMap)
Comparable 구현 또는 Comparator 제공이 적절하게 이루어져야 합니다.null 처리
HashMap/LinkedHashMap 은 허용, TreeMap 은 키에 대해서 불허.스레드 안전성
HashMap, LinkedHashMap, TreeMap은 스레드 안전하지 않음. ConcurrentHashMap 혹은 Collections.synchronizedMap(new HashMap<>) 활용 고려.Map 인터페이스는 키-값 쌍을 효율적으로 저장하고 탐색하기 위한 자료구조입니다.HashMap: 가장 널리 쓰이며, 순서 보장 없음, 평균 접근 O(1).LinkedHashMap: 삽입 순서 유지, 해시 테이블 기반.TreeMap: 정렬된 순서 유지, 이진 검색 트리 기반, 접근 O((\log n)).Hashtable: 레거시 동기화 Map, 잘 쓰이지 않음.ConcurrentHashMap: 멀티스레드 안전 & 좋은 성능.각 구현체의 특징(순서, 정렬, 스레드 안전 등)과 null 키/값 허용 여부를 고려하여 상황에 맞게 선택하는 것이 중요합니다.
Java에서 스레드(Thread) 를 생성하는 대표적인 방법은 다음과 같습니다.
Thread 클래스 확장(상속)하기Thread 클래스를 상속받아 run() 메서드를 오버라이딩한다.start() 메서드를 호출하면, 내부적으로 run() 메서드가 실행된다.class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running via Thread subclass.");
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start(); // 별도의 스레드에서 run() 실행
}
}
Runnable 인터페이스 구현하기Runnable 인터페이스의 run() 메서드를 오버라이딩한다.Thread 생성자에 Runnable 구현 객체를 넘겨준다.start() 메서드를 호출한다.class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running via Runnable implementation.");
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
장점: 다른 클래스를 상속받아도 되므로, 클래스 상속 계층에 유연합니다.
=> Thread 클래스는 Runnable 인터페이스를 구현한 것이기 때문에 어느 것을 사용해도 거의 차이가 없다. 대신 Runnable 인터페이스를 구현하면 원하는 기능을 추가할 수도 있다. 이는 장점이 될 수도 있지만, 해당 클래스를 수행할 때 별도의 스레드 객체를 생성해야 한다는 점은 단점이 될 수도있다.
Callable 인터페이스 구현하기Callable<V> 인터페이스의 call() 메서드를 구현한다.Future<V> 객체로 결과를 받거나 예외를 처리할 수 있다.import java.util.concurrent.Callable;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 스레드에서 수행할 로직
return "Callable result";
}
}
장점:
Callable은 반환값을 가질 수 있고, 예외 처리도 가능하다는 점에서Runnable과 다릅니다.
ExecutorService 와 Executors 유틸 클래스(예: Executors.newFixedThreadPool(int)) 를 사용하여 스레드 풀을 쉽게 구성할 수 있습니다.ExecutorService executor = Executors.newFixedThreadPool(3); // 스레드 풀 3개
for (int i = 0; i < 10; i++) {
executor.submit(new MyRunnable()); // Runnable 작업 실행
}
executor.shutdown(); // 작업 종료 후 스레드 풀 종료
스레드 생성 비용 절감
시스템 자원 관리
작업(태스크) 구조화
코드 간결화 및 유지보수
ExecutorService, ScheduledExecutorService 등을 사용하면, 스레드 관리(생성/종료) 로직을 직접 작성하지 않아도 됩니다. Thread 클래스 상속 Runnable 인터페이스 구현 Callable 인터페이스 구현 후 스레드 풀에서 실행 ExecutorService 및 Executors 유틸 클래스를 통해 쉽게 구현 가능합니다.이처럼 Java에서 스레드를 직접 만들 수도 있지만, 실제 프로젝트에서는 대체로 스레드 풀을 통해 작업을 처리하는 방식을 선호합니다. 이는 성능과 자원 활용, 코드 유지보수 측면에서 훨씬 효율적이기 때문입니다.
Java 웹 애플리케이션 서버(예: Tomcat, Jetty)나 스프링 프레임워크 환경에서 스레드 풀 크기가 수백 개 이상으로 설정되는 것은 동시성(Concurrency) 이 많이 요구되기 때문입니다. 비록 스레드가 많아질수록 문맥 전환 비용(Context Switching) 이 커지지만, 다수의 요청을 처리해야 하는 서버 환경에서는 이 비용보다 동시 처리로 인한 이점이 더 크게 작용하는 경우가 많습니다. 주요 이유를 살펴보면 다음과 같습니다:
즉, I/O 대기 시간이 긴(Blocking I/O) 환경에서는 여러 스레드로 동시에 일을 시키는 것이 오히려 전체 처리량(Throughput)을 높입니다.
스레드 풀이 크면 컨텍스트 스위칭 비용이 증가하기는 하지만,
문맥 전환 비용을 감수하더라도 많은 스레드를 두는 편이 전체적인 응답 지연과 처리율 측면에서 유리한 경우가 많습니다. 결국 애플리케이션 특성과 트래픽 패턴을 파악하고, 자원(메모리, DB 커넥션 등) 한계를 고려하여 스레드 풀 크기를 조정하는 것이 핵심입니다.