컬렉션 프레임워크

박대운·2022년 11월 10일
0

Java

목록 보기
6/9

컬렉션 프레임워크

컬렉션이란 여러 데이터들의 집합을 의미합니다. 즉, 여러 데이터들을 그룹으로 묶어놓은 것을 컬렉션이라고 하며, 이러한 컬렉션을 다루는 데에 있어 편리한 메서드들을 미리 정의해놓은 것을 컬렉션 프레임워크라고 합니다.

컬렉션 프레임워크 구조

List

List는 데이터의 순서가 유지되며, 중복 저장이 가능한 컬렉션을 구현하는 데에 사용됩니다.

ArrayList, Vector, Stack, LinkedList 등이 List 인터페이스를 구현합니다.

Set

Set은 데이터의 순서가 유지되지 않으며, 중복 저장이 불가능한 컬렉션을 구현하는 데에 사용됩니다.

HashSet, TreeSet 등이 Set 인터페이스를 구현합니다.

Map

Map은 키(key)와 값(value)의 쌍으로 데이터를 저장하는 컬렉션을 구현하는 데에 사용됩니다.

데이터의 순서가 유지되지 않으며, 키는 값을 식별하기 위해 사용되므로 중복 저장이 불가능하지만, 값은 중복 저장이 가능합니다.

HashMap, HashTable, TreeMap, Properties 등

이 셋 중에서 List와 Set은 서로 공통점이 많아 위 그림과 같이 Collection이라는 인터페이스로 묶입니다. 즉, 이 둘의 공통점이 추출되어 추상화된 것이 바로 Collection이라는 인터페이스입니다.

Collection 인터페이스

List

List 인터페이스는 배열과 같이 객체를 일렬로 늘어놓은 구조를 가지고 있습니다. 객체를 인덱스로 관리하기 때문에 **객체를 저장하면 자동으로 인덱스가 부여되고, 인덱스로 객체를 검색, 추가, 삭제할 수 있는 등의 여러 기능을 제공합니다.
**

ArrayList

ArrayList는 저장 용량을 초과하여 객체들이 추가되면, 자동으로 저장용량이 늘어나게 됩니다. 또한, 리스트 계열 자료구조의 특성을 이어받아 데이터가 연속적으로 존재합니다. 즉, 데이터의 순서를 유지합니다.

예시)

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

ArrayList<String> container1 = new ArrayList<String>();
// String 타입의 객체를 저장하는 ArrayList 생성
// 초기 용량이 인자로 전달되지 않으면 기본적으로 10으로 지정됩니다. 

ArrayList<String> container2 = new ArrayList<String>(30);
// String 타입의 객체를 저장하는 ArrayList 생성
// 초기 용량을 30으로 지정하였습니다. 

ArrayList에 객체를 추가하면 인덱스 0부터 차례대로 저장됩니다. 그리고 특정 인덱스의 객체를 제거하면, 바로 뒤 인덱스부터 마지막 인덱스까지 모두 앞으로 1씩 당겨집니다.

예시)

public class ArrayListExample {
	public static void main(String[] args) {

		// ArrayList를 생성하여 list에 할당
		ArrayList<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 컬렉션은 데이터를 효율적으로 추가, 삭제, 변경하기 위해 사용합니다. 배열에는 모든 데이터가 연속적으로 존재하지만, LinkedList에는 불연속적으로 존재하며, 이 데이터는 서로 연결(link)되어 있습니다.

출처. 구글이미지

활용 예제)

    public static void main(String[] args) {

        // Linked List를 생성하여 list에 할당
        LinkedList<String> list = new LinkedList<String>();

        // 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);
    }

ArrayList와 LinkedList 차이

ArrayList

ArrayList에 객체를 순차적으로 저장할 때는 데이터를 이동하지 않아도 되므로 작업 속도가 빠르지만, 중간에 위치한 객체를 추가 및 삭제할 때에는 데이터 이동이 많이 일어나므로 속도가 저하됩니다.

반면 인덱스가 n인 요소의 주소값을 얻기 위해서는 배열의 주소 + n * 데이터 타입의 크기를 계산하여 데이터에 빠르게 접근이 가능하기 때문에 검색(읽기) 측면에서는 유리합니다.

장점

데이터를 순차적으로 추가하거나 삭제하는 경우

  1. 순차적으로 추가한다는 것은 0번 인덱스에서부터 데이터를 추가하는 것을 의미합니다.
  2. 순차적으로 삭제한다는 것은 마지막 인덱스에서부터 데이터를 삭제하는 것을 의미합니다.

데이터를 읽어들이는 경우

  1. 인덱스를 통해 바로 데이터에 접근할 수 있으므로 검색이 빠릅니다.

단점

중간에 데이터를 추가하거나, 중간에 위치하는 데이터를 삭제하는 경우

  1. 추가 또는 삭제 시, 해당 데이터의 뒤에 위치한 값들을 뒤로 밀어주거나 앞으로 당겨주어야 합니다.

LinkedList

위 그림의 Dog와 Cat 사이에 Mango라는 데이터를 추가하는 상황을 가정해봅시다. 이 때, 내부적으로 다음과 같은 동작이 이루어집니다.

