top bar

글 목록

2015년 5월 24일 일요일

[JAVA] 컨테이너 - 기초

개요



 이번엔 벼르고 별러왔던 자바 컨테이너(Container)에 대한 정리를 하려고 한다. 객체의 '저장' 이라는 관점에서 우리가 흔히 생각하는 것은 배열(array)이다. 특히나 원시(primitive)타입의 값들을 저장하여 다룰때 배열을 많이 사용한다.

 하지만 코드를 작성하는 시점에서, 우리가 필요홀 하는 객체들이 얼마나 되는지, 또는 객체 저장을 위해 더 복잡한 방법이 필요한지 알 수 없다.  따라서 '크기가 한번 정해지면 바꿀 수 없다' 라고 하는 것은 배열의 가장 큰 단점이며, 그로인한 제약은 상당히 크다.

 이러한 문제의 해결 방안으로 java.util 라이브러리에는 컨테이너(container) 클래스 들이 있으며, 그것의 기본 타입들은 List, Set, Queue, Map 이다.

 이번 포스트에는 컨테이너의 기본적인 원리들을 살펴보고, 다음 포스트에 본격적인 컨테이너의 Usage를 정리하려고 한다.


1. Generics와 컨테이너



아래 코드를 보자.

import java.util.ArrayList;

class Apple {

    private static long counter;
    private final long id = counter++;

    public long id() {
        return id;
    }
}

class Orange {
}

/**
 * @author Jupyo Hong
 */
public class ApplesAndOrangesWithoutGenerics {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        
        @SuppressWarnings("rawtypes")
        ArrayList apples = new ArrayList();
        
        for(int i = 0; i < 3; i++) {
            apples.add(new Apple());
        }
        
        // 같은 컨테이너에 다른 타입의 객체(Orange)를 추가해도 막지 않는다
        apples.add(new Orange());
        
        for(int i = 0; i < apples.size(); i++) {
            // Orange 타입의 요소를 가져와 id() 메서드를 호출할때 
            // RuntimeException 이 발생한다.
            ((Apple)apples.get(i)).id(); 
        }
    }
}

위 코드의 주석처럼, ArrayList 컨테이너를 생성할때 Generic을 지정하지 않으면, 아무 Type 이나 객체를 저장할 수 있다. 이는 당연히 두번째 for 문에서처럼 Apple 타입으로 캐스팅 후에 id() 메서드를 호출할때 발생하는 RuntimeException 때문에 문제가 된다. 왜냐하면 Orange 클래스에는 id() 메서드가 없기 때문이다.

따라서 아래와 같이 제네릭을 지정한다

ArrayList<Apple> apples = new ArrayList<Apple>();

apples.add(new Orange()); // Compile Error 발생!

제네릭을 지정하면, 지정한 Type외에 다른타입을 추가하려고 할때 Compile 시점에서 Error를 발생시켜 실수를 방지 할 수 있다. 또한 컨테이너에서 요소를 꺼낼때도, 캐스팅을 할 필요없이 제네릭으로 지정한 Type으로 알아서 캐스팅해준다. 아래와 같다.

ArrayList<apple> apples = new ArrayList<apple>();
        
for(int i=0; i<3; i++) {
    apples.add(new Apple());
}       
        
for(int i=0; i<apples.size(); i++) {
    System.out.println(apples.get(i).id()); // 캐스팅을 하지 않는다.
}

따라서 제네릭을 사용하면 컨테이너에 넣는 객체의 Type을 컴파일러가 확인해주고, 구문도 더 깔끔해진다.


2. 기본 개념들



 자바 컨테이너 라이브러리는 다음과 같은 두 개의 서로 다른 개념으로 나뉜다.

Collection
Map
하나 이상의 규칙이 적용되는 개별적인 요소들을 모아 놓은 것이다. List Sequencial 하게 객체들을 저장하며, Set은 중복요소를 가질 수 없다. QueueQueuing Discipline 에 의해 결정된 순서로 요소들을 산출 한다
(key)와 그에 대응되는 (value)의 쌍으로 구성되는 객체들을 모아 놓은 것으로, key를 사용하여 값을 검색할 수 있다. 연관 배열(associative array)이라고도 한다.


3. 컨테이너를 사용한 객체 저장과 출력



다음 코드는 Collection(List, Set)과 Map을 사용하여 객체를 저장하고, 콘솔로 출력하는 예제다. 코드와 출력결과를 잘 살펴보면 각 컨테이너의 특징을 알 수 있다.

public class PrintingContainers {
 
 static Collection fill(Collection<string> collection) {
  collection.add("dog");
  collection.add("rat");
  collection.add("cat");
  collection.add("dog");
  return collection;  
 }
 
 static Map fill(Map<String, String> map) {
  map.put("rat", "Fuzzy");
  map.put("cat", "Rags");
  map.put("dog", "Bosco");
  map.put("dog", "Spot");
  return map;
 }
 
 public static void main(String[] args) {
  
  System.out.println("ArrayList : \t" + fill(new ArrayList<string>()));
  System.out.println("LinkedList : \t" + fill(new LinkedList<string>()));
  System.out.println("HashSet : \t" + fill(new HashSet<string>()));
  System.out.println("TreeSet : \t" + fill(new TreeSet<string>()));
  System.out.println("LinkedHashSet : " + fill(new LinkedHashSet<string>()));
  System.out.println("HashMap : \t" + fill(new HashMap<String, String>()));
  System.out.println("TreeMap : \t" + fill(new TreeMap<String, String>()));
  System.out.println("LinkedHashMap : " + fill(new LinkedHashMap<String, String>()));
 }
}

## 출력결과
ArrayList :     [dog, rat, cat, dog]
LinkedList :    [dog, rat, cat, dog]
HashSet :       [dog, cat, rat]
TreeSet :       [cat, dog, rat]
LinkedHashSet : [dog, rat, cat]
HashMap :       {dog=Spot, cat=Rags, rat=Fuzzy}
TreeMap :       {cat=Rags, dog=Spot, rat=Fuzzy}
LinkedHashMap : {rat=Fuzzy, cat=Rags, dog=Spot}
ArrayList, LinkedList : 중복된 요소가 있으며 요소를 추가한 순서대로 출력된다.
HashSet : 중복이 허용되지 않았고, 순서가 보장되지 않았다.
TreeSet : 중복이 허용되지 않았고, 요소들이 오름차순으로 정렬되어 출력되었다.
HashMap : 키(key)와 값(value)가 '=' 로 묶여서 출력되었다. 순서를 보장하지 않는다.
TreeMap : HashMap과 같은 형식으로출력되며, key들이 정렬되어 출력된다.
LinkedHashMap : HashMap과 같은형식으로 출력되며, 추가된 순서를 보장한다.

 참고로 우리가 컨테이너를 쓰는 가장 큰 이유중 하나는 '크기를 지정할 필요가 없다' 라는 것이다. 이들 컨테이너는, 요소가 추가될 때마다 크기를 동적으로 증가시킨다.

댓글 1개: