CodeStates_Backend/TIL (Today I Learned)

[TIL #15-2][JAVA] Collection Framework, List, Set, Map

developo6 2022. 5. 31. 17:20

코드스테이츠 백엔드 부트캠프 39기 23~24일차, 05/17~18

 

 

 

Collection Framework

자바에서 Collection Framework는, 널리 알려져 있는 자료 구조를 바탕으로 데이터들을 효율적으로 추가, 삭제, 검색할 수 있도록 컬렉션을 만들고 관련된 인터페이스와 클래스들을 포함시켜둔 것이다. Collection Framework가 제공하는 다양한 인터페이스와 구현 클래스를 활용하면 객체 지향적이고 재사용성이 높은 코드를 작성할 수 있다.

자바 Collection Framework 구조

주요 인터페이스로 List , Set, Map 이 있다. ListSet 은 객체를 추가, 삭제, 검색하는 방법에 공통점이 많아 둘의 공통된 메소드만 모아 Collection 이라는 인터페이스로 정의해두었다. Map 은 Key-Value 쌍으로 묶어 관리하는 구조로, List 와 Set 과는 사용법이 달라 따로 독립된 인터페이스로 정의해두었다.

 

추가적으로, 컬렉션 인터페이스 상위에는 Iterable 인터페이스가 있다.

컬렉션 프레임워크 상속관계

 

다음은 각 인터페이스의 특징과 이를 구현한 클래스이다.

인터페이스 특징 구현 클래스
List 순서를 유지하고 저장, 중복저장 가능  <순서O,중복O> ArrayList, Vector, Stack, LinkedList 등
Set 순서를 유지하지 않고 저장, 중복저장 불가능 <순서X,중복X> HashSet, TreeSet 등
Map 키(key)와 값(value)의 쌍으로 저장, 순서 유지하지 않음, 키는 중복저장 불가 <순서X,Key중복X> HashMap, Hashtable, TreeMap, Properties 등

 

List 와 Set 이 포함된 Collection 인터페이스에서 제공하는 주요 메서드는 다음과 같다.

기능 리턴타입 메소드 설명
객체 추가     boolean add(Object o), addAll(Collection c) 주어진 객체 / 컬렉션의 객체들을 컬렉션에 추가
객체 검색   boolean contains(Object o), containsAll(Collection c) 주어진 객체 / 컬렉션이 저장되어 있는지 여부
Iterator iterator() 컬렉션의 iterator를 반환
boolean equals(Object o) 컬렉션이 동일한지 여부
boolean isEmpty() 컬렉션이 비어있는지 여부
int size() 저장되어 있는 전체 객체 수를 반환
객체 삭제  void clear() 컬렉션에 저장된 모든 객체를 삭제
boolean remove(Object o), removeAll(Collection c) 주어진 객체 / 컬렉션을 삭제하고 성공 여부를 반환
boolean retainAll(Collection c) 주어진 컬렉션을 제외한 모든 객체를 컬렉션에서 삭제하고 컬렉션에 변화가 있는지 여부를 반환
객체 변환  Object[] toArray() 컬렉션에 저장된 객체를 객체배열(Object [])로 반환
Object[] toArray(Object[] a) 주어진 배열에 컬렉션의 객체를 저장해서 반환

 

 

 

Iterable

자바의 컬렉션 프레임워크에서는 컬렉션에 저장된 요소를 읽어오는 방법을 Iterable 인터페이스로 표준화하고 있다. Iterable 인터페이스를 구현한 클래스의 인스턴스를 반환하는 iterator() 메소드를 정의하여 각 요소를 접근할 수 있도록 하고 있다. 따라서 Iterable 인터페이스를 상속받는 Collection 인터페이스를 또다시 상속받는 List와 Set 인터페이스에서도 iterator() 메소드를 사용할 수 있다.

 

Iterator

Iterable 인터페이스, Collection 인터페이스와는 별개로 존재하는 인터페이스이다. 용도는 컬렉션 클래스의 데이터를 하나씩 읽어올 때 사용한다. 다음은 Iterator 인터페이스의 메소드이다.

메소드 설명
hasNext() 가져올 객체가 있으면 true를 리턴하고, 없으면 false를 리턴한다
next() 컬렉션에서 하나의 객체를 가져온다
remove() 컬렉션에서 객체를 제거한다

 

현재는 하나씩 요소를 불러올 경우, '향상된 for문'이 권장되고 있기는 하다.

 

 

List

<순서O,중복O>

객체를 일렬로 늘어놓은 구조이다. 따라서 객체를 저장하면 자동으로 인덱스가 부여되고, 인덱스를 이용하는 것이 중요하다. 

 

List 인터페이스를 구현(implements)한 클래스로는 ArrayList, LinkedList, Vector, Stack 등이 있다. 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

컬렉션 프레임워크에서 가장 빈번하게 사용되는 클래스로, 기존의 Vector와 기능적으로 동일하지만 개선한 것이다. ArrayList는 인덱스로 관리한다는 점에서 배열과 거의 동일하지만, 한 가지 차이점이 크기가 가변적이라는 것이다. 배열은 생성될 때 크기가 고정되고 사용 중 크기를 변경할 수 없지만, ArrayList는 가능하다. 최초 설정한 크기를 넘을 경우 자동으로 크기가 증가된다. (👉크기변화가능한 배열)

 

ArrayList의 생성은 저장할 객체 타입을 타입 파라미터, 즉 제네릭으로 표기하고 기본 생성자를 호출한다. 아래와 같다.

List<타입 파라미터> 객체명 = new ArrayList<타입 파라미터>(초기 저장용량);

List<String> container1 = new ArrayList<String>();
//String타입의 객체를 저장하는 ArrayList, 이때 초기 용량은 10개 객체 저장가능.

List<String> container2 = new ArrayList<String>(30);
//용량의 크기를 매개값으로 받아 ArrayList 객체 생성

 

다음은 ArrayList에 String 객체를 추가, 검색, 삭제하는 예제이다.

public class ArrayListExample {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();

		list.add("Java"); // String 객체 저장/추가
		list.add("egg");
		list.add("tree");

		int size = list.size(); // 저장된 총 객체 수 얻기
		String skill = list.get(0); // 0번 인덱스의 객체 얻기

		for(int i = 0; i < list.size(); i++){// 저장된 총 객체 수 만큼 조회
			String str = list.get(i);
			System.out.println(i + ":" + str);
		}

		list.remove(0); // 0번 인덱스 객체 삭제
	}
}

 

 

 

