본문 바로가기
Java

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

by jphwany 2022. 6. 1.

Java 컬렉션 프레임워크 구조

제네릭 부분에서도 언급한 거지만
배열을 예시로 들어보면

배열을 사용하는데 있어서 여러 비효율적인 문제가 생기는데
크기가 고정적이라는 치명적인 단점이 있다

배열의 크기는 생성할 때 결정되고 그 크기를 넘어가게 되면 더는 데이터를 저장할 수 없다
그리고 데이터를 삭제하기라도 하면 그 인덱스 데이터가 비어있어서 메모리의 낭비가 굉장히 심하다

그래서 익히 알려져있는 여러 자료구조를 바탕으로 객체나 데이터들을 효율적으로 관리할 수 있는 자료구조들
만들어놓은 이 라이브러리를 컬렉션 프레임 워크라고 한다


컬렉션 프레임워크의 주요 인터페이스는 List, Set, Map 정도가 있다

List, Set은 객체 추가, 삭제, 검색하는 방법에 공통점이 많다
그래서 이 인터페이스들의 공통된 메소드만 모아서 컬렉션 인터페이스로 정의해두고 있는 것이다

Map은 키와 값을 하나의 쌍으로 묶어서 관리하는 구조로
List, Set과는 사용법이 달라서 독립된 인터페이스로 분리되어 있다


인터페이스 특징 구현 클래스


List : 순서를 유지하고 저장, 중복저장 가능 ArrayList, Vector, Stack, LinkedList 등

Set : 순서를 유지하지 않고 저장, 중복저장 불가능 HashSet, TreeSet 등

Map : 키(key)와 값(value)의 쌍으로 저장, 순서 유지하지 않음, 키는 중복저장 불가
HashMap, Hashtable, TreeMap, Properties 등

List와 Set이 포함된 컬렉션 인터페이스에서 제공하는 주요 메소드

기능 리턴타입 메소드 설명
객체 추가 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) 주어진 배열에 컬렉션의 객체를 저장해서 반환

List<E>

객체를 일렬로 늘어놓은 구조

객체를 인덱스로 관리하기 때문에 객체를 저장하면 자동으로 인덱스가 부여되며
이 인덱스로 객체 검색, 삭제할 수 있는 기능을 제공한다

기능 리턴 타입 메소드 설명
객체 추가 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


List 인터페이스를 구현한 클래스, 컬렉션 프레임워크에서 가장 빈번하게 사용된다
기능적으로는 Vector와 동일하지만 기존 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에 객체를 추가하면 인덱스가 0부터 차례로 저장된다
특정 인데스 객체를 제거하게 된다면 바로 뒤 인덱스부터 마지막 인덱스까지 모두 앞으로 1씩 당겨진다

import java.util.ArrayList;
import java.util.List;

public class ArrayListExample {
    public static void main(String[] args) {
//      List<String> list = new ArrayList<String>();
//      Explicit type argument String can be replaced with <>
//      중복되니까 생략해도 된다 String
        List<String> list = new ArrayList<>();
        list.add("jp");  //String 객체 저장/추가
        list.add("hwany");
        list.add("dev");
        list.add("abcdefg");
        list.add("qwertyasdfg");

        int ls_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);
        } // 저장된 총 객체 수 만큼 조회, 출력
    }
}


 

LinkedList


데이터를 효율적으로 추가, 삭제, 변경하기 위해 사용

배열은 모든 데이터가 연속적으로 존재하지만
이건 불연속적으로 존재하며, 그 대신 데이터가 서로 연결 되어있다

LinkedList 각 요소 노드들은 자신과 연결된 요소의 주소값과 데이터로 구성되어있어서

데이터를 삭제하려고 한다면 삭제하려고 하는 요소의 이전 요소가 삭제하고자 하는 요소의 다음 요소
참조하도록 변경하면 된다 

말이 어려운데 그냥 링크를 끊어버린다는 말이다

배열처럼 데이터를 이동하기위해 복사 할 필요가 없어서 처리 속도가 빠르다

데이터 추가할 때도 삭제와 마찬가지로 하면 된다 
링크를 이어준다

이렇게 보면 ArrayList와 LinkedList는 비슷한 것 같지만
차이점이 있는데

출처 :&nbsp;&nbsp;https://honey8dukes.tistory.com/77


ArrayList에 데이터를 추가, 삭제하려면 다른 데이터를 복사해서 이동해야 한다

배열에 객체를 순차적으로 저장할 땐 이동하지 않아도 돼서 작업 속도가 빠른 편이지만
중간에 위치한 객체를 추가나 삭제할 경우엔 데이터 이동이 많이 일어나기 때문에 속도가 느려진다

하지만 인덱스가 n인 요소의 주소값을 얻기 위해서 배열주소 + n * 데이터타입크기  를 계산해서 
데이터에 빠르게 접근할 수 있기 때문에 검색(읽기) 에는 유리한 부분이 있다

LinkedList는 그 반대인데

출처 :&nbsp;https://velog.io/@chicori3/Java-%EC%BB%AC%EB%A0%89%EC%85%98-LinkedList


