먼저 소개드릴 컬렉션 프레임워크는 순서가 있고, 중복을 허용하는 List 인터페이스를 상속한 클래스들입니다. 소개드릴 컬렉션들에서 사용할 수 있는 메소드들은 지난 포스트에서 모두 정리했기 때문에 여기서는 각 컬렉션에 대한 설명과 기본적인 사용에 대해서만 다룰 예정입니다.
ArrayList
는 가장 많이 사용되는 컬렉션 프레임워크입니다. ArrayList
는 객체를 배열의 형태처럼 순서를 매겨서 저장하게 됩니다. 배열과는 다르게 크기가 가변적이라는 점입니다.
데이터를 추가하면 인덱스 0번부터 순서대로 저장됩니다. 만약 특정 위치에 데이터를 삽입하면 인덱스가 1씩 밀리면서 삽입되고 삭제도 동일한 원리로 수행됩니다.
저장 과정에서 내부적으로는 배열을 선언하고 저장하는 방식으로 이루어져있습니다. 따라서 삽입 과정에서 배열이 가득차 있다면 새 배열을 생성하고 기존 내용을 복사하는 방식으로 저장이 됩니다.(삭제도 마찬가지)
따라서 빈번하게 객체가 삽입/삭제가 이루어지는 경우 ArrayList
는 다소 효율이 떨어지기에 잠시뒤에 소개드릴 LinkedList
를 사용하는 것을 추천드립니다.
ArrayList
는 2가지 방법으로 선언할 수 있습니다. 선언 방법에 따라 저장할 수 있는 객체가 달라집니다.
ArrayList<E> 변수명 = new ArrayList<E>(); //지정된 타입 E의 객체만 저장 가능
ArrayList 변수명 = new ArrayList(); //모든 타입의 객체를 저장가능
제네릭에서 다뤘던 것 처럼 생성자의 타입 파라미터와 선언 타입 파라미터가 동일하다면 생성자 타입 파라미터를 생략할 수 있습니다. 또한 타입 파라미터로 객체를 전달할 수도 있는데, 그에 대한 자세한 내용은 제네릭 포스트를 참조해주세요.
ArrayList<E> 변수명 = new ArrayList<E>(); ArrayList<E> 변수명 = new ArrayList<>(); //둘 다 같은 코드. E 객체 타입만 리스트에 저장 가능
가장 기본적인 동작인 삽입, 삭제, 검색
를 구현한 코드입니다.
public class Main {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>(); //String타입 ArrayList 생성
al.add("가"); //ArrayList에 요소 추가
al.add("나");
al.add("다");
System.out.println("ArrayList 크기: " + al.size()); //ArrayList 크기 반환
al.remove(2); //인덱스 지정 요소 삭제
System.out.println("ArrayList 크기: " + al.size());
System.out.println("\"나\"의 위치: " + al.indexOf("나")); //"나"라는 요소 검색
}
}
요소 삭제의 remove()
는 인덱스를 전달하면 해당 인덱스의 위치를 지우고, 객체를 전달하면 해당 객체와 일치하는 객체를 지웁니다.
위 코드의 내부 동작을 그림으로 표현하면 다음과 같습니다.
요소 추가요소 삭제요소 검색
Vector
는 ArrayList
가 추가되기 전에 사용되던 컬렉션입니다. ArrayList
와의 차이점은 Vector
는 자동으로 동기화된 메소드를 지원하기 때문에 멀티 스레드에 대처할 수 있다는 점입니다.
하지만 멀티 스레드를 이용하지 않음에도 메소드가 동기화를 수행한 후에 실행되어 효율이 나쁘기 때문에 현재는 동기화 기능이 없는 ArrayList
를 사용합니다. ArrayList
에서 동기화 기능이 필요할 경우 Collections
클래스를 통해 동기화 메소드를 사용합니다.
지금 Vector
의 존재 의의는 구버전과의 호환을 위해서이기 때문에 ArrayList
만을 사용할 것을 권장하고 있습니다.
Vector
는 선언 및 사용법이 ArrayList
와 동일하기 때문에 따로 다루지 않으려고 합니다. 추후에 쓰레드를 다루는 부분이후나 필요해지면 그때 따로 다루도록 하겠습니다.
LinkedList
는 ArrayList
와 동일한 사용방법과 개념을 가졌지만 내부구조에서 차이가 있는 컬렉션입니다. ArrayList
는 배열을 생성해서 컬렉션을 다루었지만 LinkedList
는 링크
라는 개념을 사용해서 데이터를 저장합니다.
C로 작성된 포스트긴 하지만 연결 리스트(Linked List)에 대한 개념이 있으므로 자세한 내용은 이 포스트를 참조해주세요.
요약하자면, 데이터를 데이터를 저장하는 데이터 필드와 다음 데이터 필드를 가리키는 링크 필드로 구성된 데이터(노드)를 이용합니다. 이 노드의 링크 필드가 다음 데이터를 가리키는 방식을 이어나가면서 리스트의 형태를 구성하게 되는 것이 연결 리스트입니다.
자바에서 사용되는LinkedList
는 이중 연결 리스트의 형태를 취하고 있어서 이전 노드를 가리키는 링크 필드, 다음 노드를 가리키는 링크 필드 두 개의 링크 필드가 사용됩니다.
ArrayList
에서는 삽입, 삭제를 할 때 인덱스를 밀거나 당기고 새로운 배열을 만들어서 삽입하는 방식으로 동작하던 것이 LinkedList
에서는 링크의 연결을 끊고 링크를 새로 잇기만 하면 되어서 삽입, 삭제가 빈번한 경우에서는 LinkedList
가 더 좋은 효율을 보여줍니다.
LinkedList
의 선언과 사용법은 ArrayList
와 동일합니다.
LinkedList<E> 변수명 = new LinkedList<E>(); //객체 타입 E인 객체만 저장
LinkedList 변수명 = new LinkedList(); //모든 타입의 객체를 저장
마찬가지로 LinkedList
의 삽입, 삭제, 검색을 이용하는 코드입니다.
public class Main {
public static void main(String[] args) {
LinkedList<String> ll = new LinkedList<String>();
ll.add("가");
ll.add("나");
ll.add("다");
System.out.println("LinkedList 크기: " + ll.size());
ll.remove("다");
System.out.println("LinkedList 크기: " + ll.size());
System.out.println("\"나\"의 위치: " + ll.indexOf("나"));
}
}
위에서 계속 삽입, 삭제 연산이 많은 경우 LinkedList
가 더 빠르다고 했었는데요. 한 번 직접 확인해보겠습니다.
간단하게 for문을 통해 각각의 리스트에 숫자 1부터 1000까지를 삽입하는 메소드를 만들고 nanoTime()
을 통해 수행시간을 비교해보겠습니다.
public class Main {
public static void main(String[] args) {
ArrayList<Integer> al = new ArrayList<>();
LinkedList<Integer> ll = new LinkedList<>();
long alStartTime, alEndTime, alTotalTime;
long llStartTime, llEndTime, llTotalTime;
alStartTime = System.nanoTime(); //ArrayList 시간 측정
insertElements(al);
alEndTime = System.nanoTime();
alTotalTime = alEndTime - alStartTime;
llStartTime = System.nanoTime(); //LinkedList 시간 측정
insertElements(ll);
llEndTime = System.nanoTime();
llTotalTime = llEndTime - llStartTime;
System.out.println("ArrayList 소요시간: " + alTotalTime);
System.out.println("LinkedList 소요시간: " + llTotalTime);
}
//리스트에 1부터 1000까지 삽입하는 메소드
static List<Integer> insertElements(List<Integer> list) {
for (int i = 1; i <= 1000; i++) {
list.add(i);
}
return list;
}
}
컴퓨터한테는 적은 양인 1000번의 삽입 연산인데도 꽤 많은 시간 차이가 나죠? (약 0.00003초 차이) 간단한 값만을 삽입했는데도 이정도 차이면 더 큰 규모의 연산을 진행할 경우 유의미한 시간차가 발생하게 됩니다.
따라서 다시 정리하자면 ArrayList
는 삽입, 삭제 연산이 적은 경우, LinkedList
는 삽입, 삭제 연산이 빈번한 경우에 사용하시면 되겠습니다.