📌 LinkedList

LinkedList는 데이터가 불연속적으로 존재하지만 주소값을 참조하는 방법으로 서로 연결(link)되어 있다. 아래 그림을 보면 바로 이해가 된다.

LinkedList 에 담겨있는 요소들을 노드(node)라고 하는데 노드는 [이전 노드 주소값 + 객체 + 다음 노드 주소값] 으로 구성되어 있다. 

 

📌 ArrayList 와 LinkedList 의 비교

  • LinkedList 는 추가, 삭제에 유리하다.
  • ArrayList 는 검색에 유리하다.

 

LinkedList는 추가, 삭제 시 참조하는 주소값만 변경해주면 된다. 하지만 ArrayList 는 추가, 삭제 시 해당 인덱스의 자리를 만들어주거나(=추가) 메꾸기 위해(=삭제) 많은 요소들을 복사하여 이동시켜줘야 한다. 따라서 ArrayList가 추가, 삭제 시 유리하다. 

하지만 검색 시, ArrayList는 인덱스로 관리할 수 있기 때문에 배열의 주소 + n * 데이터 타입의 크기 로 빠르게 접근이 가능하지만, LinkedList 는 하나씩 주소를 따라 순회해야 하므로 느리다. 

 

 

 

Set

<순서X,중복X>

Set은 '집합'의 의미이다. 대표적인 클래스로 HashSet, TreeSet 이 있다.

 

📌 HashSet

Set 의 가장 기본형태의 클래스이다. <순서X, 중복X>인 집합이다.

import java.util.*;

public class Main {