데이터 추가, 삭제 할 때 참조만 변경해주면 되기 때문에
다른 데이터를 복사할 필요가 없어서 ArrayList에 비해 성능이 빠르다

그렇지만 데이터 검색할 땐 노드 별로 처음부터 순회해야 하기 때문에 좋지 않다

정리해보면,
데이터를 자주 수정한다면 LinkedList
그렇지 않고 데이터 개수가 변하지 않으면 ArrayList 를 사용하면 된다

import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        // LinkedList list = new LinkedList();
        // 타입 미설정 Object로 선언
        // LinkedList는 ArrayList와 선언이 같지만
        // 초기의 크기를 미리 생성할 수 없다
        // 위와 같이 선언하고 내부에 임의 값을 넣어 사용할 수 있지만
        // 이럴 경우 내부 값 사용할 때 캐스팅을 해야하고
        // 잘못된 타입으로 캐스팅이라도 하는 날에는 에러가 발생하게된다
        // 그래서 제네릭 요소를 사용을 한다

        // LinkedList<Student> list = new LinkedList<>();
        // <> 안에 다른 컬렉션 객체를 가져올 수도 있다

        LinkedList<Integer> list = new LinkedList<>();
        // Integer 타입의 LinkedList list 선언

        list.add(0);  // 0 추가
        list.add(1,5); // 1번째 인덱스 뒤 5 추가
        System.out.println(list);
        // [0, 5]  출력

        list.addFirst(-1); // 노드 맨 앞에 -1 추가
        list.addLast(7); // 노드 맨 마지막에 7 추가
        System.out.println(list);
        // [-1, 0, 5, 7] 출력

        list.remove();  // 맨 앞 노드 삭제
        System.out.println(list);
        // [0, 5, 7] 출력

        list.remove(1); // 1번째 인덱스 노드 삭제
        System.out.println(list);
        // [0, 7] 출력,   1번째 인덱스인 5를 삭제했기 때문

        list.removeFirst(); // 맨 앞 노드 삭제
        System.out.println(list);
        // [7] 출력,  맨 앞 0을 삭제했기 때문

        list.removeLast(); // 맨 뒤 노드 삭제
        System.out.println(list);
        // []  출력, 남아있는게 7 밖에 없지만 아무튼 맨 뒤 노드 삭제하니까 빈배열 출력
    }
}

 

Iterator


자바 컬렉션 프레임워크에선 컬렉션에 저장된 요소를 읽어오는 방법을 Iterator 인터페이스로 표준화 하고 있다

Iterator 인터페이스를 구현한 클래스의 인스턴스를 반환하는 iterator() 메소드를 정의해서 각 요소에 접근하게끔 한다
그래서 컬렉션 인터페이스를 상속받는 List와 Set 인터페이스에서도 iterator() 메소드를 사용할 수 있다

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

  
next()메소드를 사용하기 전에 먼저, 가져올 객체가 있는지 확인부터 하는 게 좋다
hasNext()는 가져올 객체가 있으면 true를 리턴하는데 true 리턴될 때 next() 메소드를 사용한다

import java.util.*;

public class IteratorEx {
	public static void main(String[] args) {
    	Vector<Integer> v = new Vector<Integer>();
	   // 정수 값만 다루는 제네릭 벡터 생성
        v.add(5);
        v.add(4);
        v.add(-1);
        v.add(2, 100);
	// 2번째 인덱스에 100 삽입
            // 0번째 : 5
            // 1번째 : 4
            // 2번째 : 100
            // 3번째 : -1
        
        Iterator<Integer> it = v.iterator(); // Iterator 객체 얻기
        
        
        // Iterator를 이용, 모든 정수 출력
        while(it.hasNext()) {
      	  int n = it.next();
          System.out.println(n);
        }
        
        
        // Iterator를 이용, 모든 정수 합
        int sum = 0;
        it = v.iterator(); // Iterator 객체 얻기
        while(it.hasNext()) {
        	int n = it.next();
            sum += n;
        }
        
        System.out.println("벡터에 있는 정수 합 :  " + sum);
    }
}

출력 :


Set<E>


요소의 중복을 허용하지 않고 저장 순서를 유지하지 않는다
대표적 클래스는 HashSet, TreeSet이 있다

HashSet


중복체크할 때 많이 활용되는 Set 인터페이스 구현한 대표적 컬렉션 클래스

import java.util.HashSet;
import java.util.Iterator;

