Java/Java

컬렉션 프레임워크 (Collection Framework)

마손리 2023. 3. 8. 00:26

컬렉션 프레임워크

자바에서의 컬렉션이란 여러 데이터들의 집합을 의미한다. 여러 데이터들을 그룹으로 묶어 놓은것이 컬렉션이며 이 컬렉션을 다루는 기능의 메서드들을 미리 정의해 놓은것을 컬렉션 프레임워크라한다.

 

컬렉션 프레임워크는 특정 자료구조를 이용하여 데이터의 접근, 삽입, 검색, 삭제 등의 동작을 수행하는 메서드들을 제공해준다.

컬렉션 프레임워크의 상속 계층도

 

 

List

List인터페이스는 배열과 같이 객체를 일렬로 정렬해놓은 구조이며 각 요소들은 객체로 이루어저 있고 인덱스로 각 객체들을 관리한다. 

 

List의 큰 특징으로는 인덱스로 데이터의 순서를 관리하기 때문에 데이터의 순서가 유지되며 데이터값이 중복되더라도 저장이 가능하다.

 

List 인터페이스의 공통 메서드

기능 리턴타입 메서드 설명
객체 추가 void add(int index, Object element) 주어진 인덱스에 객체를 추가
  boolean addAll(int index, Collection c) 주어진 인덱스에 컬렉션을 추가
  Object set(int index, Object element) 주어진 위치에 객체를 저장
객체 검색 Object get(int index) 주어진 인덱스에 저장된 객체를 반환
  int indexOf(Object o) / lastIndexOf(Object o) 순방향 / 역방향으로 탐색하여 주어진 객체의 위치를 반환
  ListIterator listIterator() / listIterator(int index) List의 객체를 탐색할 수 있는ListIterator 반환 / 주어진 index부터 탐색할 수 있는 ListIterator 반환
  List subList(int fromIndex, int toIndex) fromIndex부터 toIndex에 있는 객체를 반환
객체 삭제 Object remove(int index) 주어진 인덱스에 저장된 객체를 삭제하고 삭제된 객체를 반환
  boolean remove(Object o) 주어진 객체를 삭제
객체 정렬 void sort(Comparator c) 주어진 비교자(comparator)로 List를 정렬

 

ArrayList

ArrayList<타입 매개변수> 객체명 = new ArrayList<타입 매개변수>(초기 저장 용량);

ArrayList<String> arrayList = new ArrayList<String>();

ArrayList는 List 인터페이스를 구현한 클래스이며 컬렉션 프레임워크에서 가장 많이 사용된다. 기능적으로는 Vector와 동일하지만 기존의 Vector를 개선한 것이다.

 

ArrayList는 배열의 특징을 모두 가지고 있지만 ArrayList의 요소는 객체로 이루어저 있다는 점과 편리한 기능들을 정의한 메서드들이 있다는 점에서 일반 배열과 큰 차이를 보인다. 

 

반면에 ArrayList에 데이터를 삽입 혹은 제거를 할시 해당 데이터 이후의 데이터들의 인덱스들을 재정렬이 필요하다는 점에서 배열과 같은 특징을 보인다. 

이 특징으로 인해 ArrayList는 Stack 자료구조와 같이 마지막 인덱스부터 작업이 이루어질경우 큰 효율을 보이지만 ArrayList의 첫번째 혹은 중간부터 작업을 해야하는경우 LinkedList가 더 좋은 효율을 보인다.

 

LinkedList

LinkedList는 데이터를 효율적으로 추가, 삭제, 변경하기 위해 사용된다. 배열과 달리 LinkedList의 데이터는 불연속적으로 존재하지만 서로의 데이터들이 순서대로 연결되어 있기에 순서를 유지할수 있다.

 

자바의 LinkedList는 Doubly linked list를 사용한다. 즉 각각의 데이터들이 자신 이전과 이후의 데이터들의 참조값을 저장하고있다.

Doubly Linked List의 자료구조