  1. Fig 객체가 생성됩니다.

  2. Dog의 Next에 Fig의 주소값이 저장됩니다.
    이 때, Fig의 Prev에 Dog의 주소값이 저장됩니다.

  3. Fig의 Next에 Cat의 주소값이 저장됩니다.
    이 때, Cat의 Prev에 Fig의 주소값이 저장됩니다.

이처럼 LinkedList의 중간에 데이터를 추가하면, Next와 Prev에 저장되어 있는 주소값만 변경해주면 되므로, 각 요소들을 ArrayList처럼 뒤로 밀어내지 않아도 됩니다. 마찬가지로, 중간에 위치한 데이터를 삭제하는 경우에도 삭제한 데이터의 뒤에 위치하는 요소들을 앞으로 당기지 않아도 됩니다.

따라서 데이터를 중간에 추가하거나 삭제하는 경우, LinkedList는 ArrayList보다 빠른 속도를 보여줍니다.

하지만 데이터 검색할 때에는 시작 인덱스에서부터 찾고자하는 데이터까지 순차적으로 각 노드에 접근해야 하기 때문에 데이터 검색에 있어서는 ArrayList보다 상대적으로 속도가 느립니다.

결론적으로 데이터의 잦은 변경이 예상된다면 LinkedList를, 데이터의 개수가 변하지 않는다면 ArrayList를 사용하는 것이 좋습니다.

Iterator

Iterator는 직역하면 반복자라는 의미를 가지며, 컬렉션에 저장된 요소들을 순차적으로 읽어오는 역할을 합니다.

Collection 인터페이스를 상속받는 List와 Set 인터페이스를 구현한 클래스들은 iterator() 메서드를 사용할 수 있습니다.

List 에서 String 객체들을 반복해서 하나씩 가져오는 예시)

ArrayList<String> list = ...;
Iterator<String> iterator = list.iterator();

while(iterator.hasNext()) {     // 읽어올 다음 객체가 있다면 
	String str = iterator.next(); // next()를 통해 다음 객체를 읽어옵니다. 
	...
}

next() 메서드로 가져온 객체를 컬렉션에서 제거하고 싶다면 remove() 메서드를 호출하면 됩니다. next() 메서드는 컬렉션의 객체를 그저 읽어오는 메서드로, 실제 컬렉션에서 객체를 빼내는 것은 아닙니다. 하지만, remove() 메서드는 컬렉션에서 실제로 객체를 삭제합니다.

예시)

ArrayList<String> list = ...;
Iterator<String> iterator = list.iterator();

while(iterator.hasNext()){        // 다음 객체가 있다면
	String str = iterator.next();   // 객체를 읽어오고,
	if(str.equals("str과 같은 단어")){ // 조건에 부합한다면
		iterator.remove();            // 해당 객체를 컬렉션에서 제거합니다. 
	}
}

Set

Set은 요소의 중복을 허용하지 않고, 저장 순서를 유지하지 않는 컬렉션입니다. 대표적인 Set을 구현한 클래스에는 HashSet, TreeSet이 있습니다.

HashSet

HashSet은 Set 인터페이스를 구현한 가장 대표적인 컬렉션 클래스입니다. 따라서, Set 인터페이스의 특성을 그대로 물려받으므로 중복된 값을 허용하지 않으며, 저장 순서를 유지하지 않습니다.

활용 예시)

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());
        }
    }
}

반복자를 생성하여 반복자를 통해 HashSet를 순회하며 각 요소들을 출력하면 중복제거 할 수 있습니다.

TreeSet

TreeSet은 이진 탐색 트리 형태로 데이터를 저장합니다. 데이터의 중복 저장을 허용하지 않고 저장 순서를 유지하지 않는 Set 인터페이스의 특징은 그대로 유지됩니다.

class Node {
	Object element; // 객체의 주소값을 저장하는 참조변수 입니다. 
	Node left;      // 왼쪽 자식 노드의 주소값을 저장하는 참조변수입니다.
	Node right;     // 오른쪽 자식 노드의 주소값을 저장하는 참조변수입니다. 
}
import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {

				// TreeSet 생성
        TreeSet<String> workers = new TreeSet<>();

				// 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의 기본 정렬 방식이 오름차순이기 때문입니다.

Integer로 하게 되면 오름차순으로 정렬됩니다.

Map

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

Map을 사용할 때에 중요한 사실은 키는 중복 저장될 수 없지만, 값은 중복 저장이 가능하다는 것입니다. 이는 키의 역할이 값을 식별하는 것이기 때문입니다.
만약 기존에 저장된 키와 동일한 키로 값을 저장하면, 기존의 값이 새로운 값으로 대치됩니다.

HashMap

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

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

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

예시)

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

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

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

HashTable

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("입력하신 아이디가 존재하지 않습니다.");
        }
    }
}
profile
성장하는사람이 되자

0개의 댓글