차근차근/JAVA JSP

컬렉션 Collection

예쁜꽃이피었으면 2014. 11. 11. 11:42

http://www.java-school.net/java/10.php


Collection이란 같은 타입의 참조값을 여러개 저장하기 위한 자바 라이브러리이다.
"배열과 비슷한데 훨씬 더 편리하다." 라는 정도로 접근하자.
다음 그림은 Collection관련 주요 인터페이스의 계층관계를 보여 준다.

Collection Framework
컬렉션 클래스를 선택할 때 다음을 고려하자.1

  • Set - 중복을 허용하지 않고 순서도 가지지 않는다.
  • List - 중복을 허용하고 순서를 가진다.
  • Map - key 와 value의 형태로 저장한다.

다음은 자주 사용되는 컬렉션 클래스이다.
자바 2이후의 6개의 클래스와 자바 2이전의 2개 클래스를 보여준다.

인터페이스구현 클래스(자바 2)구현 클래스(자바 2이전)
SetHashSet
TreeSet
ListArrayListVector
LinkedList
MapHashMapProperties
TreeMap

이들 컬렉션 클래스들을 아래에서 예제로 다룬다.

컬렉션 클래스 예제

Set

예제는 Set인터페이스의 사용법을 보여 주고 있다.
HashSet을 생성하고 Set인터페이스의 add메소드를 사용하여 이름을 추가한다.
양효선은 중복 추가를 시도하고 있는데 Set은 중복을 허용하지 않으므로 추가되지 않는다.
System.out.println(set);로 확인할 수 있다.2
System.out.println(set);다음에 기존의 Set을 TreeSet으로 처리한다.
TreeSet를 표준 출력 메소드로 출력해보면 리스트가 정렬되어 있는 것을 확인할 수 있다.

SetExample.java
package net.java_school.collection;

import java.util.*;

public class SetExample {
	public static void main(String args[]) {
	  
		Set set = new HashSet();
		set.add("양효선");
		set.add("홍용표");
		set.add("황진호");
		set.add("김동진");
		set.add("전경수");
		set.add("양효선");
		    
		System.out.println(set);
		    
		Set sortedSet = new TreeSet(set);
		System.out.println(sortedSet);
	}
}

다음은 제네릭을 사용하여 변경한 코드다.

SetExample.java - 제네릭을 사용
package net.java_school.collection;

import java.util.*;

public class SetExample {
	public static void main(String args[]) {
	  
		Set<String> set = new HashSet<String>();
		set.add("양효선");
		set.add("홍용표");
		set.add("황진호");
		set.add("김동진");
		set.add("전경수");
		set.add("양효선");
		    
		System.out.println(set);
		    
		Set<String> sortedSet = new TreeSet<String>(set);
		System.out.println(sortedSet);
	}
}
C:\java\Collection\bin>java net.java_school.collection.SetExample
[홍용표, 김동진, 전경수, 양효선, 황진호]
[김동진, 양효선, 전경수, 홍용표, 황진호]

예제에서 다룬 컬렉션 클래스를 자바 문서에서 찾아보면 클래스 선언부에 <E>, <T>, <K, V>가 붙어있는 것을 볼 수 있다. 이런 인터페이스, 추상클래스, 클래스를 제네릭(Generic)이라 한다.
제네릭은 JDK 1.5부터 추가되었다.
<E>는 Element, <T>는 Type, <K, V> 는 Key, Value 의미한다.
이 기호를 이용하여 정해지지 않은 데이터 타입을 선언할 수 있다.
정해지지 않는 데이터 타입은 제네릭으로부터 객체가 생성될 때 결정된다.
다음 예는 계좌 클래스의 계좌 번호를 제네릭으로 만든 것이다.

package net.java_school.collection;

public class Account<T> {
	
	private T accountNo;//accountNo는 어떤 타입도 될 수 있다.
	
	public T getAccountNo() {
		return accountNo;
	}

	public void setAccountNo(T accountNo) {
		this.accountNo = accountNo;
	}

