생활코딩 강의를 듣고 적는 글입니다.
컬렉션 프레임워크는 여러 데이터를 효과적으로 처리하는 클래스(자료구조와 알고리즘)의 집합이다. java.util 패키지에서 제공한다.
Collection은 객체의 저장을 뜻하고, Framework는 사용 방법을 정해놓은 라이브러리를 뜻한다.
컬렉션 프레임워크의 구조를 보면 Collection은 인터페이스고 단일 객체를 다룬다. Set과 List는 Collection을 상속받는 하위 인터페이스이다. 그리고 HashSet, ArrayList는 각각 인터페이스의 구현 클래스이다. 정확히는 AbstractSet, AbstractList가 구현하고 그걸 HashSet과 ArrayList가 상속받는다.
List 컬렉션
- 배열과 비슷하게 객체를 인덱스로 관리한다.
- 배열과 차이점은 저장 용량(크기)이 자동으로 증가하고, 객체를 저장할 때 자동 인덱스가 부여된다.
- 객체 자체가 아닌 참조 번지를 저장하기 때문에 같은 객체를 저장할 수 있다.
- ArrayList, Vector, LinkedList 등이 있다.
- 제네릭을 이용해 저장되는 객체의 타입을 지정할 수 있다.(타입을 안정해도 된다.)
List가 제공하는 메소드
기능 | 메소드 | 설명 |
객체 추가 | boolean add(E e) | 객체를 맨 끝에 추가 |
void add(int index, E element) | 원하는 인덱스에 객체 추가 | |
E set(int index, E element) | 원하는 인덱스의 객체를 전달된 객체로 변경 | |
객체 검색 | boolean contains(Object o) | 전달된 객체가 저장됐는지 확인 |
E get(int index) | 주어진 인덱스에 저장된 객체를 리턴 | |
boolean isEmpty() | 컬렉션이 비었는 지 확인 | |
int size() | 저장된 객체 수 리턴 | |
객체 삭제 | void clear() | 저장된 객체 모두 삭제 |
E remove(int index) | 주어진 인덱스의 객체 삭제 | |
boolean remove(Object o) | 주어진 객체 삭제 |
ArrayList: List 인터페이스의 구현 클래스. 초기 용량을 10으로 갖지만 객체 수가 늘어나면 용량은 자동으로 증가한다.
조회가 잦은 경우에는 적합하지만 빈번한 객체 삭제와 삽입이 일어난다면 바람직하지 않다.
ArrayList al = new ArrayList(); - 선언
al.add("one"); - 값 추가
al.size(); - 크기 반환
al.get(i); - i번째 값 반환
기본으로 Object 타입으로 값을 처리하는데 제네릭을 사용할 수도 있다.
ArrayList<String> al = new ArrayList<String>();
Vector는 ArryaList와 동일한 내부 구조를 갖는다. ArrayList와 다르게 Vector는 동기화 메소드(syschronized)로 구성되어 멀티 스레드가 동시에 Vector의 메소드들을 실행할 수 없고, 하나의 스레드가 메소드 실행을 완료해야 다른 스레드가 메소드를 실행할 수 있다.(스레드 관련: jang-sn.tistory.com/42) -> 그래서 멀티 스레드 환경에서 안전하게 객체를 주가, 삭제할 수 있다.(thread safe)
List<E> list = new Vecotr<E>();
List<E> list = new Vecotr<>(); // 앞에서 지정된 타입을 따라간다.
LinkedList는 ArrayList와 사용 방법은 똑같지만 내부 구조는 완전히 다르다. ArrayList는 내부 배열에 객체를 저장해서 관리하지만, LinkedList는 인접 참조를 링크해서 체인처럼 관리한다. 이중 연결 리스트의 구조이다. 따라서 객체를 제거하더라도 앞뒤 링크만 변경되고 나머지 링크는 변경되지 않는다. 삽입도 마찬가지
LinkedList가 처음 생성될 때에는 어떤 링크도 만들어지지 않아 내부는 비어있다.
List<E> list = new LinkedList<E>();
List<E> list = new LinkedList<>();
LinkedList의 구조적인 특징 때문에 순차적으로 추가, 삭제하는 경우는 ArrayList가 빠르지만, 중간에 추가, 삭제하는 경우는 앞뒤 링크 정보만 변경하므로 LinkedList가 더 빠르다.
Set 컬렉션
- 객체의 저장 순서가 유지되지 않는다.(보장되지 않는다.)
- 객체를 중복해서 저장할 수 없고, 하나의 null만 저장할 수 있다.
- HashSet, LinkedHashSet, TreeSet 등이 있다.
Set이 제공하는 메소드
기능 | 메소드 | 설명 |
객체 추가 | boolean add(E e) | 주어진 객체를 저장. 성공적으로 저장되면 true를 리턴, 중복 객체면 false 리턴 |
객체 검색 | boolean contains(Object o) | 객체가 저장됐는 지 확인 |
boolean isEmpty() | 컬렉션이 비었는 지 확인 | |
iterator<E> iterator() | 저장된 객체를 한 번씩 가져오는 반복자를 리턴 | |
int size() | 저장된 객체 수 리턴 | |
객체 삭제 | void clear() | 저장된 모든 객체 삭제 |
boolean remove(Object o) | 주어진 객체 삭제 |
Collection의 메소드 중 iterator(반복자)라는 메소드, Iterator라는 데이터 타입이 있다.
iterator라는 메소드를 실행하면 Iterator 타입의 객체가 생성된다. 이 객체는 컬렉션이 갖고 있는 값을 갖게 된다.
제네릭을 사용할 수 있다.
Iterator는 인터페이스고 각각 타입에 맞게 구현되고 공통적으로는 hasNext(), next(), remove() 메소드가 제공된다.
Set과 같이 인덱스가 없는 자료구조에서 자료들을 탐색할 때, 향상된 for문 과 Iterator가 유용하게 사용된다.
hasNext()는 값이 존재하는 지에 대한 메소드고
next()는 반복자가 갖고 있는 값 중 하나를 호출하고 그 값을 삭제한다.
위 두 메소드는 while문과 같이 잘 사용한다.
remove()는 현재 객체를 실제 컬렉션에서 제거한다.
Set<String> A = new Set<>();
Iterator<String> hi = (Iterator)A.iterator();
while(hi.hasNext()) {
System.out.println(hi.next());
}
HashSet은 Set 인터페이스의 구현 클래스이다. 따라서 동일한 객체를 중복 저장하지 않는다. HashSet은 객체의 hashCode() 메소드를 호출해 해시코드를 얻고, 이미 저장된 객체들의 해시코드와 비교한다. 만약 같은 해시코드가 있다면 다시 equals() 메소드로 객체를 비교해서 true가 나오면 같은 객체로 판단하고 저장하지 않는다.
HashSet에서 같은 문자열을 갖는 String 객체는 같은 객체로 간주되는데, hashCode()와 equals() 메소드가 재정의 됐기 때문이다. 이처럼 실제 인스턴스가 달라도 hashCode, equals 메소드를 재정의하여 같은 객체로 간주하게 할 수 있다.
다음은 HashSet의 사용예시다.
HashSet<T> A = new HashSet<T>(); - 초기화
A.add(1);
A.containsAll(B); - B는 A의 부분집합?
A.addAll(B); - B의 모든 원소를 A에 추가한다. -> 합집합으로 만듦
A.retainAll(B); - B의 원소와 겹치는 것만 남김. -> 교집합을 만듦.
A.removeAll(B); - B랑 겹치는 건 제거 -> 차집합
TreeSet은 원소들을 정렬할 수 있는 Set이다. TreeSet, TreeMap은 내부적으로 이진 검색 트리를 사용해서 자료를 저장할 때 저장할 위치를 저장한다. 따라서 TreeSet은 중복을 허용하지 않으면서, 원소를 정렬시킨다. 다른 것은 일반 Set과 비슷하다. -> 주의: 정렬은 되지만, 인덱스가 있는 것은 아니다.
객체 비교를 위해 Comparable<T> 또는 Comparator<T> 인터페이스를 구현해야 한다. T는 비교 대상의 타입이다.
Comparable 인터페이스를 구현하려면, CompareTo() 메소드를 구현해야 한다. 비교 대상 객체가 매개 변수로 전달받는다. 객체 자신(this)의 비교 기준 값과 매개 변수의 기준 값을 더하거나 뺀 값을 리턴하고 그것을 기준으로 정렬한다. 리턴 값이 양수라면 더 큰 걸로 간주한다.
하지만 String의 경우, compareTo() 메소드가 이미 정의되어 있기 때문에 this.문자열.compareTo(비교객체.문자열) 의 값을 리턴하면 된다.
Comparator 인터페이스를 구현하려면, compare() 메소드를 구현해야 한다. 매개 변수의 첫번째는 자신 객체, 두번째는 비교 대상을 전달 받는다. CompareTo()와 마찬가지로 비교 기준 값을 비교해서 리턴한다.
대신 Comparator를 사용해 비교하려면, TreeSet이나 TreeMap의 생성자에 compare()가 구현된 객체를 생성해 전달해야 한다. 생성자에 전달된 객체는 비교하려는 객체들과 같은 타입이 아니어도 된다. 그러나 그 객체의 compare() 메소드를 사용하는 것이 기 때문에 Comparator와 compare() 메소드를 구현해야 한다.
class Member implements Comparable<Member>{
///
public int compareTo(Member m){
return this.id - m.id; // 오름차순 정렬
}
///
}
TreeSet<Member> mySet = TreeSet<Member>(new Member());
String 같은 경우, 이미 CompareTo() 메소드를 사용해 비교하지만, 위와 같은 방법을 사용하면 다른 방법으로 정렬방식을 바꿀 수 있다.
일반적으로 Comparable을 더 많이 사용하고, 이미 Comparable이 구현된 경우 Comparator를 이용해 다른 정렬 방식을 사용할 수 있다.
중간 정리:
Set은 집합이고 중복이 허용되지 않는다. 순서가 보장되지 않는다.(Tree 제외)
List는 배열이다. 중복을 허용한다. 순서가 보장된다.
Set과 List의 특징 차이 때문에 Set은 Collection과 같은 API를 제공하지만, List는 인덱스와 관련된 메소드를 추가해서 제공한다.
Map 컬렉션
- key(키)와 value(값), pair로 구성된 Map.Entry 객체를 저장하는 구조다. Entry는 Map 인터페이스 내부에 선언된 중첩 인터페이스이다.
- 맵은 key와 그에 대응되는 value를 갖는다.
- key는 중복되지 않고, value는 중복이 허용된다. 하나의 key에는 하나의 value가 대응된다. 기존의 key와 동일한 key로 저장하면 새로운 value가 저장된다.
- key 의 중복을 확인하기 위해 equals(), hashCode()를 사용한다.
- 내부적으로 Hash 방식으로 구현되어 있다.(index = hash(key))
- HashMap ,HashTable, LinkedHashMap, Properties, TreeMap 등이 있다.
Map이 제공하는 메소드
기능 | 메소드 | 설명 |
객체 추가 | V put(K key, V value) | 주어진 키로 값을 저장. 새로운 키라면 null을 리턴하고, 있던 거라면 값을 대체하고 이전 값을 리턴한다. |
객체 검색 | boolean containsKey(Object key) | 주어진 키가 있는 지 확인 |
boolean containsValue(Object value) | 주어진 값이 있는 지 확인 | |
Set<Map.Entry<K,V>> entrySet() | 키와 값의 쌍으로 구성된 모든 Map.Entry 객체를 Set에 담아서 리턴 | |
V get(Object key) | 키에 대응하는 값 리턴. | |
boolean isEmpty() | 컬렉션이 비었는 지 확인. | |
Set<K> keySet() | 모든 키를 Set 객체에 담아서 리턴 | |
int size() | 저장된 키의 수를 리턴 | |
Collection<V> values() | 저장된 모든 값을 Collection에 담아 리턴 | |
객체 삭제 | void clear() | 모든 Map.Entry를 삭제 |
V remove(Object key) | 주어진 키와 일치하는 Map.Entry를 삭제하고 리턴. |
키와 값 때문에 초기화할 때 제네릭을 사용하면서 두 개의 데이터 타입이 필요하다.
HashMap은 Map 인터페이스를 구현한 Map 컬렉션이다. HashSet과 마찬가지로 키를 구분할 때, hashCode() 메소드를 호출해 해시코드를 얻고, 이미 저장된 키들의 해시코드와 비교한다. 만약 같은 해시코드가 있다면 다시 equals() 메소드로 키를 비교해서 true가 나오면 같은 키로 판단한다.
HashMap<String, Integer> a = new HashMap<String, Integer>(); - 초기화
a.put("one", 1); - Map에만 존재하는 메소드다. 키와 밸류를 전달한다.
a.get("one"); 키에 해당하는 밸류를 가져온다.
반복적으로 처리하려면 key와 value가 존재하기 때문에 entrySet() 메소드를 사용해서 Map.Entry 타입의 데이터가 담긴 Set을 가져와야 한다. 그러면 Entry의 메소드인 getKey(), getValue()로 값을 얻을 수 있다.
static void iteratorUsingForEach(HashMap map){
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
Hashtable은 HashMap과 내부 구조가 동일하다. 차이점은 Hashtable은 동기화 메소드로 구성되어 있어, 멀티 스레드가 동시에 Hashtable의 메소드들을 실행할 수 없고, 하나의 스레드가 실행을 완료해야만 다른 스레드를 실행할 수 있다. 따라서 멀티 스레드 환경에서 안전하게 객체를 추가, 삭제할 수 있다.
Map<String, Integer> map = new Hashtable<String, Integer>();
Map<String, Integer> map = new Hashtable<>();
TreeMap은 key-value pair를 관리한다. 하지만 이름에 Tree가 붙는 것을 보면 알 수 있든이 이진 탐색 트리를 사용해 key 객체를 정렬하여 관리한다. 위에 기술한 TreeSet과 마찬가지로 비교 대상의 key는 Comparable 또는 Comparator 인터페이스를 구현하여 사용한다. 자바의 많은 클래스들은 이미 Comparable이 구현된 경우가 있는데 이럴 땐 따로 구현을 해주지 않아도 된다.
Collections.sort()메소드를 이용하면 리스트 타입의 원소들을 정렬할 수 있다. Collections의 메소드들은 static이기 때문에 바로 사용할 수 있다.
대신 정렬하려는 리스트의 원소들은 비교 가능해야 한다.(Comparable, Comparator)
class Computer implements Comparable {
int serial;
String name;
Computer(int serial, String name){
this.serial = serial;
this.name = name;
}
public int compareTo(Object o) {
return this.serial - ((Computer)o).serial;
}
}
///////////////////////////////////////////////////
Collections.sort(computer);
'study > java' 카테고리의 다른 글
자바/JAVA 스레드(1) (0) | 2021.02.11 |
---|---|
자바/JAVA 스트림과 입출력 (0) | 2021.02.09 |
자바/JAVA enum 열거 (0) | 2021.01.29 |
자바/JAVA 자신을 호출(생성)하는 객체 (2) | 2021.01.29 |
자바/JAVA 클래스 패스(Class Path) (0) | 2021.01.27 |