-
Collection FrameworkJava 2020. 11. 24. 08:36반응형
Collection Famework의 개요
다수의 객체를 효율적으로 관리하기 위해 JAVA는 객체를 모아서 조작하는 기능들을 한데 모아 framework로 제공하는데 이를 Collection framewok라 한다.
배열을 사용하여 Collection framework를 대채할 수 있지만 다음과 같은 단점이 있다.
- 배열은 초기화 시 그 크기가 고정되어 확장하기 어렵다.
- 중간에 위치한 객체를 제거하는 경우 인덱스는 남아있으나 참조만 깨지게 되어 관리하기 어렵게 된다. (비어있는 배열 인덱스를 확인하는 성능비용이 발생된다.)
Collection framework는 배열을 통해 객체를 관리할 때의 단점을 해결하고 보다 효율적인 기능을 제공한다. 객체들의 효율적인 추가, 삭제, 검색을위해 java.util 패키지에 Collection과 관련된 interface와 class를 정의해 놓았으며 이것이 바로 Collection framework이다.
JAVA Collection은 객체를 수집해서 저장하는 역할을 하고 framework란 사용방법을 미리 정해놓은 library를 말한다.
Collection Framework의 구성
List Collection
List Collection은 객체를 일렬로 늘어놓은 구조이다. 객채를 인덱스로 관리하기 때문에 객체를 저장하면 자동으로 인덱스가 부여되고 인덱스를 통해 객체를 조회, 검색, 삭제 할 수 있는 기능을 제공한다. List Collection은 저장공간에 객체를 복사하여 저장하는 것이 아니라 객체의 참조주소를 저장한다. (인덱스0과 1에 각각 동일한 객체를 저장할 경우 인덱스0과1은 서로 동일한 객체의 주소를 참조하게된다.) 참조주소를 저장하기에 참조가 없음을 표현하는 null도 저장할 수 있다.
List Collection(ArrayList, Vector, LinedList)에서 공통으로 사용되는 List Interface의 method는 다음과 같다. List interface는제네릭 타입이므로 구체적인 타입은 구현 객체를 생성할 때 결정된다.
List Collection에서 참조하는 모든 객체를 Access할 때는 for문을 사용한다.
for(int i = 0; i < listCollection.size(); i++) {...}
Index를 사용할 일이 없다면 향상된 for문을 사용해도 무방하다.
for(ObjectType object : listCollection) {...}
1. ArrayList
생성자를 통해 ArrayList를 생성하면 10개의 객체를 참조할 수 있는 ArrayList 객체가 생성된다. 처음부터 더 큰 용량의ArrayList가 필요한 경우 매개변수에 그 크기를 지정하여 생성자를 호출한다.
ArrayList는 배열과는 달리 저장 용량을 초과하여 참조정보를 넣으려고 하면 자동으로 용량을 늘려 입력하게 된다.
ArrayList에 객체를 추가하면 인덱스 0부터 추가하게 되고 중간 인덱스를 제거하면 바로 뒤 인덱스 부터 마지막 인덱스까지 모두 앞으로 1씩 당겨진다. 즉, 빈번한 참조정보의 삭제와 삽입이 필요한 경우에 대해 사용하기 어렵다. 이런 경우에는LinkedList를 사용하는 것이 바람직하다.
ArrayList 타입의 예
List<String> ojbectName = new ArrayList<String>(10); //생성자 매개변수로 List 초기 size 지정가능. 지정하지 않으면 Default size = 10
ArrayList에 한번에 다수의 객체를 추가고자 할 때는 Arrays.asList( )를 사용하면 편하다.
import java.util.Arrays; import java.util.List; public class ArraysAsListEX { public static void main(String[] args) { List<String> list = Arrays.asList( "Dave", "Mike", "Choi" ); for(String name : list) { System.out.println(name); } } }
2. Vector
Vector는 Thread safe(동기화된 메소드로 구성되어 한 번에 한 Thread만 접근이 가능 함)한 List Collection으로 그 구조는ArrayList와 동일하다.
Vector 타입 선언의 예
List<String> objectName = new Vector<String>();
3. LinkedList
LinedList의 사용법은 ArrayList와 동일. 내부구조는 인접한 저장소의 참조정보를 가지고있어 체인처럼 관리한다. 따라서특정 인덱스에 해당하는 저장소를 제거하면 제거된 저장소에 인접한 두 저장소의 링크만 변경된다. 객체의 삽입과 삭제가 빈번하게 발생하는 경우 ArrayList보다 좋은 성능을 기대할 수 있다. 다만 참조의 추가, 삭제가 크게 없는 경우는ArrayList를 사용하는 것이 조회 속도를 비약적으로 높일 수 있다.(약 280배 차이)
LinkedList 타입 선언의 예
List<String> objectName = new LinkedList<String>();
Set Collection
저장순서가 유지되지 않고 객체를 중복해서 저장할 수 없으며 null또한 하나의 요소로 인식하여 한번만 저장할 수 있는컬렉션이다.
Set Collection의 method는 다음과 같다.
Set Collection은 Set에 포함된 모든 객체들(집합요소)을 한번씩 가지고 오는 Iterator를 제공한다. Iterator는 Iterator interface를 구현한 객체이며 iterator( ) 메서드를 호출하여 얻을 수 있다.
Set<String> set = new Set<String>(); set.add("1"); set.add("2"); set.add("3"); Iterator<String> iterator = set.iterator();
iterator를 통해 Set collection의 객체참조를 가져오기 전에 hasNext( ) 메서드로 가져올 참조가 있는지 먼저 확인한 후에next( ) 메서드로 가져오는 것이 좋다.
Set<String> set = new Set<String>(); Iterator<String> iterator = set.iterator(); while(iterator.hasNext()) { String str = iterator.next(); }
Iterator를 사용하지 않고 Set Collection에 대해 향상된 for문을 사용하여 Set Collection의 모든 객체참조를 순회할 수 있다.
Set<String> set = new Set<String>(); for(String str : set) { //각 집합요소에 대해 필요한 작업을 한다. }
1. HashSet
객체의 상태가 동일함을 확인하기 위해서는 두 가지 확인을 거쳐야 한다. 우선 hash value가 같은지 확인하고 두번째로 실제 field의 값들이 동일한지 확인한다.
hash란 단방향 암호화 기법중 하나이다. hash를 통해 value(평문)를 고정길이로 암호화된 문자열로 변경한다. 이 때 원본데이터는 key이고 암호화된 문자열이 hash value이다. 단순히 실제 field 값으 비교만으로도 객체들의 상태가 동일함을 확인할 수 있는데 왜 filed 비교에 앞서 hash value를 확인하는 것일까?
hash를 사용하는 이유는 데이터를 분산된 공간에 저장하고 각 공간을 인덱싱 하여 데이터를 조회할 때 hash value가 속하는 범위의 index에 해당하는 데이터들만 조회 하도록 하는 것으로 조회속도를 높이기 위해서이다.
(ex)
0~99 까지의 데이터가 있다고 할 때 55를 조회한다고 하면 0부터 55가 나올 때까지 55회 비교연산이 필요하다.
조회 속도를 개선하기 위해 다음과 같이 hashing을 이용한다.
- value를 고유의 hash값으로 변환한다.
- hash값들을 나눠담을 indexing 영역을 만든다. (int hashArea[10]와 같이...)
- index 개수로 hash값을 mod연산하여 결과에 해당하는 index에 hash값을 담는다.
- 조회할 때 hash값을 mod연산한 결과에 해당하는 index의 데이터들만 비교하는 것으로 조회속도를 높일수 있다.
따라서 HashSet에 저장되는 객체참조 요소들의 중복여부를 확인하기 위해서는 객체를 생성하기 위한 Class에 boolean equals(Object o) 메서드와 int hashCode( ) 메서드를 Override해야 한다.
Map Collection
Map Collection은 키값쌍(Key - Value)으로 구성된 Entry객체를 저장하는 구조를 가지고 있다. 키와 값은 모두 객체이다. 값은 중복으로 저장할 수 있지만 키는 중복저장이 불가하다. 기존에 저장한 키와 동일한 값으로 저장하면 값을 Update한다.
Map collection에서 공통으로 사용할 수 있는 Map Interface method 목록은 다음과 같다.
Map collection의 모든 요소에 대해 순회하는 방법
keySet( ) 메서드로 모든 키를 Set 컬렉션으로 얻은 다음, 반복자를 통해 키를 하나씩 얻고 get( )메소드를 통해값을 얻는 방법
Map<K, V> map = ...; Set<K> keySet = map.keySet( ); Iterator<K> keyIterator = keySet.iterator( ); while(keyIterator.hasNext( )) { K key = keyIterator.next( ); V value = map.get(key); }
entrySet( ) 메서드로 모든 Mapy.Entry객체를 Set 컬렉션으로 얻은 다음, 반복자를 통해 Map.Entry 객체를 하나씩 접근하고, 접근한 Map.Entry객체에 대해 getKey( )와 getValue( ) 메서드로 각각 키와 값을 얻는 방법
Map<String, String> map = new HashMap<>(); // ① Set<Map.Entry<String, String>> entrySet = map.entrySet(); // ② Iterator<Map.Entry<String, String>> entryIterator = entrySet.iterator(); // ③ while(entryIterator.hasNext()) { // ④ Map.Entry<String, String> entry = entryIterator.next(); // ⑤ K key = entry.getKey(); // ⑥ V value = entry.getValue(); // ⑦ }
① HashMap<Key, Value> 타입의 key와 value는 통상적으로 String타입을 사용하는데 이는 동일 객체를 확인하기 위한 hashCode()와 equals()가 미리 정의되어 있기 때문이다.
② Map타입 객체의 entrySet() 메서드를 사용하여 객체 안에 있는 entry(등록된 데이터들)를 Set<Map.Entry<String, String>> 타입의 객체(entrySet)에 담는다.
③ Set<Map.Entry<String, String>> 타입은 Set 타입 객체이므로 iterator() 메서드를 사용하여 Set 타입의 각 요소들에 접근하기 위한 Iterator 타입의 객체를 얻을 수 있다.
④ 이터레이터 타입의 hasNext() 메서드를 통해 다음 접근할 요소가 있는지 확인하면 반복문을 실행한다. 접근할 요소가 있으면 true가 반환되어 반복문 내부가 실행되며 없으면 false가 반환되어 반복문이 종료되는 구조이다.
⑤ Iterator 타입 객체의 next() 메서드를 사용하여 hasNext()를 통해 있는지 확인했던 Map.Entry<Key, Value> 타입의 객체에 실제로 접근한다.
⑥ ⑤에서 접근한 Map.Entry<Key, Value> 타입 객체의 getKey() 메서드를 사용하여 Map에 등록된 key 데이터를 받아온다.
⑦ ⑤에서 접근한 Map.Entry<Key, Value> 타입 객체의 getValue() 메서드를 사용하여 Map에 등록된 value 데이터를 받아온다.
Set 컬렉션과 향상된 for문을 조합하여 Map 컬렉션 요소에 접근하능 방법
Map<String, String> map = new HashMap<>(); map.put("3", "mike"); map.put("2", "jane"); map.put("4", "dave"); map.put("1", "john"); for(Map.Entry<String, String> entry : map.entrySet()) { // ① System.out.println(entry.getKey() + ", " + entry.getValue()); // ② }
① Map.entrySet() 메서드로 Map 타입 객체의 entry set 객체를 생성하고 향상된 for문을 통해 entry set의 각 요소에 접근한다.
② 각 entry set 요소별로 getKey()와 getValue() 메서드를 사용하여 key와 value를 각각 얻어낼 수 있다.
1. HashMap
HashMap의 key로 사용할 객체는 hashCode( )와 equals( ) 메서드를 override하여 동등 객체가 될 조건을 정해야 한다.
key의 타입은 주로 String을 사용하는데, String은 문자열이 같은 경우 동등 객체로 판단하도록 hasCode( )와 equals( )가 이미 override되어 있기 때문이다.
HashMap을 생성하기 위해서는 key와 value의 타입을 파라메터로 주고 기본 생성자를 호출하면 된다.
Map<String, String> map = new HashMap<String, String>( );
key와 value의 타입은 클래스와 인터페이스 타입만 사용할 수 있다. (기본 타입은 사용불가)
2. HashTable
HashTable은 HashMap과 동일한 내부구조를 가지고 있으므로 key로 사용할 객체는 hashCode( )와 eqauls( ) 를 override하여객체 동등 조건을 정해야 한다.
HashTable은 HashMap과는 다르게 동기화된 메서드로 구성되어 있기 때문에 Thread safe하다.
HashTable을 생성하기 위한 구문은 다음과 같다.
Map<Stirng, String> table = new HashTable<String, String>( );
3. Properties
Properties는 HashTable의 하위 클래스이다. 따라서 HashTable의 모든 특징을 그대로 가지고 있다. 단, 차이점은 HashTable의 경우 key와 value의 타입으로 모든 클래스와 인터페이스 타입으로 지정할 수 있으나 Properties의 경우 key와 value 모두String으로만 지정 가능하다는 것이다.
Properties는 어플리케이션의 옵션 정보, 데이터베이스 연결정보, 국제화(다국어) 정보 등이 저장된 *.properties 파일을 읽을 때 주로 사용한다.
프로퍼티 파일은 key와 value가 = 기호로 연결되어 있는 텍스트 파일로 ISO 8859-1 문자셋으로 저장된다. ISO 8859-1 문자셋으로 표현할 수 없는 한글은 유니코드로 변환되어 저장된다. 유니코드 변환을 자동으로 진행해주는 IDE를 사용하지않을 경우 JDK 설치 경로의 \bin\native2ascii.exe를 사용하여 ISO-8859-1 파일을 얻으면 된다.
- native2ascii.exe OriginFileName.properties DestFileName.properties
프로퍼티 파일을 읽어들이기 위한 순서
- Properties 객체를 생성
- FileReader 객체를 매개값으로 하여 load( ) 메서드를 호출한다.
Properties properties = new Properties( ); properties.load(new FileReader(/*프로퍼티 파일이 위치한 경로를 문자열 형태로 입력*/));
프로퍼티 파일은 일반적으로 *.class 파일과 함께 저장된다. *.class 파일을 기준으로 상대경로를 이용하여 프로퍼티 파일의 경로를 얻을 수 있다. 프로퍼티 파일의 절대경로는 다음과 같이 얻는다.
1. Class에 getResource( ) 메소드를 호출하여 매개변수로 지정된 파일의 상대 경로를 URL 객체로 리턴
2. URL 객체의 getPath( ) 메서드를 호출하여 프로퍼티 파일의 절대경로를 얻는다.
String path = 클래스명.class.getResouce("database.properties").getPath();
3. 경로에 한글이 포함된 경우 utf-8로 디코딩하여 복원해준다.
path = URLDecoder.decode(path, "utf-8");
4. load( ) 메서드에 매개값으로 프로퍼티 파일 경로(path)를 지정하여 파일을 읽어들인다.
String path1 = A.class.getResource("database.properties").getPath(); String path2 = A.class.getResource("config/database.properties").getPath(); // ① Properties properties = new Properties(); properties.load(path1);
① 만약 다른 패키지에 프로퍼티 파일이 있을 경우 경로 구분자 "/"를 사용한다.
Propertie 객체에서 특정 key의 value를 확인
Propertie 객체의 getProperty( ) 메소드에 매개값으로 key를 전달하여 value를 확인한다. Properties도 Map 컬렉션이므로 get( ) 메서드로 값을 얻을 수 있지만 get( ) 메서드의 경우 리턴 값이 Object이므로 String 타입으로 강제 타입변환이 필요하기 때문에 일반적으로 getProperty( ) 메서드를 사용하여 value를 확인한다.
Sting value = properties.getProperty("key");
'Java' 카테고리의 다른 글
문자열 뒤집기 예제 (0) 2020.12.10 형변환 (type casting) (0) 2020.12.04 라이브러리를 사용한 배열 정렬 (0) 2020.11.30 Map의 정렬 (0) 2020.11.28 콘솔 입력 (0) 2020.10.08