	public static void main(String[] args) {
		Account<String> ac1 = null;
		ac1 = new Account<String>();//계좌번호 데이터 타입은 String으로 결정
		ac1.setAccountNo("111-222-333");//문자열만 가능
		
		Account<Integer> ac2 = null;
		ac2 = new Account<Integer>();//계좌번호 데이터 타입은 Integer로 결정
		ac2.setAccountNo(111222333);//Integer만 가능(아래 랩퍼클래스 참조)
	}

}

List

List는 Collection인터페이스를 상속하며, 순서가 있고 중복을 허락한다.
List는 배열과 같이 방마다 차례대로 0부터 시작하는 인덱스 번지가 주어진다.
다음 예제는 가장 많이 사용되고 있는 ArrayList에 대한 예제이다.

ArrayListExample.java
package net.java_school.collection;

import java.util.ArrayList;

public class ArrayListExample {

	public static void main(String[] args) {
		ArrayList a = new ArrayList();
		
		a.add("장길산");
		a.add("홍길동");
		
		String hong = (String) a.get(1);//형변환 필요
		System.out.println(hong);
		
		//모든 요소를 출력하려면
		for (Object name : a) {//JDK 1.5부터 추가된 '확장 for문'
			System.out.print(name +"\t");
		}
	}

}

다음은 제네릭 ArrayList<E>를 사용한 코드이다.
제네릭을 사용하면 특정 데이터 타입의 참조값만 저장하게 되므로 값을 가져올 때 형변환이 필요없다는 사실에 주목해야 한다.3

ArrayListExample.java - 제네릭을 사용
package net.java_school.collection;

import java.util.ArrayList;

public class ArrayListExample {

	public static void main(String[] args) {
		ArrayList<String> a = new ArrayList<String>();
		
		a.add("장길산");
		a.add("홍길동");
		
		String hong = a.get(1);//형변환이 필요없다.
		System.out.println(hong);
		
		//'확장for문'에서 name의 데이터타입을 String둘 수 있다.
		for (String name : a) {
			System.out.print(name +"\t");
		}
	}

}
C:\java\Collection\bin>java net.java_school.collection.ArrayListExample
홍길동
장길산	홍길동

아래 예제는 List의 구현체중에 ArrayList와 LinkedList의 사용법을 비교하여 보여주고 있다.

ListExample.java
package net.java_school.collection;

import java.util.*;

public class ListExample {
	public static void main(String args[]) {
		List list = new ArrayList();
		    
		list.add("A");
		list.add("B");
		list.add("C");
		list.add("D");
		list.add("E");
		
		System.out.println(list);
		System.out.println("2: " + list.get(2));
		System.out.println("0: " + list.get(0));
		
		LinkedList linkedList = new LinkedList();
		
		linkedList.addFirst("A");
		linkedList.addFirst("B");
		linkedList.addFirst("C");
		linkedList.addFirst("D");
		linkedList.addFirst("E");
		    
		System.out.println(linkedList);
		linkedList.removeLast();
		linkedList.removeLast();
		    
		System.out.println(linkedList);
	    
	}
}

다음은 제네릭을 사용하여 변경한 코드이다.

ListExample.java
package net.java_school.collection;

import java.util.*;

public class ListExample {
	public static void main(String args[]) {
		List<String> list = new ArrayList<String>();
		    
		list.add("A");
		list.add("B");
		list.add("C");
		list.add("D");
		list.add("E");
		
		System.out.println(list);
		System.out.println("2: " + list.get(2));
		System.out.println("0: " + list.get(0));
		
		LinkedList<String> linkedList = new LinkedList<String>();
		
		linkedList.addFirst("A");
		linkedList.addFirst("B");
		linkedList.addFirst("C");
		linkedList.addFirst("D");
		linkedList.addFirst("E");
		    
		System.out.println(linkedList);
		linkedList.removeLast();
		linkedList.removeLast();
		    
		System.out.println(linkedList);
	    
	}
}
C:\java\Collection\bin>java net.java_school.collection.ListExample
[A, B, C, D, E]
2: C
0: A
[E, D, C, B, A]
[E, D, C]

Map

Map은 키(key)와 값(value)의 쌍으로 데이터를 저장한다.
다음 예제는 HashMap을 사용하고 있다.
끝부분에서 HashMap을 TreeMap으로 처리한다.
TreeMap은 데이터를 키값으로 정렬한다.

MapExample.java
package net.java_school.collection;

import java.util.*;

public class MapExample {
	public static void main(String args[]) {
	
		Map map = new HashMap();
		
		map.put("1", "양효션");
		map.put("2", "홍용표");
		map.put("3", "황진호");
		map.put("4", "김동진");
		map.put("5", "전경수");
		
		System.out.println(map);
		System.out.println((String) map.get("4"));//형변환 필요
		
		Map sortedMap = new TreeMap(map);
		System.out.println(sortedMap);
	
	}
}

다음은 제네릭 HashMap<K,V>과 TreeMap<K,V>을 사용하여 변경한 코드이다.

MapExample.java - 제네릭 사용
package net.java_school.collection;

import java.util.*;

public class MapExample {
	public static void main(String args[]) {
	
		Map<String,String> map = new HashMap<String,String>();
		
		map.put("1", "양효션");
		map.put("2", "홍용표");
		map.put("3", "황진호");
		map.put("4", "김동진");
		map.put("5", "전경수");
		
		System.out.println(map);
		System.out.println(map.get("4"));//형변환 필요없다!
		
		Map<String,String> sortedMap = new TreeMap<String,String>(map);
		System.out.println(sortedMap);
	
	}
}
C:\java\Collection\bin>java net.java_school.collection.MapExample
{3=황진호, 2=홍용표, 1=양효션, 5=전경수, 4=김동진}
김동진
{1=양효션, 2=홍용표, 3=황진호, 4=김동진, 5=전경수}

예제를 아래와 같이 바꾸어 테스트한다.
Integer는 int에 대응하는 랩퍼(Wrapper) 클래스이다.
Integer타입의 키값을 주면 HashMap도 정렬이 된다는 것을 확인할 수 있다.

MapExample.java
package net.java_school.collection;

import java.util.*;

public class MapExample {
	public static void main(String args[]) {
	
		Map<Integer,String> map = new HashMap<Integer,String>();
		
		map.put(1, "양효션");
		map.put(2, "홍용표");
		map.put(3, "황진호");
		map.put(4, "김동진");
		map.put(5, "전경수");
		
		System.out.println(map);
		System.out.println(map.get(4));
		
		Map<Integer,String> sortedMap = new TreeMap<Integer,String>(map);
		System.out.println(sortedMap);
	
	}
}
C:\java\Collection\bin>java net.java_school.collection.MapExample
{1=양효션, 2=홍용표, 3=황진호, 4=김동진, 5=전경수}
김동진
{1=양효션, 2=홍용표, 3=황진호, 4=김동진, 5=전경수}

Vector

과거에 자주 쓰였던 Vector에 관한 예제다.
현재는 Vector 대신에 ArrayList가 더 많이 사용되고 있다.4

VectorExample.java
package net.java_school.collection;

import java.util.*;

public class VectorExample {

	public static void main(String[] args) {
		Vector v = new Vector();
		    
		for (int i = 0; i < 10; i++) {
			v.addElement(String.valueOf(Math.random() * 100));
		}
		    
		for (int i = 0; i < 10; i++) {
			System.out.println(v.elementAt(i));//Object타입의 참조값을 반환
		}
	}
  
}

다음은 제네릭 Vector<E>을 사용하여 변경한 코드이다.

VectorExample.java - 제네릭 사용
package net.java_school.collection;

import java.util.*;