(Doubly Linked List에 관한 포스트  :https://mason-lee.tistory.com/31)

 

ArrayList와 LinkedList의 차이

ArrayList는 각 객체요소들이 인덱스를 가지고있으며 그 인덱스로 바로 접근이 가능하지만 LinkedList의 경우 어떤 특정한 노드로 접근하기 위해선 해당 컬렉션의 맨앞 혹은 맨뒤 노드부터 순차적으로 접근해야 하기에 접근에 관해선 ArrayList가 더 좋은 효율을 보인다.

 

하지만 데이터를 삽입 혹은 삭제 해야하는경우 ArrayList는 해당 데이터이후의 모든 데이터에 접근하여 인덱스를 재정렬 해줘야 되는 반면 LinkedList는 해당 데이터의 이전 이후 데이터의 참조값만을 변경해주면 되므로 데이터 수정에 관해서는 LinkedList가 우세하다. 다만 해당 LIst들의 맨 마지막 데이터를 수정해줄때에는 둘다 좋은 효율을 보이고 있다.

 

 

각 기능별 시간 복잡도

  추가 삭제 접근
ArrayList O(n) O(n) O(1)
LinkedList O(1) O(1) O(n)

결론적으로, 데이터의 잦은 변경이 필요할경우 LinkedList를, 고정된 데이터를 갖는경우 ArrayList가 효율적이다.

 

 

Iterator (Iterable 인터페이스)

Iterator는 컬렉션에 저장된 요소들을 순회하는 역할을 한다.

List, Queue, Set과 같이 Iterable인터페이스를 구현하는 자료구조에서만 사용가능하며 해당 자료구조들로 iterator()메서드를 호출하면 Itorator타입의 인스턴스가 반환되고 그 인스턴스를 이용하여 순회메서드들을 사용할수 있다.

 

Iterator인스턴스의 메서드

메서드 설명
hasNext() 읽어올 객체가 남아 있으면 true를 리턴하고, 없으면 false를 리턴합니다.
next() 컬렉션에서 하나의 객체를 읽어옵니다. 이 때, next()를 호출하기 전에 hasNext()를 통해 읽어올 다음 요소가 있는지 먼저 확인해야 합니다.
remove() next()를 통해 읽어온 객체를 삭제합니다. next()를 호출한 다음에 remove()를 호출해야 합니다.

 

Iterator 사용 예제

ArrayList<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator(); // iterator()메서드를 이용한 객체생성 필수
// 타입매개변수<String>은 생략가능

while(iterator.hasNext()) {     // 읽어올 다음 객체가 있다면 
	String str = iterator.next(); // next()를 통해 다음 객체를 읽어온다. 
    System.out.println(str);
    
    if(str.equals("str과 같은 단어")){ // 조건에 부합한다면
	iterator.remove();            // 해당 객체를 컬렉션에서 제거한다. 
	}
} 
// next()메서드를 호출안해주게되면 반복문이 무한루프를 돌게된다.

 

 

Set

수학에서의 Set(집합)과 같이 자바의 Set도 중복된 값을 허용하지 않으며 저장 순서를 유지하지 않는 컬렉션이다. 

 

Set의 메서드

기능 리턴타입 메서드 설명
객체 추가 boolean add(Object o) 주어진 객체를 추가하고, 성공하면 true를, 중복 객체면 false를 반환합니다.
객체 검색 boolean contains(Object o) 주어진 객체가 Set에 존재하는지 확인합니다.
  boolean isEmpty() Set이 비어있는지 확인합니다.
  Iterator Iterator() 저장된 객체를 하나씩 읽어오는 반복자를 리턴합니다.
  int size() 저장되어 있는 전체 객체의 수를 리턴합니다.
객체 삭제 void clear() Set에 저장되어져 있는 모든 객체를 삭제합니다.
  boolean remove(Object o) 주어진 객체를 삭제합니다.

Set을 기반으로 하는 자료구조들도 다른 자료구조들과 마찬가지로 Collection과 Itorable 인터페이스의 추상메서드 또한 사용이 가능하다.

 

HashSet

HashSetSet 인터페이스를 구현하는 가장 대표적인 컬렉션 클래스이며 Set인터페이스 특성 그대로 중복된 값을 허용하지 않고 저장 순서를 유지하지 않는다.

 

HashSet의 데이터 저장 흐름

  1. add(Object o)를 통해 객체를 저장하고자 한다..
  2. 이 때, 저장하고자 하는 객체의 해시코드를 hashCode() 메서드를 통해 얻어낸다.
  3. Set이 저장하고 있는 모든 객체들의 해시코드를 hashCode() 메서드로 얻어낸다.
  4. 저장하고자 하는 객체의 해시코드와, Set에 이미 저장되어져 있던 객체들의 해시코드를 비교하여, 같은 해시코드가 있는지 검사한다.
    1. 이 때, 만약 같은 해시코드를 가진 객체가 존재한다면 아래의 5번으로 넘어간다.
    2. 같은 해시코드를 가진 객체가 존재하지 않는다면, Set에 객체가 추가되며 add(Object o) 메서드가 true를 리턴한다.
  5. equals() 메서드를 통해 객체를 비교한다.
    1. true가 리턴된다면 중복 객체로 간주되어 Set에 추가되지 않으며, add(Object o)가 false를 리턴한다.
    2. false가 리턴된다면 Set에 객체가 추가되며, add(Object o) 메서드가 true를 리턴한다.

 

HashSet 사용 예제

HashSet<String > set = new HashSet<String>();

// HashSet에 객체 추가
set.add("Korea");
set.add("America");
set.add("China");
set.add("Korea"); //중복되므로 무시

// HashSet 또한 Iterable 인터페이스를 구현하므로 사용가능 
Iterator iterator = set.iterator();

 

TreeSet

TreeSet이진 탐색 트리(Binary Search Tree) 형태로 이 또한 데이터의 중복 저장을 허용하지 않고 저장 순서를 유지하지 않는다.

(이진 탐색 트리 포스트 : https://mason-lee.tistory.com/33)

 

TreeSet 사용 예제

TreeSet<String> alphabet = new TreeSet<>();

// TreeSet에 요소 추가
alphabet.add("c");
alphabet.add("b");
alphabet.add("d");
alphabet.add("a");

System.out.println(alphabet); // [a, b, c, d]
System.out.println(alphabet.first()); // a
System.out.println(alphabet.last()); // d
System.out.println(alphabet.higher("b")); // c
System.out.println(alphabet.subSet("a", "d")); // [a, b, c]

분명 랜덤하게 요소들을 추가했음에도 자동적으로 오름차순으로 정렬된 것을 확인할수 있다.

 

 

 

Map

Map 인터페이스는 키(key)와 값(value)으로 구성된 객체를 저장하는 구조를 가지고 있다. 여기서 이 객체를 Entry객체라고 하, 이 Entry 객체는 키와 값을 각각 Key 객체와 Value 객체로 저장한다.

 

Map을 사용할 때에 중요한 사실은 키는 중복 저장될 수 없지만, 값은 중복 저장이 가능하다는 것이다. Index 혹은 Id와 같이 키의 역할은 값을 식별하는 것이기 때문이다.

만약 기존에 저장된 키와 동일한 키로 값을 저장하면, 기존의 값이 새로운 값으로 대치된다.

 

이러한 이유들 때문에 중복값이 없는 key데이터는 keySet()을 이용하여 Set에 담으며 value데이터는 중복값이 있을수 있으므로 values를 이용하여 Collection에 담아주게된다.

 

다음은 Map 인터페이스를 구현한 클래스에서 공통적으로 사용 가능한 메서드이다. List가 인덱스를 기준으로 관리되는 반면에, Map은 키(key)로 객체들을 관리하기 때문에 키를 매개값으로 갖는 메서드가 많다.

기능 리턴 타입 메서드 설명
객체 추가 Object put(Object key, Object value) 주어진 키로 값을 저장합니다. 해당 키가 새로운 키일 경우 null을 리턴하지만, 동일한 키가 있을 경우에는 기존의 값을 대체하고 대체되기 이전의 값을 리턴합니다.
객체 검색 boolean containsKey(Object key) 주어진 키가 있으면 true, 없으면 false를 리턴합니다.
  boolean containsValue(Object value) 주어진 값이 있으면 true, 없으면 false를 리턴합니다.
  Set entrySet() 키와 값의 쌍으로 구성된 모든 Map.Entry 객체를 Set에 담아서 리턴합니다.
  Object get(Object key) 주어진 키에 해당하는 값을 리턴합니다.
  boolean isEmpty() 컬렉션이 비어 있는지 확인합니다.
  Set keySet() 모든 키를 Set 객체에 담아서 리턴합니다.
  int size() 저장된 Entry 객체의 총 갯수를 리턴합니다.
  Collection values() 저장된 모든 값을 Collection에 담아서 리턴합니다.
객체 삭제 void clear() 모든 Map.Entry(키와 값)을 삭제합니다.
  Object remove(Object key) 주어진 키와 일치하는 Map.Entry를 삭제하고 값을 리턴합니다.

 

HashMap

HashMap은 Map 인터페이스를 구현한 대표적인 클래스이다. HashMap은 아래 그림과 같이 키와 값으로 구성된 객체를 저장하는데, 이 객체를 Entry 객체라고 한다.



HashMap은 해시 함수를 통해 '키'와 '값'이 저장되는 위치를 결정하므로, 사용자는 그 위치를 알 수 없고, 삽입되는 순서와 위치 또한 관계가 없다.

이렇게, HashMap은 이름 그대로 해싱(Hashing)을 사용하기 때문에 많은 양의 데이터를 검색하는 데 있어서 뛰어난 성능을 보인다.

 

또한, HashMap의 개별 요소가 되는 Entry 객체는 Map 인터페이스의 내부 인터페이스인 Entry 인터페이스를 구현하며, Map.Entry 인터페이스에는 다음과 같은 메서드가 정의되어져 있다.

리턴 타입 메서드 설명
boolean equals(Object o) 동일한 Entry 객체인지 비교합니다.
Object getKey() Entry 객체의 Key 객체를 반환합니다.
Object getValue() Entry 객체의 Value 객체를 반환합니다.
int hashCode() Entry 객체의 해시코드를 반환합니다.
Object setValue(Object value) Entry 객체의 Value 객체를 인자로 전달한 value 객체로 바꿉니다.

 

HashMap 예제1)

HashMap<String, Integer> map = new HashMap<>();

// Entry 객체 저장
map.put("Korea", 85);
map.put("Canada", 95);
map.put("America", 75);
map.put("Australia", 65);
map.put("Japan", 15);

// 저장된 총 Entry 수 얻기
System.out.println("총 entry 수: " + map.size());

// 객체 찾기
System.out.println("Korea : " + map.get("Korea"));

// 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);
}
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑  keySet()메서드로 key값만을 가저와 iteraor()메서드로 반복문을 실행

