배열은 여러 데이터를 한번에 저장하고 처리하기 위한 자료구조이다. 하지만 배열의 크기는 한 번 지정하면 고정되어 크기를 변경할 수 없고 데이터 삽입/삭제 시 빈 자리를 비워두거나 뒤에 있는 데이터들의 위치를 모두 바꿔야 한다.
이 때 가변 크기를 가지며 자동으로 위치를 옮겨주는 컬렉션을 사용하면 이러한 문제들이 해소된다. 컬렉션은 배열의 고정 크기 단점을 극복할 수 있도록 객체를 쉽게 삽입, 삭제, 검색할 수 있는 가변 크기의 컨테이너이다.
java.util 패키지에서 다양한 컬렉션 인터페이스와 컬렉션 클래스를 제공한다. 구조는 다음과 같다.

모든 컬렉션 클래스들은 Collection<E>를 상속받는다. Vector<E>와 ArrayList<E>는 가변 크기 배열, LinkedList<E>는 링크드 리스트, Stack<E>은 스택, HashSet<E>은 집합을 구현하며 HashMap<k, V>은 키와 값의 쌍으로 이루어진 데이터를 저장하는 컬렉션이다.
컬렉션은 제네릭 기법으로 만들어진다.
제네릭은 <E>, <K>, <V>와 같은 타입 매개변수를 가지며 이 매개변수에 구체적인 타입을 지정하여 사용한다. 이렇게 타입 매개변수를 통해 컬렉션을 일반화하면 다양한 타입을 다룰 수 있다.
컬렉션의 요소들은 객체만 올 수 있으며 int, char, double과 같은 기본 타입은 불가하다. 따라서 다음과 같이 wrapper 클래스를 사용하도록 한다.
Vector<int> v = new Vector<int>(); //컴파일 오류. 기본 타입 불가
Vector<Integer> v = new Vector<Integer>(); //객체만 가능
제네릭은 클래스나 메소드 선언 시 타입 매개변수를 두어 모든 종류의 타입을 다룰 수 있도록 하는 것이다. C++의 템플릿과 같이 일반화시킨 틀을 만드는 것이라고 생각할 수 있다. 다음 예시를 보자.
class Stack<E>{
void push(E element){
...
}
E pop(){
...
}
}
일반화한 타입 E를 두고, Stack<Integer>와 같이 E에 구체적인 타입을 지정하면 구체화된 스택이 된다.

컬렉션 클래스에서 타입 매개변수로는 보통 하나의 대문자를 사용한다.
👉🏻타입 매개변수에 최상위 클래스인 Object 클래스를 지정하면 문자열, 상수, 사용자 정의 클래스 등 모든 객체에 대해 업캐스팅 할 수 있다.
Vector<E>는 배열을 가변 크기로 다루며 객체의 삽입, 삭제, 이동이 쉽다. 벡터는 삽입되는 요소의 개수에 따라 자동으로 크기를 조절하며 요소의 삽입, 삭제에 따라 자동으로 자리가 이동된다.
다음과 같이 벡터를 생성한다. 레퍼런스 선언과 벡터 생성을 분리할 수 있으며, 선언과 동시에 생성할 수도 있다.
Vector<Integer> v1;
v1 = new Vector<Integer>();
//또는
Vector<Double> v2 = new Vector<Double>();
컬렉션은 자신의 용량을 스스로 조절하므로 용량을 알거나 설정할 필요가 없지만, 초기에 벡터의 용량을 설정하려는 경우 다음과 같이 생성자에 초기화 인자값을 전달하면 된다.
Vector<Double> v = new Vector<Double>(10);
벡터 컬렉션의 주요 메소드는 다음과 같다.

삽입을 위한 add(), 반환을 위한 get(), 삭제를 위한 remove(), 크기 측정을 위한 size(), capacity()등이 있다.
add() 메소드를 통해 벡터에 요소를 삽입할 수 있는데, 기본 타입을 객체로 변환하여 삽입하거나 자동 박싱을 통해 삽입할 수 있다.
Vector<Integer> v = new Vector<Integer>();
v.add(Integer.valueOf(1));
v.add(Integer.valueOf(2));
v.add(Integer.valueOf(3));
v.add(5); //자동 언박싱
v.add(6); //자동 언박싱
벡터의 중간에 위치를 지정하여 객체를 삽입하는 것도 가능하다. 인덱스 위치에 정수를 삽입하고 해당 위치에 있던 기존의 요소와 그 뒤의 요소들은 뒤로 한 자리씩 이동된다.
v.add(3, 4); //index 3에 정수 4를 삽입
벡터에 null을 삽입할 수 있다.
v.add(null);
System.out.print(v);
[1, 2, 3, 4, 5, 6, null]
벡터 내에의 요소를 알아내기 위해서 get()이나 elementAt() 메소드를 사용한다.
Integer obj = v.get(0); //벡터의 0번째 객체 반환
int i = obj.intValue(); //객체 obj의 정수 반환
System.out.print(i);
1
자동 언박싱되므로 다음과 같이 간단하게 작성할 수 있다.
int i = v.get(0);
remove() 메소드를 이용해 특정 인덱스의 요소를 삭제할 수 있다.
v.remove(6); //index 6의 요소 삭제
해당 요소가 삭제되면 삭제된 요소의 뒤에 있는 요소들은 한 자리씩 앞으로 이동한다.
벡터 내의 모든 요소를 삭제하려는 경우 removeAllElements() 메소드를 호출한다.
v.removeAllElements();
[1, 2, 3, 4, 5, 6, null]
[1, 2, 3, 4, 5, 6]
[]
벡터에 들어있는 요소의 개수를 알기 위해 size() 메소드를 사용한다.
int len = v.size() //벡터의 크기 반환
벡터의 용량은 capacity() 메소드를 통해 얻을 수 있다.
int cap = v.capacity();
다음 그림을 통해 전체 과정과 구조를 이해할 수 있다.


ArrayList는 문자열만을 다루는 제네릭 컬렉션이다. 다음과 같이 생성한다.
ArrayList<String> a = new ArrayList<String>();
add()를 사용해 요소를 삽입한다.
a.add("Nice");
a.add("to");
a.add("meet");
a.add("you");
System.out.print(a);
[Nice, to, meet, you]
ArrayList에 String이 아닌 다른 값이나 객체를 삽입하면 오류가 발생한다. ArrayList에도 null을 삽입할 수 있다.
ArrayList의 특정 인덱스 위치에 요소를 삽입할 수 있다.
a.add(4, "!");
System.out.print(a);
[Nice, to, meet, you, !]
get()과 elementAt() 메소드를 통해 ArrayList내의 요소를 알아낼 수 있다.
String s = a.get(0);
System.out.print(s);
Nice
remove() 메소드를 이용해 ArrayList 특정 위치의 요소를 삭제할 수 있다. ArrayList의 모든 요소를 삭제하려면 clear() 메소드를 호출한다.
size() 메소드를 호출하여 현재 ArrayList에 들어있는 요소의 개수를 알 수 있다. 현재의 용량을 리턴하는 메소드는 없다.
다음 그림을 통해 전체 과정과 구조를 이해할 수 있다.

