Java

13. 컬렉션 프레임워크

문정훈 2022. 3. 30. 20:30
더보기

목차

1. 컬렉션 프레임 워크란?

2. List 컬렉션 - ArrayList, Vector, LinkedList

3. Set 컬렉션 - HashSet, TreeSet

4. Map 컬렉션 - HashMap, Hashtable, Propertise, TreeMap

1. 컬렉션 프레임 워크란? 

컬렉션 프레임워크는 배열을 사용 했을 때 문제점 때문에 사용한다고 볼 수 있다.
배열을 사용했을 때의 문제점


문제점1) 저장할 수 있는 객체 수가 배열을 생성할 때 결정되기 때문에 불특정 다수의 객체를 저장하기에는 문제가 있다.
문제점2) 객체를 삭제했을 경우 해당 인덱스가 비게 되어 낱알이 듬성듬성한 옥수수가 될 수 있다. 

컬렉션 뜻: 객체들을 vector와 같이 효을적으로 추가, 삭제, 검색할 수 있도록 객체를 수집해 저장하는 역할을 해주는 클래스 또는 인터페이스를 말한다. 
프레임워크 뜻 : 위의 클래스 또는 인터페이스들을 저장해 놓은 라이브러리를 말한다. 


 

 

2. List 컬렉션 - ArrayList, Vector, LinkedList

interface List<E> {
    boolean add(E e); // 객체릴 맨 끝에 저장
    void add(int index, E element); //index에 객체를 저장
    E get(int index); //index의 객체를 리턴함
    E remove(int index); //인덱스의 객체 삭제
}

List 컬렉션이란 List 인터페이스의 구현 클래스를 말하며 대표적으로 ArrayList, Vector, LinkedList  3개의 클래스가 있다. 3개는 사용 방법은 모두 똑같으나 코드의 실행 흐름에 맞는 클래스를 사용하면 된다. 예를 들어 단일 스레드 환경에선 ArrayList를 사용하고 멀티 스레드 환경에서는 Vector를 사용한다. 

 

2.1 class ArrayList<E> implements List<E> {}

public class ArrayList_Example {
	public static void main(String ar[]) {
    	List<String> list = new ArrayList<String>();
        list.add("A");
        list.add("B");
        list.add("C");
        
        String str = list.get(2); // index가 2인 리스트의 값을 가져옴
        list.remove(2); // index가 2인 리스트의 값을 제거함
        
        System.out.println(list.size()); // 현제 ArrayList의 크기를 알려줌
    }
}

ArrayList 클래스의 메소들은 동기화(synchronize)로 작성 되어 있지 않다. 그래서 싱글 스레드 상황에서 사용 되어야 하며 중간중간에 빈번한 객체 삽입이 이루어지는 상황이 아닌 순차적으로 들어가는 상황에서 사용 되는 것이 좋다. 

서로 다른 인덱스가 같은 객체를 가리킬 수 도 있다. 

 

2.2 class Vector<E> implement List<E> {}

Vector라는 컬렉션은 ArrayList와 그 사용법이 동일하다. 다른점이라면 메소드들이 동기화(synchronize) 메소드로 작성되어 있어 멀티 스레드 환경에 사용하는 것이 적합하다. 

 

2.3 class LinkedList<E> implements List<E>{}

LinkedList를 사용하는 방법 역시 위 두 컬렉션과 사용 방법이 동일하다.

이것은 객체를 인덱스로 관리하지 않는다. 특정 인덱스의 객체를 제거하거나 삭제를 한다 치면 앞 뒤 링크만 변경되고 나머지 링크는 변경되지 않는 체인 구조 이다. ArrayList는 인덱스가 1씩 모두 땡겨야 하므로 시간 복잡도가 Vector보다 크다.
만약 LinkedList로 get(3)을 할 때 idnex로 관리하지 않으며 체인 구조이기 때문에

처음 객체부터 하나하나 세어 3번째 객체를 리턴한다. 그래서 순차적으로 작업을 할 때는 이 방법은 인덱스 값이 인덱스를 의미하지 않고 처음부터 몇 번째 자리에 떨어진 객체 냐를 따지기 때문에 하나하나 세는 적업이 이루어져 순차적 작업은 ArrayList가 더 좋은 성능을 발휘한다.  


 

3. Set 컬렉션 - HashSet, (TreeSet)

3.1 class HashSet<E> implements Set<E> {}

interface Set<E> {
    boolean add(E e);
    Iterator<E> iterator();
    boolean remove(Object obj);
}

Set 인터페이스의 구현 클래스(컬렉션)들에는 HashSet, TreeSet이 존재합니다. 

이러한 컬렉션들은 집합의 특성과 같습니다. 즉 요소들 사이에 순서가 없으며 중복이 허용되지 않습니다. 

비유하면 구슬 주머니 안에 서로 달느 구슬들을 넣고 빼고하는 작업과 유사합니다. 

 

※ 객체를 넣을 때 같은 객체인 경우에는 중복해서 넣지 못하며, 같은 객체임을 식별하는 방법은 아래 두 단계를 거쳐 객체가 논리적으로 같은지를 검사한다. 

 

