컬렉션 프레임워크란?
컬렉션(collection)은 동일한 타입을 묶어 관리하는 자료구조를 말한다. 하지만 배열도 동일한 타입을 묶어 관리하지만 컬렉션이라 부르지않는다.
컬렉션이 배열과 구분되는 가장 큰 특징은 데이터의 저장 용량(capacity)을 동적으로 관리할 수 있다는 것이다.
배열은 생성 시점에 저장 공간의 크기를 확정해야 하고, 나중에 변경할 수 없는 반면, 컬렉션의 저장 공간은 메모리 공간이 허용하는 한 데이터의 개수에 따라 동적으로 변화할 수 있다.
프레임워크(framework)는 클래스 또는 인터페이스를 생성하는 과정에서 설계의 원칙 또는 구조에 따라 클래스 또는 인터페이스를 설계하고, 이렇게 설계된 클래스와 인터페이스를 묶어 놓은 개념이다. 정리하면 아래와 같다.
컬렉션
- 동일한 타입을 묶어 관리하는 자료구조
- 저장 용량을 동적으로 관리
프레임워크
- 클래스와 인터페이스의 모임
- 클래스의 정의에 설계 원칙 또는 구조가 존재
컬렉션 프레임워크
- 리스트, 스택, 큐, 트리 등의 자료구조에 정렬, 탐색 등의 알고리즘을 구조화해 놓은 프레임워크
자바에서 제공하는 컬렉션 프레임워크의 주요 클래스와 인터페이스는 아래 그림과 같다.
컬렉션의 특성에 따라 구분하면 크게 List<E>, Set<E>, Map<K, V>로 나눌 수 있고, 메모리의 입출력 특성에 따라 기존의 컬렉션 기능을 확장 또는 조합한 Stack<E>, Queue<E>가 있다.
List<E> 컬렉션 인터페이스
List<E>는 인터페이스이기 때문에 객체를 스스로 생성할 수 없다. 따라서 List<E> 인터페이스를 구현한 클래스를 이용해서 객체를 생성해야 한다.
List<E>의 대표적인 구현 클래스로는 ArrayList<E>, Vector<E>, LinkedList<E>가 있으며 아래와 같이 컬렉션을 생성할 수 있다.
List<Integer> list1 = new ArrayList<Integer>(); // 타입 지정
List<Integer> list2 = new ArrayList<>(); // 타입 지정 생략
List<String> list3 = new ArrayList<>(30); // 초기 저장 용량 지정
List<String> list4 = new Vector<String>(); // 벡터 객체 생성
List<Integer> list5 = new LinkedList<>(); // 연결리스트 객체 생성
List<Integer> list6 = Arrays.asList(1, 2, 3); // 정적 컬렉션 객체 생성
List<E>는 객체를 생성할 때 타입 지정과 초기 저장 용량 지정을 생략할 수 있다.
초기 저장 용량을 생략하면 기본으로 10만큼의 저장 용량을 내부에 확보하고, 이후에 데이터가 추가되서 저장 용량이 더 필요하면 JVM이 저장 용량을 자동으로 늘려준다.
여기서 유의할 점은 LinkedList<E> 객체는 기본 생성자만 존재하므로 저장 용량 지정을 할 수 없다는 점이다.
그리고 마지막의 처럼 Arrays 클래스의 asList(T ...) 정적 메서드를 사용하면 저장 용량이 동적으로 늘어나지 않는 정적 컬렉션 객체를 생성하게 된다.
List<E>의 주요 메서드
구분 | 리턴 타입 | 메서드명 | 기능 |
데이터 추가 | boolean | add(E element) | 매개변수로 입력된 원소를 리스트 마지막에 추가 |
void | add(int index, E element) | index 위치에 입력된 원소 추가 | |
boolean | addAll(Collection<? Extends E> c) | 매개변수로 입력된 컬렉션 전체를 마지막에 추가 | |
boolean | addAll(int index, Collection <? Extends E> c) |
index 위치에 입력된 컬렉션 전체를 추가 | |
데이터 변경 | E | set(int index, E element) | index 위치의 원솟값을 입력된 원소로 변경 |
데이터 삭제 | E | remove(int index) | index 위치의 원솟값 삭제 |
boolean | remove(Object o) | 원소 중 매개변수 입력과 동일한 객체 삭제 | |
void | clear() | 전체 원소 삭제 | |
리스트 데이터 정보 추출 |
E | get(int index) | index 위치의 원솟값을 꺼내 리턴 |
int | size() | 리스트 객체 내에 포함된 원소의 개수 | |
boolean | isEmpty() | 리스트의 원소가 하나도 없는지 여부를 리턴 | |
리스트 배열 반환 |
Object[] | toArray() | 리스트를 Object 배열로 변환 |
T[] | toArray(T[] t) | 입력매개변수로 전달한 타입의 배열로 변환 |
ArrayList<E> 구현 클래스
ArrayList<E>는 대표적인 List<E> 구현 클래스로, List<E>가 지니고 있는 대표적인 특징인 데이터를 인덱스로 관리하는 기능, 저장 공간을 동적으로 관리하는 기능등을 그대로 지니고 있다.
public class ArrayListExample {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>();
// 데이터 추가
arrayList.add(1);
arrayList.add(2);
arrayList.add(1, 3);
System.out.println(arrayList);
arrayList.addAll(arrayList);
System.out.println(arrayList);
// 데이터 변경
arrayList.set(1, 5);
System.out.println(arrayList);
// 데이터 삭제
arrayList.remove(1);
System.out.println(arrayList);
arrayList.remove(Integer.valueOf(1));
System.out.println(arrayList);
// 정보 추출
System.out.println(arrayList.get(3));
System.out.println(arrayList.size());
System.out.println(arrayList.isEmpty());
}
}
add()는 데이터를 리스트의 맨 마지막에 추가해주며, index가 매개변수에 포함되면 해당 위치에 데이터를 넣은 후에 뒤에 있는 데이터들은 한 칸씩 밀려나게 된다.
addAll()은 매개변수에 있는 객체를 맨 뒤에, index가 포함된다면 해당 위치에 그대로 추가해준다.
set()은 index에 해당하는 위치의 데이터를 변경해준다.
remove는 기본 자료형인 숫자만 넣을 경우 index에 해당하는 위치의 데이터를 삭제하며, 리스트의 제네릭 타입에 맞는 객체를 매개변수에 넣으면 해당되는 데이터의 맨 앞의 값을 삭제한다.
get()은 index에 해당하는 위치의 데이터를 반환한다.
size()는 리스트에 저장된 데이터의 개수를 반환한다.
isEmpty()는 리스트에 데이터가 존재한다면 true, 존재하지 않는다면 false를 반환한다.
Vector<E> 구현 클래스
Vector<E>는 List<E>의 구현 클래스중 하나다.
동일한 타입의 객체를 저장하고, 메모리를 동적 할당할 수 있으며 데이터의 추가, 변경, 삭제가 가능하다는 List<E>의 공통적인 특성을 모두 갖고있다.
앞에서 본 ArrayList<E>와의 차이점은 Vector<E>의 주요 메서드는 동기화 메서드로 구현돼 있어서 멀티 쓰레드에 적합하도록 설계돼 있다는 것이다.
위와 같이 get(), set(), remove() 등의 주요 메서드들은 synchronized를 통해 동기화 메서드로 구현되어 있다.
동기화 메서드는 하나의 공유 객체를 2개의 쓰레드가 동시에 사용할 수 없도록 만든 메서드다. 따라서 하나의 공유 객체에서 2개의 쓰레드가 동시에 읽기, 쓰기를 하는 경우가 생긴다면 Vector<E> 클래스를 사용하는 것이 좋다.
하지만 1개의 쓰레드로만 구성된 환경이라면 굳이 무겁고 많은 리소스를 차지하는 Vector<E>를 쓰는 것보다는 ArrayList<E>를 쓰는 것이 효율적이다.
LinkedList<E> 구현 클래스
LinkedList<E>는 List<E>의 구현 클래스중 하나이다.
앞에서 본 구현 클래스들 처럼 List<E>의 모든 공통적인 특징(동일 타입 수집, 메모리 동적 할당, 데이터 추가/변경/삭제 메서드 등)을 가지고 있다.
그리고 ArrayList<E>처럼 메서드를 동기화하지 않았기 때문에 싱글 쓰레드에서 사용하기에 적합하다.
ArrayList<E>와의 차이점으로 첫번째는 저장 용량을 매개변수로 갖는 생성자가 없기 때문에 객체를 생성할 때 저장 용량을 지정할 수 없다는 것이다.
List<Integer> list1 = new LinkedList<>(); // 정상 작동
List<Integer> list2 = new LinkedList<>(30); // 오류 발생
두번째 차이점은 내부적으로 데이터를 저장하는 방식이 다르다는 것이다.
ArrayList<E>는 모든 데이터를 위치 정보(index)와 값으로 저장하는 반면, LinkedList<E>는 앞뒤 객체의 정보를 저장한다.
따라서 데이터를 추가, 삭제할 때와 검색할 때 두 구현 클래스간에 성능 차이가 생긴다.
데이터를 추가, 삭제할 때부터 보면 ArrayList<E>는 추가, 삭제를 하면 뒤쪽 데이터들을 밀거나 당겨야한다. 하지만 LinkedList<E>는 데이터를 추가, 삭제할 때 앞, 뒤쪽 데이터의 연결 정보만 수정하면 되므로 성능 면에서 더 효율적이다.
반면 데이터를 검색할 때 ArrayList<E>는 데이터의 위치 정보(index)를 통해 빠르게 검색할 수 있다. 하지만 LinkedList<E>는 위치 정보(index)가 없기 때문에 앞에서부터 데이터를 찾아야 하므로 성능 면에서 비효율적이다.
따라서 데이터를 자주 읽어야하는 상황이라면 ArrayList<E>를, 데이터를 자주 추가하고 삭제해야하는 상황이라면 LinkedList<E>를 사용하는 것이 성능면에서 좋을 것이다.
Reference
- 자바 완전 정복 | 김동형 지음
댓글