public class VectorExample {
	public static void main(String[] args) {
	
		Vector<String> v = new Vector<String>();
	
		for (int i = 0; i < 10; i++) {
			v.addElement(String.valueOf(Math.random() * 100));
		}
		
		for (int i = 0; i < 10; i++) {
			System.out.println(v.elementAt(i));//String타입의  참조값 반환
		}
	}
  
}
C:\java\Collection\bin>java net.java_school.collection.VectorExample
64.93767837163008
1.7024404924644077
56.445592597123806
23.41304656773643
92.55620070095163
41.6525553754475
47.39373268828609
83.84855063525016
67.34657837510855
41.04715452201211

Properties

자바에서 설정 파일로부터 값을 읽을 때 많이 사용하는 클래스이다.
키와 값의 쌍으로 데이터를 저장한다.

PropertiesStore.java

package net.java_school.collection;

import java.util.*;
import java.io.*;

public class PropertiesStore {
	public static void main(String[] args) {
	
		Properties prop = new Properties();
		prop.put("name", "장길산");
		prop.put("address", "황해도 구월산");
		
		try {
			prop.store(new FileOutputStream("test.properties"),"My Favorite Bandit");
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
}
PropertiesLoad.java
package net.java_school.collection;

import java.util.*;
import java.io.*;

public class PropertiesLoad {
	public static void main(String[] args) {
	
		Properties prop = new Properties();
		try {
			prop.load(new FileInputStream("test.properties"));
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		System.out.println(prop.getProperty("name"));
		System.out.println(prop.getProperty("address"));
	}
}
C:\java\Collection\bin>java net.java_school.collection.PropertiesStore

C:\java\Collection\bin>java net.java_school.collection.PropertiesLoad
장길산
황해도 구월산

PropertiesStore를 실행하면 파일시스템에 test.properties파일이 만들어진다.5
파일을 열어보면 다음과 같다.

test.properties
#My Favorite Bandit
#Thu Apr 10 13:07:41 KST 2014
address=\uD669\uD574\uB3C4 \uAD6C\uC6D4\uC0B0
name=\uC7A5\uAE38\uC0B0

예상과 달리 한글 부분이 이상한 문자로 되어 있다.
자바 프러퍼티는 자바 프로그램에서 설정에 관련된 부분에 이용하기 위해 만들어 졌지만 비영어권을 위한 배려는 하지 않은 것 같다.
한글은 프로퍼티 파일에서 자바에서 사용하는 유니 코드값으로 저장되어 있어야 한다.
이것이 우리로서는 자바 프로퍼티의 단점이다.

Enumeration 인터페이스

열거형 형태로 저장된 객체를 처음부터 끝까지 차례로 조회하는데 유용한 인터페이스이다.
메소드는 다음 2개가 전부이다.

hasMoreElements()
nextElement()

아래 코드 조각은 Vector<E> v 의 모든 요소를 출력한다.

for (Enumeration<E> e = v.elements(); e.hasMoreElements();) {
  System.out.println(e.nextElement());
}

이 코드를 이용하여 우리의 벡터 예제를 수정하면 다음과 같다.
성능으로만 보면 이 예제는 전 예제보다 떨어진다.

VectorExample.java - 제네릭, Enumeration 사용
package net.java_school.collection;

import java.util.*;

public class VectorExample {
	public static void main(String[] args) {
	
		Vector<String> v = new Vector<String>();
	
		for (int i = 0; i < 10; i++) {
			v.addElement(String.valueOf(Math.random() * 100));
		}
		
		for (Enumeration<String> e = v.elements(); e.hasMoreElements();) {
			System.out.println(e.nextElement());
		}
	}
  
}

Iterator 인터페이스

Collection인터페이스의 iterator()메소드는 Iterator를 리턴한다.6
Iterator는 Enumeration인터페이스와 비슷하나 Enumeration보다 나중에 만들어졌다.
Enumeration보다 메소드명이 간단하며 Enumeration과는 달리 값을 삭제하는 메소드가 추가되어 있다.

hasNext()
next()
remove()

랩퍼(Wrapper) 클래스

컬렉션은 참조값만을 담을 수 있다.
기본 자료형의 값은 컬렉션에 담을 수 없다.
기본 자료형의 값을 컬렉션에 담기 위해선는 랩퍼 클래스 이용하는 것이 답일 수 있다.
모든 기본 자료형에 대해서 그에 대응하는 랩퍼 클래스가 존재한다.
기본 자료형의 값을 멤버 변수의 값으로 저장하고 이 값 주위로 값을 가공하는 메소드들이 감싸고 있다 해서 랩퍼(Wrap:감싸다)클래스라고 불리는 것이다.

기본 자료형랩퍼 클래스
booleanBoolean
byteByte
charCharacter
shortShort
intInteger
longLong
floatFloat
doubleDouble
IntegerExample.java
package net.java_school.collection;

public class IntegerExample {

	public static void main(String[] args) {
		Integer a = new Integer(2000000000);//20억
		int intValue = a.intValue();
		System.out.println(intValue);

		byte byteValue = a.byteValue();
		System.out.println(byteValue);
		
		short shortValue = a.shortValue();
		System.out.println(shortValue);
		
		long longValue = a.longValue();
		System.out.println(longValue);
		
		float floatValue = a.floatValue();
		System.out.println(floatValue);
		
		double doubleValue = a.doubleValue();
		System.out.println(doubleValue);
		
		String strValue = a.toString();
		System.out.println(strValue);

		System.out.println(Integer.MAX_VALUE);
		System.out.println(Integer.MIN_VALUE);
		System.out.println(Integer.parseInt("1004"));

		/* 
		* 아래 코드는 컴파일러에 의해 
		* Integer b = new Integer(200000000);로 바뀐다. 
		* 이를 오토박싱이라고 한다.
		* 형변환이 아니다. 기본 자료형이 참조형으로 바뀌는 그런 형변환은 없다.		  
		*/
		Integer b = 2000000000;
		
		/* 
		 * == 은 항상 값이 같은가를 묻는다. 
		 * 참조값이 올 때는 같은 객체인지를 판단한다.
		*/
		if (a == b) {
			System.out.println("a == b true");
		} else {
			System.out.println("a == b false");
		}
		
		/* 
		 * a와 b가 같은 int값을 가지고 있는지 판단하기 위해선 Integer의 equals()메소드를 사용한다.
		 * Integer의 equals메소드는 관리하는 int값이 같은지를 판단하도록 Object의 equals메소드를 오버라이딩했다.
		 if (obj instanceof Integer) {
		    return value == ((Integer)obj).intValue();
		 }
		 return false;  
		 */
		if (a.equals(b)) {
			System.out.println("a.equals(b) true");
		} else {
			System.out.println("a.equals(b) false");
		}
		
		
		/*
		 * a와 b가 가진 값을 다양하게 판단하기 위해선
		 * Integer의 compareTo()메소드를 이용한다. 
		 */
		int check = a.compareTo(b);
		System.out.println(check);
		if (check == 0) {
			System.out.println("a(int) == b(int)");
		} else if (check < 0) {
			System.out.println("a(int) < b(int)");
		} else {
			System.out.println("a(int) > b(int)");
		}
		
		/*
		 * 오토박싱의 또다른 예제
		 * equals메소드의 인자값은 new Integer(c)의 참조값으로
		 * 컴파일 단계에서 변경된다.
		 */
		int c = 2000000000;
		if (a.equals(c)) {
			System.out.println("a.equals(c) true");
		} else {
			System.out.println("a.equals(c) false");
		}
		
		
		/*
		 * 오토언박싱
		 * a가 참조하는 Integer객체안에 있는 
		 * int값의 복사본이 객체 밖으로 나와 d에 대입
		 * 이 역시 컴파일 단계에서 
		 * int d = a.intValue();로 변경된다. 
		 */
		int d = a;
		System.out.println(d);
		
		
		/*
		* obj에는 1를 안에 내포하는 Integer객체의 참조값이 대입
		* 컴파일 단계에서 Object obj = new Integer(1);로 변경
		* Object타입의 레퍼런스로 Integer 고유의 메소드를 호출할 수 없다.
		* 호출하려면 형변환이 필요하다.
		*/
		Object obj = 1;
		System.out.println(obj);
		System.out.println(((Integer)obj).intValue());
		
	}

}

C:\java\Collection\bin>java net.java_school.collection.IntegerExample
2000000000
0
-27648
2000000000
2.0E9
2.0E9
2000000000
2147483647
-2147483648
1004
a == b false
a.equals(b) true
0
a(int) == b(int)
a.equals(c) true
2000000000
1
1

예제에서 보이는 오토박싱(AutoBoxing)과 오토언박싱(AutoUnboxing)은 개발의 편의성을 제공하기 위한 것이다.
유리로 사면이 막힌 상자에 동전을 유리를 통과해서 상자에 넣고 또 상자안의 동전을 상자 밖으로 통과시키는 마술을 본 적이 있을 것이다.
비유하면 상자 안에 동전을 넣는 것이 오토박싱, 상자 밖으로 동전을 꺼내는 것이 오토언박싱이다.

주석


  1. 컬렉션 클래스는 Set인터페이스를 구현했거나 List인터페이스를 구현했거나 Map인터페이스를 구현한 것으로 나눌 수 있다.
  2. HashSet의 toString()이 어떻게 오버라이딩하고 있는지 결과를 보면 확인할 수 있다.
    Set은 저장한 값이 순서가 없으므로 인덱스를 주고 값을 반환하는 메소드가 없다.
    저장한 값을 하나씩 가져올 수는 있는데 뒤에 학습할 Enumeration또는 Iterator인터페이스의 구현체를 반환하는 메소드를 이용해야 한다.
  3. JDK1.5이전에 컬렉션 클래스에서 값을 추가하는 메소드는 모두 매개변수 타입이 Object이다.
    모든 참조값을 저장할 수 있다고 하여 마치 자바의 강점처럼 설명되기도 했다.
    하지만 컬렉션을 사용할 때 다양한 참조값을 저장하는 경우가 많지 않을 뿐 아니라 값을 가져올 때 형변환을 해야 한다는 것이 악몽과 같다는 사실을 경험으로 깨닫고 JDK1.5부터 제네릭이 추가된 것이다.
  4. ArrayList와 Vector는 큰 차이가 있는데 ArrayList는 스레드 안전하지 않는데 반해 Vector는 스레드 안전하다.
    스레드 안전한 것과 안전하지 않는 것의 성능상 차이가 많으므로 스레드 안전한 것을 선택할 때는 그만한 이유가 반드시 필요하다.
    대부분의 경우 스레드 안전하지 않는 것을 선택하는 것이 옳다.
    참고로 JDBC에서 다루게 되는 JDBC커넥션 풀링관련 코드는 스레드 안전한 Vector를 사용한다.
  5. 이때 이클립스로 실행하는 경우와 콘솔에서 실행하는 경우 파일의 위치가 다르다.
    이클립스의 경우는 프로젝트 디렉토리에 생성된다.
    모호하다고 생각하면 위 코드에서 FileOutputStream과 FileInputStream의 생성자에 다음과 같이 파일 시스템의 전체 경로를 전달하면 된다.
    new FileOutputStream("C:/java/Collection/test.properties"), new FileInputStream("C:/java/Collection/test.properties")
  6. Set이나 List인터페이스를 구현한 모든 클래스에 iterator()메소드가 있어야 한다.
    왜냐하면 Set과 List인터페이스는 Collection인터페이스를 상속하기 때문이다.
    iterator()메소드가 Iterator타입을 리턴한다는 것에도 주목해야 하는데
    물론 Iterator가 인터페이스이기 때문에 실제 리턴되는 것은 Iterator를 구현한 구현 클래스로부터 생성된 객체일 것이다.
    하지만 우리는 그 구현체가 뭔지는 관심을 가지지 않아도 된다.
    그 구현체는 Iterator인터페이스를 구현하고 있다는 것으로 충분하기 때문이다.
참고 자료


반응형