자바는 널리 알려진 자료구조를 사용해서 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 인터페이스와 구현 클래스를 java.util 패키지에서 제공합니다. 이들을 총칭해서 컬렉션 프레임워크라고 부릅니다.
컬렉션은 객체의 저장을 뜻하고, 프레임워크란 사용 방법을 정해 놓은 라이브러리를 말합니다. 실제로 컬렉션 프레임워크는 사용 방법을 정의한 인터페이스와 실제 객체를 저장하는 다양한 컬렉션 클래스(구현 클래스)를 제공합니다.
1. List 컬렉션 - ArrayList, Vector, LinkedList
List 컬렉션은 배열과 비슷하게 객체를 인덱스로 관리합니다. 배열과의 차이점은 저장 용량이 자동으로 증가하며, 객체를 저장할 때 자동 인덱스가 부여된다는 것입니다. 그리고 추가, 삭제, 검색을 위한 다양한 메소드들이 제공됩니다.
List 컬렉션은 객체 자체를 저장하는 것이 아니라 객체의 번지를 참조합니다. 그렇기 때문에 동일한 객체를 중복 저장할 수 있는데, 이 경우 동일한 번지가 참조됩니다. null도 저장이 가능하며 이 경우 해당 인덱스는 객체를 참조하지 않습니다.
다음은 List 컬렉션에서 공통적으로 사용할 수 있는 List 인터페이스의 메소드입니다. 인덱스로 객체를 관리하기 때문에 인덱스를 매개값으로 갖는 메소드가 많습니다.
기능 | 메소드 | 설명 |
객체 추가 | boolean add(E e) | 주어진 객체를 맨 끝에 추가 |
void add(int index, E e) | 주어진 인덱스에 객체를 추가 | |
E set(int index, E e) | 주어진 인덱스에 저장된 객체를 주어진 객체로 교체 |
객체 검색 | boolean contains(Obj o) | 주어진 객체가 저장되어 있는지 조사 |
E get(int index) | 주어진 인덱스에 저장된 객체를 리턴 | |
boolean isEmpty() | 컬렉션이 비어있는지 확인 | |
int size() | 저장된 전체 객체 수 리턴 |
객체 삭제 | void clear() | 저장된 모든 객체 삭제 |
E remove(int index) | 주어진 인덱스에 저장된 객체를 삭제 | |
boolean remove(Obj o) | 주어진 객체를 삭제 |
위의 표에서 메소드의 매개 변수와 리턴 타입에 E라는 타입 파라미터가 있는데, 이것은 저장되는 객체의 타입을 List 컬렉션을 생성할 때 결정하라는 뜻입니다.
List<String> list = ... 이런 식으로 리스트 변수를 String 타입으로 선언하면 E 타입 파라미터는 String 타입이 되는 것입니다.
* 향상된 for문으로 List 컬렉션의 모든 객체 처리하기
for(String str : list) {
}
1-1. ArrayList
ArrayList는 다음과 같이 생성할 수 있습니다.
List<E> list = new ArrayList<E>();
List<String> list = new ArrayList<>();
두 번째 코드와 같이 ArrayList의 E 타입 파라미터를 생략하면 왼쪽 List에 지정된 타입을 따라 갑니다.
기본 생성자로 ArrayList 객체를 생성하면 내부에 10개의 객체를 저장할 수 있는 초기 용량(capacity)을 가지게 됩니다. 저장되는 객체 수가 늘어나면 용량이 자동으로 증가합니다.
ArrayList에 객체를 추가하면 0번 인덱스부터 차례대로 저장됩니다. ArrayList에 객체를 추가하면 0번 인덱스부터 차례대로 저장됩니다. ArrayList에서 특정 인덱스의 객체를 제거하면바로 뒤 인덱스부터 마지막 인덱스까지 모두 앞으로 1씩 당겨집니다. 마찬가지로 특정 인덱스에 객체를 삽입하면 해당 인덱스부터 마지막 인덱스까지 1씩 밀려납니다.이러한동작때문에 저장되 객체수가 많고,특정 인덱스에 객체를 추가하거나 제거하는 일이 빈번하다면 ArrayList보다는 LinkedList를 사용하는 것이 좋습니다. 하지만 인덱스를 이용해서 객체를 찾거나 맨 마지막에 객체를 추가하는 경우에는 ArrayList가 더 좋은 성능을 발휘합니다.
결과:
6
HKHJK
JAVA
HAJS
HKHJK
JdddA
1123A
dkkdkdkA
HAJS
JdddA
1123A
1-2. Vector
Vector는 ArrayList와 동일한 내부 구조를 가지고 있습니다.
List<E> list = new Vector<E>();
List<E> list = new Vector<>();
ArrayList와 다른 점은 Vector는 동기화된(synchronized) 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 Vector의 메소드를 실행할 수 없고, 하나의 스레드가 메소드 실행을 완료해야만 다른 스레드가 메소드를 실행할 수 있다는 것입니다. 그래서 멀티 스레드 환경에서 안전하게 객체를 추가, 삭제할 수 있습니다. 이것을 스레드에 안전(Thread safe)하다고 표현합니다.
1-3. LinkedList
List<E> list = new LinkedList<E>();
List<E> list = new LinkedList<>();
LinkedList에서 특정 인덱스의 객체를 제거하면 앞뒤 링크만 변경되고 나머지 링크는 변경되지 않습니다. 삽입도 마찬가지 입니다.
결과:
LinkedList 걸린시간: 15562164ns
ArrayList 걸린시간: 395901848ns
위의 예제를 통해 맨 앞에 삽입할 때마다 기존의 모든 인덱스를 한 칸 뒤로 밀어야 하는 ArrayList보다 LinkedList가 속도가 훨씬빠름을 알 수 있습니다.맨 끝에서부터 차례대로 추가,삭제하는 경우는 ArrayList가 더 빠르지만, 중간에 추가 삭제하는 경우는 LinkedList가 더욱 빠릅니다.
구분 | 순차적으로 추가, 삭제 | 중간에 추가, 삭제 | 검색 |
ArrayList | 빠르다 | 느리다 | 빠르다 |
LinkedList | 느리다 | 빠르다 | 느리다 |
----------------------------------------------------------------------------------------------------------------------------
2. Set 컬렉션 - 수학의 집합
List 컬렉션은 객체의 저장 순서를 유지하지만, Set 컬렉션은 저장 순서가 유지되지 않습니다. 또한 객체를 중복해서 저장할 수 없고, 하나의 null만 저장할 수 있습니다.
Set 컬렉션에는 HashSet, LinkedHashSet, TreeSet 등이 있는데, 다음은 Set 컬렉션에서 공통적으로 사용 가능한 Set 인터페이스와 메소드입니다. 인덱스로 관리하지 않기 때문에 인덱스를 매개값으로 갖는 메소드가 없습니다.
기능 | 메소드 | 설명 |
객체 추가 | boolean add(E e) | 주어진 객체를 저장. 성공적으로 저장되면 true를, 중복 객체면 false를 리턴 |
객체 검색 | boolean contains(Obj o) | 주어진 객체가 저장되어 있는지 조사 |
boolean isEmpty() | 컬렉션이 비어 있는지 조사 | |
Iterator<E> iterator() | 저장된 객체를 한 번씩 가져오는 반복자를 리턴 | |
int size() | 저장된 객체 수 리턴 | |
객체 삭제 | void clear() | 저정된 모든 객체 삭제 |
boolean removed(Obj o) | 주어진 객체 삭제 |
Set 컬렉션은 인덱스로 객체를 검색해서 가져오는 메소드가 없습니다. 대신, 전체 객체를 대상으로 한 번씩 반복해서 가져오는 반복자(itertator)가 있습니다. 반복자는 iterator() 메소드를 호출하면 얻을 수 있습니다.
다음은 iterator 인터페이스에 선언된 메소드들입니다.
리턴 타입 | 메소드 | 설명 |
boolean | hasNext() | 가져올 객체가 있으면 참 |
E | next() | 컬렉션에서 하나의 객체를 가져옴 |
void | remove() | 컬렉션에서 객체를 제거 |
다음과 같이 사용할 수 있습니다.
Set<String> set = ...
Iterator<String> iterator = set.iterator();
while(iterator.has.next()){
String str = iterator.next();
}
Iterator를 사용하지 않더라도 향상된 for문을 통해 전체 객체를 대상으로 반복할 수 있습니다.
for(String str : set) {
}
Iterator의 next() 메소드로 가져온 객체를 제거하고 싶다면 remove() 메소드를 호출하면 됩니다. Iterator의 메소드지만, 실제 Set 컬렉션에서 객체가 제거됨을 알아야 합니다.
While(iterator.hasNext()) {
String str = iterator.next();
if(str.equals("홍길동")) {
iterator.remove();
}
}
2-1. HashSet
HashSet은 Set 인터페이스의 구현 클래스입니다. HashSet은 객체들을 순서 없이 저장하고 동일한 객체는 중복 저장하지 않습니다. HashSet이 판단하는 동일한 객체란 꼭 같은 인스턴스를 뜻하지 않습니다. HashSet은 객체를 저장하기 전에 먼저 hashCode() 메소드를 호출해서 해시코드를 얻어내고, 이미 저장되어 있는 객체들의 해시코드와 비교합니다. 만약 동일한 해시코드가 있다면 다시 equals() 메소드로 두 객체를 비교해서 true가 나오면 중복 저장하지 않습니다.
String 클래스는 hashCode()와 equals()메소드를 재정의해서 같은 문자열일 경우엔 hashCode()의 리턴값은 같게, equals()의 리턴값은 true가 나오게 했기 때문입니다.
결과:
총 객체 수: 4
Java
JDBC
Servlet
iBATIS
JDBC
Servlet
iBATIS
비어 있음
다음 예제는 hashCode()와 equals() 메소드를 재정의하여 인스턴스의 이름과 나이가 동일하다면 동일한 객체로 간주하여 중복 저장되지 않도록 한 것입니다.
HashSet은 객체를 저장하기 전에 먼저 hashCode()를 비교하고 equals()가 true인지 확인하므로 이 두 메소드를 재정의하여 원하는 대로 중복 처리가 가능합니다.
---------------------------------------------------------------------------------------------------------------------------
3. Map 컬렉션
Map 컬렉션은 키(key)와 값(value)으로 구성된 Map.Entry 객체를 저장하는 구조를 가지고 있습니다. Entry는 Map 인터페이스 내부에 선언된 중첩 인터페이스입니다. 여기서 키와 값은 모두 객체입니다.
키는 중복 저장될 수 없지만 값은 중복 저장될 수 있습니다. 만약 기존에 저장된 키와 동일한 키로 값을 저장하면 기존의 값은 없어지고 새로운 값으로 대체됩니다.
Map 컬렉션에는 HashMap, HashTable, LinkedHashMap, Properties, TreeMap 등이 있습니다. 다음은 Map 컬렉션에서 공통적으로 사용 가능한 Map인터페이스의 메소드들입니다.키로 객체들을 관리하기 때문에 키를 매개값으로 갖는 메소드가 많습니다.
기능 | 메소드 | 설명 |
객체 추가 | V put(K ket, V value) | 주어진 키로 값을 저장합니다. 새로운 키일 경우 null을 리턴하고, 동일한 키가 있을 경우 값을 대체하고 이전 값을 리턴합니다. |
객체 검색 | boolean containsValue(Obj value) | 주어진 값이 있는지 여부 확인 |
boolean containsKey(Obj Key) | 주어진 키가 있는지 여부 확인 | |
Set<Map.Entry<K, V>> entrySet() | 키와 값의 쌍으로 구성된 모든 Map,Entry 객체를 Set에 담아서 리턴 | |
V get(Obj key) | 주어진 키가 있는 값을 리턴 | |
boolean isEmpty() | 컬렉션이 비어있는지 확인 | |
Set<K> KeySet() | 모든 키를 Set 객체에 담아서 리턴 | |
int size() | ||
Collection<V> values() | 저장된 모든 값을 Collection에 담아서 리턴 | |
객체 삭제 | void clear() | |
V remove(Obj key) | 주어진 키와 일치하는 Map.Entry를 삭제하고 값을 리턴 |
Map<String, Integer> map = ...;
이때 매개값들은 자동 박싱 됩니다.
저장된 전체 객체를 대상으로 하나씩 값을 얻고 싶을 때는 두 가지 방법을 사용할 수 있습니다. 첫 번째는 keySet() 메소드로 모든 키를 Set 컬렉션으로 얻은 다음, 반복자를 통해 키를 하나씩 얻고 get() 메소드를 통해 값을 얻는 방법입니다.
Map<K, V> map = ...;
Set<K> keySet = map.keySet();
Iterator<k> keyiterator = keySet.iterator();
while(keyiterator.hasNext()) {
K key = keyiterator.next();
V value = map.get(key);
두 번째는 entrySey() 메소드로 모든 Map.Entry를 Set 컬렉션으로 얻은 다음 반복자를 통해 Map.Entry를 하나씩 얻고 getKey()와 getValue() 메소드를 이용해 키와 값을 얻는 방법입니다.
Set<Map.Entry<K, V>> entrySet = map.entrySet();
Iterator<Map.Entry<K, V>> entryIterator = entrySet.iterator();
while(entryIterator.hasNext()) {
Map.Entry<K, V> entry = entryIterator.next();
K key = entry.getKey();
V value = entry.getValue();
}
3-1. HashMap
HashMap은 Map 인터페이스를 구현한 대표적인 Map 컬렉션입니다. HashMap의 키로 사용할 수 있는 객체는 hashCode()와 equals()메소드를 재정의해서 동등 객체가 될 조건을 정해야 합니다. 객체가 달라도 동등 객체라면 같은 키로 간주하고 중복 저장하지 않도록 하기 위함입니다.
주로 키 타입은 String을 많이 사용하는데, String은 문자열이 같을 경우 동등 객체가 될 수 있도록 hashCode()와 equals()메소드가 재정의되어 있습니다. 키의 값은 기본 타입을 사용할 수 없고 클래스 및 인터페이스 타입만 사용 가능합니다.
다음 예제는 이름을 키로, 점수를 값으로 저장하는 HashMap의 사용 방법을 보여줍니다.
결과:
size:4
100
hk: 100
js: 80
ke: 90
do: 85
3
js: 80
ke: 90
do: 85
0
3-2. Hashtable
HashMap과 거의 동일하지만 중요한 차이점이 있습니다. Hashtable은 동기화된 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 Hashtable의 메소드들을 실행할 수 없고, 하나의 스레드가 실행을 완료해야만 다른 스레드를 실행할 수 있다는 것입니다.
'java' 카테고리의 다른 글
java 입출력 스트림 (0) | 2021.01.30 |
---|---|
java 스택, 큐 (0) | 2021.01.30 |
스레드 제어 (0) | 2021.01.29 |
java.lang 패키지 (0) | 2021.01.28 |
java 예외 처리 - 예외 클래스 (0) | 2021.01.27 |