자바는 널리 알려진 자료구조를 바탕으로, 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 관련된 인터페이스와 클래스들을 `java.util` 패키지에 포함시켜 놓음
👉 이를 총칭하여 컬렉션 프레임워크라고 부름
- List와 Set은 객체를 추가, 삭제, 검색하는 방법에 있어서 공통점이 있기에
공통된 메소드만 따로 모아 Collection 인터페이스로 정의 & 상속 - Map은 키와 값을 하나의 쌍으로 묶어서 관리하는 구조로 List와 Set 과는 사용방법이 다름
❓ 인터페이스로 구현한 이유
처음에는 공유 코드가 없어서 인터페이스로 설계 했으나,
시간이 흐르며 공통 기능이 필요해졌고 이를 해결하기 위해 `default` 메서드가 도입 됨
인터페이스 분류 | 특징 | 구현 클래스 | |
Collection | List | 순서를 유지하고 저장 중복 저장 가능 |
ArrayList, Vector, LinkedList |
Set | 순서 유지x 중복 저장 불가 |
HashSet, TreeSet | |
Map | 키와 값으로 구성된 엔트리 저장 | HashMap, HashTable, TreeMap, Properties |
List 컬렉션
List 컬렉션은 객체를 `인덱스`로 관리하므로, 객체를 저장하면 인덱스가 부여됨.
인덱스를 이용하여 객체 검색, 삭제 기능 제공
공통 메소드
기능 | 메소드 | 설명 |
객체 추가 | boolean add (E e) | 주어진 객체 맨 끝에 추가 |
void add (int index. E e) | 주어진 인덱스에 객체를 추가 | |
set (int index, E e) | 주어진 인덱스의 객체를 새로운 객체로 바꿈 | |
객체 검색 | boolean contains (Object o) | 주어진 객체가 저장되어 있는지 여부 |
E get (int index) | 주어진 인덱스에 저장된 객체 리턴 | |
int size() | 저장되어 있는 전체 객체 수 리턴 | |
객체 삭제 | void clear() | 저장된 모든 객체 삭제 |
E remove(int index) | 주어진 인덱스에 저장된 객체를 삭제 | |
boolean remove(Object o) | 주어진 객체를 삭제 |
ArrayList
- List 컬렉션에서 가장 많이 사용하는 컬렉션
- ArrayList에 객체를 추가하면, 내부 배열에 객체가 저장됨
- 배열과 달리 제한 없이 객체를 추가할 수 있다는 것
객체 자체를 저장하는 게 아닌, 객체의 `번지`를 저장
동일한 객체를 저장할 수 있는데, 이 경우에는 동일한 번지가 저장됨
List<E> list = new ArrayList<E>(); // E에 지정된 타입의 객체만 저장
List<E> list = new ArrayList<>(); // E에 지정된 타입의 객체만 저장
List list = new ArrayList(); // 모든 타입의 객체 저장
ArrayList 컬렉션에 객체를 추가하면, 인덱스 0번부터 차례대로 저장
특정 인덱스의 객체를 제거하면, 바로 뒤 인덱스부터 마지막 인덱스까지 모두 앞으로 1씩 당겨짐
∴ 빈번한 삭제/삽입이 일어나는 곳에서는 ArrayList 사용을 지양해야 함
LinkedList
ArrayList와 사용방법이 동일하나, 내부 구조가 완전히 다름
LinkedList는 인접 객체를 체인처럼 연결해서 관리
특정 위치에 객체 삽입 / 삭제 시 바로 앞뒤 링크만 변경하면 됨
빈번한 삽입 삭제가 일어나는 곳에서 ArrayList보다 좋은 성능을 냄
Set 컬렉션
List 컬렉션과 달리, Set 컬렉션은 저장 순서가 유지되지 않음
객체의 중복 저장이 안 되며, 하나의 null만 저장 가능
인덱스로 관리하지 않으므로, 인덱스를 매개값으로 갖는 메소드는 없음
공통 메소드
기능 | 메소드 | 설명 |
객체 추가 | boolean add (E e) | 주어진 객체를 성공적으로 저장하면 true 중복 객체가 있는 경우 false |
객체 검색 | boolean contains (Object o) | 주어진 객체가 저장되어 있는지 여부 |
isEmpty() | 컬렉션이 비어 있는지 조사 | |
`Iterator<E> iterator()` | 저장된 객체를 한 번씩 가져오는 반복자 리턴 | |
객체 삭제 | void clear() | 저장된 모든 객체 삭제 |
boolean remove(Object o) | 주어진 객체를 삭제 |
HashSet
Set 컬렉션 중에서 가장 많이 사용되는 HashSet
Set<E> set = new HashSet<E>(); // E에 지정된 타입의 객체만 저장
Set<E> set = new HashSet<>(); // E에 지정된 타입의 객체만 저장
Set set = new HashSet(); // 모든 타입의 객체 저장
HashSet은 동일한 객체는 중복 저장하지 않음
❓ 동일하다?
동등 객체를 의미함
`hashCode()` 메소드의 리턴값이 같고, `equals()` 메소드가 true를 리턴하면 동일한 객체라고 판단
사용자 지정 동등 객체 판단 👇🏻
상황: 이름과 나이가 동일할 경우 Member 객체를 HashSet에 중복 저장 하지않는다.
public class Member{
public String name;
public int age;
public Member(String name, int age){
this.name = name;
this.age = age;
}
@Override
public int hashCode(){
return name.hashCode()+age;
}
@Override
public boolean equals(Object obj){
if(obj instanceof Member target){
return target.name.equals(name) && (target.age==age);
}else{
return false;
}
}
}
Set 컬렉션은 인덱스로 객체를 검색해서 가져오는 메소드가 없음
∴ 객체를 한 개씩 반복해서 가져와야 함
for문을 이용해 가져오기
Set<E> set = new HashSet<>();
for(E e: set){
...
}
iterator() 메소드 이용
Set<E> set = new HashSet<>();
Iterator<E> iter = set.iterator();
while(iter.hasNext()){
E e= iter.next();
}
return 타입 | 메소드 명 | 설명 |
boolean | hasNext() | 가져올 객체가 있으면 true, 없으면 false |
E | next() | 컬렉션에서 하나의 객체 가져옴 |
void | remove() | next()로 가져온 객체를 Set에서 제거 |
Map 컬렉션
Map 컬렉션은 `key`와 `value`로 구성된 `엔트리` 객체를 저장
키는 중복 저장X, 값 중복저장O
기존에 저장된 키와 동일한 키로 값을 저장하면, 기존 값이 새로운 값으로 대치됨
여기서 키와 값은 모두 객체임
공통 메소드
기능 | return 타입 | 메소드 | 설명 |
객체 추가 | V | put(K key, V value) | 주어진 키와 값을 추가, 저장 되면 값 리턴 |
객체 검색 | boolean | containsKey(Object key) | 주어진 키가 있는지 여부 |
boolean | containsValue(Object value) | 주어진 값이 있는지 여부 | |
Set<Map.Entry<K,V>> | entrySet() | 키와 값의 쌍으로 구성된 모든 Map.Entry 객체를 Set에 담아서 return |
|
V | get(Object key) | 주어진 키의 값 리턴 | |
boolean | isEmpty() | 컬렉션이 비어있는지 여부 | |
Set<K> | keySet() | 모든 키를 Set 객체에 담아서 return | |
int | size() | 저장된 키의 총 수 리턴 | |
Collection<V> | values() | 저장된 모든 값 Collection에 담아서 리턴 | |
객체 삭제 | void | clear() | 모든 Map.Entry 삭제 |
V | remove(Object key) | 주어진 키와 일치하는 Map.Entry 삭제, 삭제되면 값 return |
put 메소드 사용 시, 이전 값 (또는 null)이 반환됨
HashMap
키로 사용할 객체가 hashCode() 메소드의 리턴 값이 같고, equals() 메소드가 true를 반환할 경우,
동일 키로 보고 중복 저장 허용X
Map<K, V> map = new HashMap<>();
Map<String, Integer> map = new HashMap<String, Integer>();
Map<String, Integer> map = new HashMap<>();
Map map = new HashMap(); // 모든 타입 키 & 객체 저장, but 거의 없음
Properties
- Properties는 키와 값을 `String` 타입으로 제한한 컬렉션
- 주로 확장자가 `.properties`인 프로퍼티 파일을 읽을 때 사용
검색 기능을 강화한 컬렉션
컬렉션 프레임워크는 검색 기능을 강화한 TreeSet, TreeMap을 제공
TreeSet
이진 트리를 기반으로 한 Set 컬렉션
❓ 이진 트리
여러 개의 노드가 트리 형태로 연결된 구조로, 루트 노드라 불리는 하나의 노드에서 시작해
각 노드에 최대 2개의 노드를 연결할 수 있는 구조
TreeSet에 객체를 저장하면 자동으로 정렬 됨.
부모노드의 객체와 비교하여 낮은 것은 왼쪽 자식에, 높은 것은 오른쪽 자식 노드에 저장
TreeSet<E> set = new TreeSet<E>();
TreeSet<E> set = new TreeSet<>();
TreeSet 타입으로 대입한 이유는 검색 관련 메소드가 TreeSet에만 정의 돼 있기 때문임
TreeSet이 가진 검색 관련 메소드
return 타입 | 메소드 | 설명 |
E | first() | 제일 낮은 객체 리턴 |
last() | 제일 높은 객체 리턴 | |
lower(E e) | 주어진 객체보다 바로 아래 객체 리턴 | |
higher(E e) | 주어진 객체보다 바로 위 객체 리턴 | |
floor(E e) | 주어진 객체와 동등 객체 리턴, 없으면 바로 아래 객체 리턴 | |
ceiling(E e) | 주어진 객체와 동등 객체 리턴, 없으면 바로 위 객체 리턴 |
|
pollFirst() | 제일 낮은 객체 꺼내오고 제거 | |
pollLast() | 제일 높은 객체 꺼내오고 제거 | |
Iterator<E> | descendingIterator() | 내림차순으로 정렬된 Iterator 리턴 |
NavigableSet<E> | descendingSet() | 내림차순으로 정렬된 NavigableSet 리턴 |
headSet( E toElement, boolean inclusive ) |
주어진 객체보다 낮은 객체들을 NavigableSet 리턴, 주어진 객체 포함 여부는 inclusive |
|
tailSet( E toElement, boolean inclusive ) |
주어진 객체보다 높은 객체들을 NavigableSet 리턴, 주어진 객체 포함 여부는 inclusive |
|
subSet( E fromElement, boolean fromInclusive, E toElement, boolean toInclusive ) |
시작과 끝으로 주어진 객체 사이의 객체들을 NavigableSet 리턴, 주어진 객체 포함 여부는 inclusive |
TreeMap
- 이진 트리를 기반으로 한 Map 컬렉션
- TreeMap에 엔트리를 저장하면 키를 기준으로 자동 정렬
TreeMap<K, V> map = new TreeMap<K, V>();
TreeMap<K, V> map = new TreeMap<>();
TreeSet과 마찬가지로 검색 관련 메소드가 TreeMap에만 정의돼 있음
return 타입 | 메소드 | 설명 |
Map.Entry<K, V> | firstEntry() | 제일 낮은 Map.Entry 리턴 |
lastEntry() | 제일 높은 Map.Entry 리턴 | |
lowerEntry(K key) | 주어진 키보다 바로 아래 Map.Entry 리턴 | |
higherEntry(K key) | 주어진 키보다 바로 위 Map.Entry 리턴 | |
floorEntry(K key) | 주어진 키와 동등 키의 Map.Entry 리턴, 없으면 바로 아래 Map.Entry 리턴 | |
ceilingEntry(K key) | 주어진 키와 동등 키의 Map.Entry 리턴, 없으면 바로 위 Map.Entry 리턴 |
|
pollFirstEntry() | 제일 낮은 Map.Entry 꺼내오고 제거 | |
pollLastEntry() | 제일 높은 Map.Entry 꺼내오고 제거 | |
NavigableSet<K> | descendingKeyset() | 내림차순으로 정렬된 키의 NavigableSet 리턴 |
NavigableMap<K, V> | descendingMap() | 내림차순으로 정렬된 Map.Entry의 NavigableMap 리턴 |
headMap( K toKey, boolean inclusive ) |
주어진 키보다 낮은 Map.Entry들을 NavigableMap 리턴, 주어진 키의 Map.Entry 포함 여부는 inclusive |
|
tailMap( K fromKey, boolean inclusive ) |
주어진 키보다 높은 Map.Entry들을 NavigableMap 리턴, 주어진 키의 Map.Entry 포함 여부는 inclusive |
|
subMap( K fromKey, boolean fromInclusive, K toKey, boolean toInclusive ) |
시작과 끝으로 주어진 키 사이의 Map.Entry 들을 NavigableMap 컬렉션으로 반환 주어진 키의 Map.Entry 포함 여부는 inclusive |
동기화된 컬렉션
컬렉션 프레임워크의 대부분 클래스는 `싱글 스레드` 환경에서 사용할 수 있도록 설계
∴ 여러 스레드가 동시에 컬렉션에 접근한다면 의도치 않게 요소가 변경될 수 있는 불안전한 상태임
Vector와 HashTable은 동기화 된 메소드로 구성돼 있기 때문에 멀티 스레드 환경에서 안전하게 요소를 처리할 수 있지만,
ArrayList와 HashSet, HashMap은 멀티스레드 환경에서 안전하지 않음
📌 Vector와 HashTable의 사용은 지양!
초기 Java(1.0) 클래스로 이후 등장한 ArrayList, HashSet & HashMap이 성능면에서 효율적임
모든 메서드에 동기화 처리가 돼있어서 안전하지만 성능이 많이 떨어진다.
경우에 따라 멀티 스레드 환경에서 사용하고 싶은 경우, `synchronizedxxx()` 메소드 사용
return 타입 | 메소드 | 설명 |
List<T> | synchronizedList(List<T> list) | List를 동기화된 List로 리턴 |
Map<K, V> | synchronizedMap(Map<K, V> m) | Map를 동기화된 Map으로 리턴 |
Set<T> | synchronizedSet(Set<T> s) | Set을 동기화된 Set으로 리턴 |
이 메소드들은 매개값으로 비동기화된 컬렉션을 대입하면 동기화된 컬렉션 리턴
수정할 수 없는 컬렉션
- 요소를 추가, 삭제할 수 없는 컬렉션
- 컬렉션 생성 시 저장된 요소를 변경하고 싶지 않을 때 유용
생성 방법
- List. Set, Map 인터페이스의 정적 메소드, `of()` 사용
- List. Set, Map 인터페이스의 정적 메소드, `copyOf()` 사용
- 배열로부터 수정할 수 없는 List 컬렉션 생성
of()
List<E> immutableList = List.of(E... elements);
Set<E> immutableSet = Set.of(E... elements);
Map<E> immutableMap = Map.of(K k1, V v1, K k2, V v2, ... );
copyOf()
기존 컬렉션을 복사하여 수정할 수 없는 컬렉션 만듦
List<E> immutableList = List.copyOf(Collection<E> coll);
Set<E> immutableSet = Set.copyOf(Collection<E> coll);
Map<E> immutableMap = Map.copyOf(Map<K, V> map);
배열 → 리스트
- 삽입, 삭제가 불가능한 List 반환
- `asList()` 메소드 사용
- 만일, 가변 리스트로 변경하고 싶은 경우 `new ArrayList(Arrays.asList(arr));`로 변경
String [] arr = {"A", "B", "C"};
List<String> immutableList = Arrays.asList(arr);
리스트 → 배열
`toArray()` 메소드 사용
Integer [] arr = list.toArray(new Integer[0]); // Integer
int [] arr2 = list.stream().mapToInt(Integer::intValue).toArray(); // int