// 객체 삭제
map.remove("Canada");

System.out.println("총 entry 수: " + map.size());

// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ entrySet()메서드로 Entry 객체를 가저와 iteraor()메서드로 반복문을 실행

// 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(); // Map.Entry 인터페이스의 메서드
    Integer value = entry.getValue(); // Map.Entry 인터페이스의 메서드
    System.out.println(key + " : " + value);
}

// 객체 전체 삭제
map.clear();

Map은 Iteratable 인터페이스를 구현시키지 않으며 키와 값을 쌍으로 저장하기 때문에 iterator()를 직접 호출할 수 없다. 그 대신 keySet() 이나 entrySet() 메서드를 이용해 Set 형태로 반환된 컬렉션에 iterator()를 호출하여 반복자를 만든 후, 반복자를 통해 순회할 수 있다.

 

 

HashMap 예제2)

HashMap<String, String> map = new HashMap<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("입력하신 아이디가 존재하지 않습니다.");
}

 

 

 

컬렉션 클래스 정리

'Java > Java' 카테고리의 다른 글

파일 입출력 (InputStream, OuputStream, FileReader, FileWriter, File)  (0) 2023.03.09
람다(Lambda)  (2) 2023.03.09
try(), catch() - 예외 처리(Exception Handling)  (0) 2023.03.07
제네릭 (Generic)  (0) 2023.03.07
열거형 (Enum)  (0) 2023.03.06