    public static void main(String[] args) {
        HashSet<String > languages = new HashSet<String>();

        languages.add("Java");
        languages.add("Python");
        languages.add("Javascript");
        languages.add("C++");
        languages.add("Kotlin");
        languages.add("Ruby");
        languages.add("Java");

        Iterator it = languages.iterator();

        while(it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

 

 

📌 TreeSet

이진 탐색 트리(Binary Search Tree)이다. 이진 탐색 트리는 하나의 부모 노드가 최대 두 개의 자식 노드와 연결되는 이진 트리(Binary Tree)의 일종으로, 정렬검색에 특화된 자료구조이다. 최상위 노드를 '루트'라고 한다. 모든 왼쪽 자식의 값이 부모보다 작고, 모든 오른쪽 자식의 값이 루트나 부모보다 큰 값을 가지는 특성이 있다.

이진 탐색 트리

import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        TreeSet<String> workers = new TreeSet<>();

        workers.add("Lee Java");
        workers.add("Park Hacker");
        workers.add("Kim Coding");

        System.out.println(workers);
        System.out.println(workers.first());
        System.out.println(workers.last());
        System.out.println(workers.higher("Lee"));
        System.out.println(workers.subSet("Kim", "Park"));
    }

}

출력값을 보면 오름차순으로 정렬된 것을 확인할 수 있다. TreeSet의 기본 정렬이 오름차순이기 때문이다.

 

 

 

Map

<순서X,Key중복X>

<Key, Value>쌍으로 구성된 Entry 객체를 저장하는 구조이다. map 이라는 단어의 의미는 '매핑(mapping)'의 의미로 Key 와 Value를 매핑한다는 뜻이다. 기존의 key와 동일한 값의 key로 add하면 해당 key의 value값만 업데이트 된다. 

Map 인터페이스를 구현한 클래스에는 HashMap, HashTable, TreeMap, SortedMap 등이 있다.

 

다음은 Map 인터페이스의 메소드들이다.

기능 리턴 타입 메소드 설명
객체 추가      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() 저장된 키-값 쌍의 총 갯수를 리턴
Collection values() 저장된 모든 값을 Collection에 담아서 리턴
객체 삭제 void clear() 모든 Map.Entry(키와 값)을 삭제
Object remove(Object key) 주어진 키와 일치하는 Map.Entry를 삭제하고 값을 리턴

 

 

📌 HashMap

Map의 가장 기본적인 형태의 클래스이다. 아래 그림과 같이 Key 와 Value 로 구성된 Entry 객체를 저장한다.

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

 

HashMap의 키로 사용할 객체는 hashCode()equals() 메서드를 재정의해서 동등 객체가 될 조건을 정해야 한다. 동등 객체, 즉 동일한 키가 될 조건은 HashSet과 동일하다. hashCode()의 리턴값이 같아야 하고, equals()메서드가 true를 리턴해야 한다. 이렇듯 HashMap은 이름 그대로 해싱(Hashing)을 사용하기 때문에 많은 양의 데이터를 검색하는 데 있어서 뛰어난 성능을 보인다. 주로 Key 타입은 String을 많이 사용하는데, String은 문자열이 같을 경우 동등 객체가 될 수 있도록, hashcode()equals() 메서드가 재정의 되어 있다.

 

HashMap을 생성하기 위해서는 Key 타입과 Value 타입을 파라미터로 주고 기본 생성자를 호출하면 된다. 코드로 표현하면 다음과 같다.

Map<Key, Value> map = new HashMap<Key, Value>();
Map<Key, Value> map = new HashMap<Key, Value>(capacity, loadFactor);

Key와 Value의 타입은 기본 타입(byte, short, int, float, double, boolean, char)을 사용할 수 없고, 클래스 및 인터페이스 타입만 가능하다. 

 

다음은 HashMap의 사용 예제이다.

package com.company;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();

        //객체 저장
        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)로 점수(value)검색
        System.out.println("///");

        //객체를 하나씩 처리
        Set<String> keySet = map.keySet(); //Set 컬렉션으로 이름(key) 얻기

        //반복해서 이름(key)를 얻고, 값을 Map에서 얻어냄
        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("///");

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

        //객체를 하나씩 처리
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
        //Set 컬렉션으로 Map.Entry 얻기
        Iterator<Map.Entry<String, Integer>> entryIterator = entrySet.iterator();

        //반복해서 Map.Entry를 얻고 키와 값을 얻어냄
        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();
    }
}

/*

총 entry 수: 5
파이리 : 65
///
야도란 : 75
꼬부기 : 95
파이리 : 65
피카츄 : 85
피존투 : 15
///
총 entry 수: 4
야도란 : 75
꼬부기 : 95
파이리 : 65
피카츄 : 85

*/

위 코드에서 주의할 점은, Map은 Key와 Value를 쌍으로 저장하기 때문에 iterator() 를 직접 호출할 수 없다는 점이다. 그 대신 keySet() 이나 entrySet() 메서드를 이용해 Set 형태로 반환된 컬렉션에 iterator() 를 호출해야 한다.

 

[참고1] HashMap은 키(key)와 값(value)에 null 값을 허용하지만, Hashtable은 null 값을 허용하지 않는다.

[참고2] Hashtable은 과거부터 쓰던 클래스이나 호환성을 위해 아직 남아있는 것.(Vector 클래스처럼..)

 

 

 

Comparator 와 Comparable

Comparator와 Comparable은 자바에서 컬렉션을 정렬하기 위해 제공하는 인터페이스들이다. 객체들 간 크기를 비교하여 오름차순 등으로 정렬할 수 있다. 두 인터페이스의 주요 차이점은, Comparable 비교 대상(매개 변수)과 자기 자신을 비교하고, Comparator 매개 변수인 두 객체를 비교한다는 것이다. 

 

📌 Comparable

Comparable 인터페이스는 compareTo() 메소드를 사용해 객체를 정렬한다. Integer, String, File, Date와 같은 클래스에서는 자체적으로 Comparable 인터페이스를 구현하여 인스턴스 간 크기를 비교하고 있다. 기본 정렬은 오름차순이다. compareTo() 메소드에서는 비교하는 두 객체가 같으면 0, 작으면 음수, 크면 양수를 반환한다.

 

어떤 컬렉션 클래스를 사용하는 게 좋을까요? - 가이드