public class HashSet_ {
    public static void main(String[] args) {
        HashSet set = new HashSet();


        // HashSet은 객체 저장하기 전, 기존에 같은게 있는지 확인한다
        // 없으면 저장, 중복이 있으면 저장 X
        set.add("가나다");
        set.add("가나다");

        set.add("jphwany");
        set.add("dev 95");
        set.add("Back-End");

        Iterator it = set.iterator();

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

출력 :


 

TreeSet


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

하나의 부모 노드가 최대 2개 자식 노드랑 연결되는 형태

정렬과 검색에 특화된 자료구조
오른쪽으로 오름차순

이진탐색트리에 관해선 자료구조 학습 때 자세히 다루도록 한다

import java.util.TreeSet;

public class TreeSetEx {
    public static void main(String[] args) {
        TreeSet<Integer> numbers = new TreeSet<>();

        numbers.add(21);
        numbers.add(15);
        numbers.add(9);
        numbers.add(6);
        numbers.add(3);
        numbers.add(1);


        System.out.println(numbers);  // 9 15 21 오름차순
        System.out.println(numbers.first()); // 1
        System.out.println(numbers.last()); // 21

    }
}

오름차순 정렬임을 확인할 수 있다




Comparable, Comparator


컬렉션을 정렬하기 위해 자바에서 제공하는 인터페이스

이 인터페이스들의 차이점은 

Comparable은 비교 대상(매개변수)과 자기 자신을 비교하는 것이고
Comparator는 매개변수인 두 객체를 비교하는 것이다

Comparable 인터페이스는 CompareTo() 메소드를 사용해서 객체를 정렬한다
이 말은, CompareTo() 메소드를 오버라이딩해야 한다는 말이기도 하다 (인터페이스니까)

Integer와 String, File, Date 와 같은 클래스에서는 자체적으로 Comparable 인터페이스를 구현해서
인스턴스 간 크기를 비교한다. 기본 정렬 순서는 오름차순

compareTo() 는 비교하는 두 객체가 같으면 0, 비교할 객체가 주어진 객체보다 작으면 음수, 크면 양수 반환한다

Comparator는 메소드가 굉장히 많지만 실질적으로는 Compare(T o1, T o2)이다


 

Map<K, V>


Map 인터페이스는 Key(키)와 Value(값) 로 구성된 Entry 객체를 저장하는 구조를 가지고 있다
map이란 것이 어떤 두 데이터를 연결한다는 의미다
키와 값에 해당하는 객체 두 가지를 연결한다는 그런 말이다

키는 중복 저장될 수 없지만, 값은 중복 저장이 가능하다
만약 기존 저장된 키와 동일한 키로 값을 저장하게된다면
기존의 값은 없어지고 새로운 값으로 대치된다


Map 인터페이스를 구현한 클래스엔 HashMap, Hashtable, TreeMap, SortedMap 등이 있다

아래 표는 Map 인터페이스를 구현한 클래스에서 공통적으로 사용 가능한 메소드들이다
List는 인덱스 기준으로 관리되지만
Map은 Key로 객체를 관리하기 때문에 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() 저장된 키-값 쌍의 총 개수를 리턴
Collection  values() 저장된 모든 값을 Collection에 담아서 리턴
객체 삭제 void clear()  모든 Map.Entry(키와 값)을 삭제
Object remove(Object key) 주어진 키와 일치하는 Map.Entry를 삭제하고 값을 리턴

HashMap

 

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



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

HashMap의 키로 사용할 객체는 hashCode()와 equals() 메소드를 재정의해서
동등 객체가 될 조건을 정해야 한다

동등 객체, 즉 동일한 키가 될 조건은 HashSet과 동일
hashCode()의 리턴값이 같아야 하고, equals() 메소드가 true를 리턴해야 한다

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


주로 키 타입은 String을 많이 사용하는데, String은 문자열이 같을 경우 동등 객체가 될 수 있도록
hashcode()와 equals() 메소드가 재정의 되어 있다
HashMap을 생성하기 위해서는 키 타입과 값 타입을 매개변수로 주고 기본 생성자를 호출하면 된다

Map<Key, Value> map = new HashMap<Key, Value>();
Map<Key, Value> map = new HashMap<Key, Value>(capacity, loadFactor);
// capacity : 해시테이블의 버킷 수
// loadFactor : 해시테이블 버킷이 얼마나 차 있는지 보여주는 수치
// default값은 capacity 16, loadFactor 0.75

 

키, 값의 타입은 기본타입을 사용할 수 없고 클래스, 인터페이스 타입만 가능하다

또한, HashMap은 키와 값에 null을 허용하지만 Hashtable은 null을 허용하지 않는다

import java.util.HashMap;
import java.util.Map;

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

        map.put("가", 10);
        map.put("나", 15);
        map.put("다", 20);
        map.put("라", 25);
        map.put("라", 30);
        // Key가 중복되면 이전 키 값을 새롭게 집어넣은 것으로 업데이트한다

        System.out.println(map);
// 출력  {가=10, 다=20, 나=15, 라=30}
        System.out.print(map.get("가"));
        System.out.print(map.get("나"));
        System.out.print(map.get("다"));
        System.out.print(map.get("라"));
        // Key에 해당하는 값을 출력
    }
}

 

 

'Java' 카테고리의 다른 글

[Java 심화] Enum  (0) 2022.06.01
[Java 컬렉션] 내부 클래스 (Inner Class)  (0) 2022.06.01
[Java 컬렉션] 제네릭 (Generic)  (0) 2022.05.29
[Java OOP 심화] 다형성(Polymorphism)  (0) 2022.05.29
[Java OOP 심화] 추상화 (Abstract)  (0) 2022.05.29

댓글