데이터의 집합을 뜻하는 컬렉션은 여러 데이터들을 그룹으로 묶어놓은 것을 의미하고, 이러한 컬렉션을 다루는 데에 있어 편리한 메서드들을 미리 정의해놓은 것을 컬렉션 프레임워크라고 한다.
컬렉션 프레임워크는 특정 자료 구조에 데이터를 추가하고, 삭제하고, 수정하고, 검색하는 등의 동작을 수행하는 편리한 메서드들을 제공한다.
데이터의 순서 유지O 및 중복 저장O
ArrayList, Vector, Stack, LinkedList 등
데이터의 순서 유지X 및 중복 저장X
HashSet, TreeSet 등
키(key)와 값(value)의 쌍으로 데이터를 저장
데이터의 순서가 유지X
키는 값을 식별하기 위해 사용되므로 중복 저장X, but 값은 중복 저장O
HashMap, HashTable, TreeMap, Properties 등
List와 Set은 서로 공통점이 많아 Collection이라는 인터페이스로 묶인다.
ArrayList는 저장 용량을 초과하여 객체들이 추가되면, 자동으로 저장용량이 늘어나게 됩니다.
List<타입 매개변수> 객체명 = new ArrayList<타입 매개변수>(초기 저장 용량);
List<String> container1 = new ArrayList<String>();
// String 타입의 객체를 저장하는 ArrayList 생성
// 초기 용량이 인자로 전달되지 않으면 기본적으로 10으로 지정됩니다.
List<String> container2 = new ArrayList<String>(30);
// String 타입의 객체를 저장하는 ArrayList 생성
// 초기 용량을 30으로 지정하였습니다.
특정 인덱스의 객체를 제거하면, 바로 뒤 인덱스부터 마지막 인덱스까지 모두 앞으로 1씩 당겨진다. 삭제할 때에는 데이터 이동이 많이 일어나므로 속도가 저하된다. 반면 배열의 주소 + n * 데이터 타입의 크기를 계산하여 데이터에 빠르게 접근이 가능하기 때문에 검색(읽기) 측면에서는 유리하다.
public class ArrayListExample {
public static void main(String[] args) {
// ArrayList를 생성하여 list에 할당
List<String> list = new ArrayList<String>();
// String 타입의 데이터를 ArrayList에 추가
list.add("Java");
list.add("egg");
list.add("tree");
// 저장된 총 객체 수 얻기
int size = list.size();
// 0번 인덱스의 객체 얻기
String skill = list.get(0);
// 저장된 총 객체 수 만큼 조회
for(int i = 0; i < list.size(); i++){
String str = list.get(i);
System.out.println(i + ":" + str);
}
// for-each문으로 순회
for (String str: list) {
System.out.println(str);
}
// 0번 인덱스 객체 삭제
list.remove(0);
}
}
LinkedList 컬렉션은 데이터를 효율적으로 추가, 삭제, 변경하기 위해 사용한다. LinkedList에는 불연속적으로 존재하며, 이 데이터는 서로 연결(link)되어 있다.
LinkedList에서 데이터를 삭제하려면, 삭제하고자 하는 요소의 이전 요소가 삭제하고자 하는 요소의 다음 요소를 참조하도록 변경하면 된다. 링크를 끊어주는 방식이라고 생각하면 된다. 배열처럼 데이터를 이동하기 위해 복사할 필요가 없기 때문에 처리 속도가 훨씬 빠르다.
데이터를 추가할 때에도 마찬가지로, 새로운 요소를 추가하고자 하는 위치의 이전 요소와 다음 요소 사이에 연결해주면 된다. 즉, 이전 요소가 새로운 요소를 참조하고, 새로운 요소가 다음 요소를 참조하게 만드는 것이다.
public class LinkedListExample {
public static void main(String[] args) {
// Linked List를 생성하여 list에 할당
List<String> list = new LinkedList<>();
// String 타입의 데이터를 LinkedList에 추가
list.add("Java");
list.add("egg");
list.add("tree");
// 저장된 총 객체 수 얻기
int size = list.size();
// 0번 인덱스의 객체 얻기
String skill = list.get(0);
// 저장된 총 객체 수 만큼 조회
for(int i = 0; i < list.size(); i++){
String str = list.get(i);
System.out.println(i + ":" + str);
}
// for-each문으로 순회
for (String str: list) {
System.out.println(str);
}
// 0번 인덱스 객체 삭제
list.remove(0);
}
}
데이터를 중간에 추가하거나 삭제하는 경우, LinkedList는 ArrayList보다 속도가 빠르다. 반면에 검색에 있어서는 ArrayList보다 상대적으로 속도가 느리다.
컬렉션에 저장된 요소들을 순차적으로 읽어오는 기능이다.
Iterator 인터페이스에 정의되어 있으며, Collection 인터페이스에는 Iterator 인터페이스를 구현한 클래스의 인스턴스를 반환하는 메서드인 iterator()가 정의되어 있다.
즉, Collection 인터페이스에 정의된 iterator()를 호출하면, Iterator 타입의 인스턴스가 반환된다.
따라서 Collection 인터페이스를 상속받는 List와 Set 인터페이스를 구현한 클래스들은 iterator() 메서드를 사용할 수 있다.
List<String> list = ...;
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) { // 읽어올 다음 객체가 있다면
String str = iterator.next(); // next()를 통해 다음 객체를 읽어옵니다.
...//for-each문으로도 반복 가능
}
next() 메서드는 컬렉션의 객체를 그저 읽어오는 메서드로, 실제 컬렉션에서 객체를 빼내는 것은 아니지만, remove() 메서드는 컬렉션에서 실제로 객체를 삭제한다.
List<String> list = ...;
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){ // 다음 객체가 있다면
String str = iterator.next(); // 객체를 읽어오고,
if(str.equals("str과 같은 단어")){ // 조건에 부합한다면
iterator.remove(); // 해당 객체를 컬렉션에서 제거합니다.
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
// HashSet 생성
HashSet<String > languages = new HashSet<String>();
// HashSet에 객체 추가
languages.add("Java");
languages.add("Python");
languages.add("Javascript");
languages.add("C++");
languages.add("Kotlin");
languages.add("Ruby");
languages.add("Java"); // 중복
// 반복자 생성하여 it에 할당
Iterator it = languages.iterator();
// 반복자를 통해 HashSet을 순회하며 각 요소들을 출력
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
출력 결과를 보면 입력한 순서대로 출력되지 않고, “Java”를 두 번 추가했지만 한 번만 저장된 것을 확인할 수 있다.
TreeSet은 이진 탐색 트리 형태로 데이터를 저장한다.
이진 탐색 트리(Binary Search Tree)란 하나의 부모 노드가 최대 두 개의 자식 노드와 연결되는 이진 트리(Binary Tree)의 일종으로, 정렬과 검색에 특화된 자료 구조이다.
이때 최상위 노드를 ‘루트'라고 한다. 이진 탐색 트리는 모든 왼쪽 자식의 값이 루트나 부모보다 작고, 모든 오른쪽 자식의 값이 루트나 부모보다 큰 값을 가지는 특징이 있다.
기존에 저장된 키와 동일한 키로 값을 저장하면, 기존의 값이 새로운 값으로 대치된다.
키와 값으로 구성된 객체를 Entry라고 하는데 이 Entry 객체를 저장한다.
HashMap은 해시 함수를 통해 '키'와 '값'이 저장되는 위치를 결정하므로, 사용자는 그 위치를 알 수 없고, 삽입되는 순서와 위치 또한 관계가 없다.
이렇게, HashMap은 이름 그대로 해싱(Hashing)을 사용하기 때문에 많은 양의 데이터를 검색하는 데 있어서 뛰어난 성능을 보인다.
HashMap을 생성할 때에는 아래와 같이 키와 값의 타입을 따로 지정해주어야 한다.
HashMap<String, Integer> hashmap = new HashMap<>();
import java.util.*;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성
HashMap<String, Integer> map = new HashMap<>();
// Entry 객체 저장
map.put("피카츄", 85);
map.put("꼬부기", 95);
map.put("야도란", 75);
map.put("파이리", 65);
map.put("피존투", 15);
// 저장된 총 Entry 수 얻기
System.out.println("총 entry 수: " + map.size());
// 객체 찾기
System.out.println("파이리 : " + map.get("파이리"));
// key를 요소로 가지는 Set을 생성 -> 아래에서 순회하기 위해 필요합니다.
Set<String> keySet = map.keySet();
// keySet을 순회하면서 value를 읽어옵니다.
Iterator<String> keyIterator = keySet.iterator();
while(keyIterator.hasNext()) {
String key = keyIterator.next();
Integer value = map.get(key);
System.out.println(key + " : " + value);
}
// 객체 삭제
map.remove("피존투");
System.out.println("총 entry 수: " + map.size());
// Entry 객체를 요소로 가지는 Set을 생성 -> 아래에서 순회하기 위해 필요합니다.
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
// entrySet을 순회하면서 value를 읽어옵니다.
Iterator<Map.Entry<String, Integer>> entryIterator = entrySet.iterator();
while(entryIterator.hasNext()) {
Map.Entry<String, Integer> entry = entryIterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " : " + value);
}
// 객체 전체 삭제
map.clear();
}
}
Map은 키와 값을 쌍으로 저장하기 때문에 iterator()를 직접 호출할 수 없다. 그 대신 keySet() 이나 entrySet() 메서드를 이용해 Set 형태로 반환된 컬렉션에 iterator()를 호출하여 반복자를 만든 후, 반복자를 통해 순회할 수 있다.
HashTable은 HashMap과 내부 구조가 동일하며, 사용 방법 또한 매우 유사하다. HashTable과 HashMap의 차이점은 스레드와 관련이 있는데, 추후에 학습할 예정이다.
import java.util.*;
public class HashTableExample {
public static void main(String[] args){
HashTable<String, String> map = new Hashtable<String, String>();
map.put("Spring", "345");
map.put("Summer", "678");
map.put("Fall", "91011");
map.put("Winter", "1212");
System.out.println(map);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("아이디와 비밀번호를 입력해 주세요");
System.out.println("아이디");
String id = scanner.nextLine();
System.out.println("비밀번호");
String password = scanner.nextLine();
if (map.containsKey(id)) {
if (map.get(id).equals(password)) {
System.out.println("로그인 되었습니다.");
break;
}
else System.out.println("비밀번호가 일치하지 않습니다. ");
}
else System.out.println("입력하신 아이디가 존재하지 않습니다.");
}
}
}