1단계) hashcode 비교

두 객체의 hashCode메소드를 호출해 이 코드 값을 비교한다. 따라서 논리적으로 동등한 객체인 경우에는 그 객체 (예를 들어 Member의 필드 값인 age와 name이 같으면 같은 객체로 보기로 한다.) 의 해시코드 메소드를 재정의 해야한다. 

 

2단계) equlas 메소드 비교

해시 코드가 같다면 equlas메소드를 호출해 서로 같은지를 비교한다. 두 필드 값이 같으면 논리적으로 같게 하기 위해 이 메소드 역시 재정의 해야한다.

class Member {
    public String str;
    public int age;
    
    public Member(String str, int age) {
    	this.str = str;
        thisa.age = age;
    }
    
    @Override
    public boolean equlas(Object obj) {
    	if(obj instanceof Member) {
        	Member member = (Member) obj;
            return member.str = str && member.age == age;
        }
        else {
        	return false;
        }
    }
    
    @Override
    public int hashCode() {
    	return str.hashCode() + age;
    }
}

 

 

HashSet을 사용한 예시 

public class HashSet_Example {
	public static void main(String ar[]) {
    	set.add(new Member("홍길동", 20);
        set.add(new Member("김길동", 21);
        
        Iterator<Member> it = set.iterator();
        while(it.hashNext()) {
        	Member m = it.next();
            String name = m.name;
            int age = m.age;
         	System.out.println("이름 : " + name + "나이 : " + age);
        }
    }
}

 

3.2 Iterator 인터페이스란?

interface Iterator<E> {
    boolean hashNext(); // set에서 가져올 객체가 있으면 true, 없으면 false를 반환한다. 
    E next(); // set 컬렉션에서 하나의 객체를 가져와 리턴한다. 
    void remove(); // set 컬렉션에서 객체를 제거한다. 
}

intreface Set<E> implemnets Set<E> {
    @Override
    Iterator<E> iterator();
    // HashSet이라는 Set 컬렉션에는 iterator()라는 메소드가 있다. 이 Iterator는 HashSet에 담긴
    // 객체에 접근할 수 있는 클래스라고 생각하면 된다. 
}

위와 같은 형태로 Iterator 클래스를 사용할 수 있도록 iterator 메소드를 제공한다.  iterator 메소드는 그 객체에 Set이라는 구슬 주머니에 Set 요소라는 구슬에 접근할 수 있는 Iterator객체를 생성시켜주는 역할을 한다.

이 메소드가 있어야 반복자를 객체를 얻을 수 있다고 생각하면 된다.

이 iterator 메소드는 Set인터페이스와 List인터페이스에도 선언되어 있다. 

 

※ 참고

iterator메소드는 List와 Set 계열에는 모두 존재 ,Collections, List, Set, NavigableSet 인터페이스에 모두 정의되어 있다. Collections이 최상위 인터페이스인데 이 Collection 인터페이스에 추상 메소드로 iterator이 정의되어있다. 

 

 

3.3 TreeSet

TreeSet은 아래 5. 에서 정리하였음..


 

 

4. Map 컬렉션 - HashMap, Hashtable,  Propertise, (TreeMap)

4.1 Map 인터페이스 

이 컬렉션은 키와 값으로 구성된 Entry객체를 저장하는 구조 이다. 키와 값은 모두 객체이며 키에는 중복 저장이 불가능하다. 만약 기존에 키와 동일한 키로 다른 값을 저장한다면 그 키 값의 기존의 값이 사라지고 새로운 값이 들어간다.  

 

List 컬렉션은 인덱스로 객체에 접근하였다면 Map컬렉션은 Key값으로 Value에 접근한다. 
이 때 Key역시 객체이며 키 값들에는 동일한 객체가 중복해서 들어가지 않는다.(서로 다른 두 키 값에 동일한 객체가 들어가지 않음) 그래서 동일한 객체임을 식별하기 위해 역시 해시코드와 equals 메소드를 비교해서 판별하고 Key 값에 들어갈 객체는 hashCode와 equals 두 메소드를 재정의 해야한다.

interface Map<K, V> {
    V put(K key, V value); // 주어진 키와 값을 추가한다. 
    V get(Object obj); //주어진 키에 있는 값을 리턴한다. 
    Set<K> KeySet(); // 모든 키를 Set 객체에 담아서 리턴한다. 
}

 

4.2 class HashMap<E> implements Map<E>{}, Hashtable

HashMap과 Hashtable 두 가지는 구조는 동일하다. 전자는 싱글 스레드에서 사용하기 적합하고 후자는 동기화 메소드로 작성된 차이 뿐이다. 그래서 멀티 스레드 환경에 적합하다. 

아래 코드는 HashMap을 사용하는 간단 예제이다. 

public class HashMap_Example {
	public static void main(String ar[]) {
    	Map<String, Integer> map = new HashMap<String, Integer>();
        
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        
        String v1 = map.get("key1"); // "value1" 문자열이 반환 된다. 
        String v2 = map.get("key2"); // "value2" 문자열이 반환 된다. 
        String v3 = map.get("key3"); // "value3" 문자열이 반환 된다. 
    }
}

 

 

4.3 class Properties extends Hashtable<Object,Object>

Properties는 해시테이블의 하위 클래스이다. 차이점은 키와 값을String 타입으로 제한한 것이다. 
이것은 애플리케이션의 옵션 정보, 데이터베이스의 연결정보 등을 위해 사용 된다. 


 

 

5. TreeSet

4.1 Set, SortSet, NavigableSet 인터페이스와 상속관계ㅍ

Set <- SortSet <- NavigableSet 이 순서대로 인터페이스들이 상속 관계를 이룬다. (SortSet은 Set을 상속함)
NavigableSet인터페이스의 추상 메소드를 정리해보면

interface NavigableSet<E> {
    E first(); // 트리에서 제일 낮은 객체(오름차순이면 젤 작은, 내림차순이면 젤 큰 수)
    E last(); // 트리에서 젤 높은 객체
    E lower(E e); //e 객체 바로 아래 객체 리턴
    E higher(E e); //e 객체 바로 위 객체를 리턴
    E floor(E e); // e와 동등한 객체가 있으면 리턴, 만약 없다면 바로 아래 객체 리턴
    E ceiling(E e); //e와 동등한 객체 리턴, 만약 없다면 바로 위 객체 리턴
    E pollFirst(); // 제일 낮은 객체를 꺼내오고 컬렉션에서 제거
    E pollLast(); // 제일 높은 객체를 꺼내오고 컬렉션에서 제거
}

 

4.2 TreeSet<E> implements NavigableSet<E>

TreeSet 클래스는 NavigableSet을 상속한 클래스이다. 그리고 저장 방식이 이진 트리 구조 이다. 

오름차순으로 이진 트리 구조를 형성해 객체를 저장한다. 
노드 값인 value 와 왼쪽과 오른쪽에는 자식 노드를 참조하기 위한 두 개의 변수로 구성된다.

 

TreeSet의 객체 생성시 NavigableSet<Integer> treeSet=new TreeSet<Intger>(); 이렇게 선언하는 것이 형태적으로 맞는데 

TreeSet<Integer> treeSet =new TreeSet<>(); 이렇게 변수 타입을 TreeSet으로 하는 이유는 객체를 찾거나 범위 검색과 관련된 메소드(TreeSet의 메소드)를 사용하기 위해서 이다.

NavigableSet의 메소드가 필요하다면 형 변환을 하면 될 것이다. 

 

4.3 TreeSet메소드인 descendingIterator(), descendingSet()메소드 

4.3.1 public <E>NavigableSet<E> descendingSet(){} 

위 메소드는 descendingSet의 메소드 몸체이다. TreeSet은 원래 데이터를 저장할 떄는 오름차순으로 트리 구조를 이루어 저장한다. 이 메소드는 TreeSet의 메소드이고 treeSet.descendingSet() 이렇게 선언하면 treeSet에 저장된 트리가 내림차순으로 바뀐다. 젤 아래에는 큰 수가 배치되게 된다. 그리고 리턴하는 타입은 NavigableSet타입이다. 

 

리턴 타입이 NavigableSet이란 것에 큰 의미를 두지 말자.. TreeSet타입으로 저장하던 NavigableSet이던 트리 구조는 동일하고 다만 차이점은 TreeSet의 메소드를 사용하지 못한단 것 뿐이다. 
descedingSet 메소드는 treeSet에 저장된 값들을 오름차순 이진 트리로 바꾸고 리턴 타입이 TreeSet이 아닌 NavigableSet이란 것이다. 

 

4.3.2 public <E>Iterator<E> desceningIterator(){}

위 메소드와 거의 비슷하다. 오름차순으로 정렬된 이진 트리를 내림차순으로 바꾸고 그 객체들에 접근 가능한 Iterator 반복자를 리턴하는 것이다. 주위할 점은 TreeSet객체들이 진짜 내림 차순으로 바뀌는 것은 아니다. 접근을 내림차순으로 하는 것이라 생각하면 된다.
 

 

4.3.3 연습

public class TreeSet_Example {
	public static void main(String ar[]) {
    	TreeSet<Integer> ts = new TreeSet();
        ts.add(1);
        ts.add(2);
        ts.add(3);
        ts.add(4);
        ts.add(5);
        System.out.println(ts.first());
        
        NavigableSet<Integer> nv = ts.decendingSet();
        System.out.println(nv.first());
    }
}

우선 객체(숫자)들을 저장하면 오름차순으로 저장되므로 1이 젤 아래 저장된다. 따라서ts.first메소드를 호출하면 1이 호출 된다. 
이제 descengingSet메소드를 호출해 트리를 내림차순으로 바꾸고 nv.first메소드를 호출하면 젤 아래에는 5가 저장되있으므로 5를 호출한다.


 

'Java' 카테고리의 다른 글

15. 람다식  (0) 2022.05.02
14. 제네릭  (0) 2022.03.30
12. 자바 API 클래스  (0) 2022.02.17
11. 자바 예외 처리(실행 예외, 예외 처리 코드, 사용자 정의 예외)  (0) 2022.02.16
10. 중첩 클래스  (0) 2022.02.06