본문 바로가기
Java/객체 지향 개념 ~

자바의 정석 (Chapter 11. 컬렉션 프레임워크)

by kevinntech 2020. 8. 23.

 

해당 게시물은 자바의 정석을 정리한 내용 입니다.

 

1.  컬렉션 프레임워크(collections framework)

컬렉션(collection)

 

- 여러 객체(데이터)를 모아 놓은 의미

 

프레임워크(framework)

 

- 표준화, 정형화된 프로그래밍 방식

 

컬렉션 프레임워크(collections framework)

 

- 컬렉션(여러 객체)을 다루기 위한 표준화된 프로그래밍 방식

- 컬렉션을 쉽고 편리하게 다룰 있는 다양한 클래스를 제공

- java.util 패키지에 포함.  JDK 1.2 부터 제공

 

컬렉션 클래스(collection class)

 

  - 여러 객체를 저장할 수 있는 클래스

     예) Vector, ArrayList, HashSet ...

 

  - 컬렉션 클래스들은 저장 공간이 부족하면 스스로 공간을 늘린다.

 

1.1 컬렉션 프레임워크의 핵심 인터페이스

List, Set 인터페이스에서 공통 부분을 뽑아낸 것이 Collection 인터페이스이다. 

"순서 : O" 이라는 것은 저장 순서가 유지된다는 것을 의미한다.

 

인터페이스 설명
List  순서가 있는 데이터의 집합. 데이터의 중복을 허용한다.

 예) 대기자 명단
 구현 클래스 : ArrayList, LinkedList, Stack, Vector 등
Set  순서를 유지하지 않는 데이터의 집합. 데이터의 중복을 허용하지 않는다.

 예) 양의 정수집합, 소수의 집합
 구현 클래스 : HashSet, TreeSet 등
Map  키(key)와 값(value)의 쌍(pair)으로 이루어진 데이터의 집합

순서는 유지되지 않으며, 키는 중복을 허용하지 않고, 값은 중복을 허용한다.

 예) 우편번호, 지역번호(전화번호), 아이디와 패스워드
 구현 클래스 : HashMap, TreeMap, Hashtable, Properties 등

 

1.2 Collection 인터페이스의 메서드

메서드 설 명
boolean add(Object o)
boolean addAll(
Collection c)
지정된 객체(o)를 Collection에 추가한다.
지정된 Collection (c)의 객체들을 Collection에 추가한다.  추가 
boolean contains(Object o)
boolean containsAll(Collection c)
지정된 객체(o)가 Collection에 포함 되어 있는지 확인한다
지정된 Collection (c)의 객체들이 Collection에 포함 되어 있는지 확인한다.  검색 
boolean remove(Object o)
boolean removeAll(Collection c)
지정된 객체를 삭제한다.
지정된 Collection에 포함된 객체들을 삭제한다.  삭제 
void clear() Collection의 모든 객체를 삭제한다.  전체 삭제 
boolean isEmpty() Collection이 비어있는지 확인한다.
int size() Collection에 저장된 객체의 개수를 반환한다.

 

자세한 내용은 "자바의 정석" p580를 참고하자.

 

1.3 List 인터페이스

List 인터페이스?

 

List 인터페이스저장 순서가 유지되고 중복을 허용하는 컬렉션을 구현하는데 사용된다.

 

 

List 인터페이스에 정의된 메서드

 

메서드 설 명
void add(int index, Object element)
boolean addAll(int index, Collection c)
지정된 위치(index)에 객체(element) 또는 컬렉션에 포함된 객체들을 추가한다.
Object get(int index) 지정된 위치(index)에 있는 객체를 반환한다.
int indexOf(Object o) 지정된 객체의 위치(index)를 반환한다.
(List의 첫 번째 요소부터 순방향으로 찾음)
int lastIndexOf(Object o) 지정된 객체의 위치(index)를 반환한다.
(List의 마지막 요소부터 역방향으로 찾음)
Object remove(int index) 지정된 위치(index)에 있는 객체를 삭제하고 삭제된 객체를 반환한다.
Object set(int index, Object element) 지정된 위치(index)에 객체(element)를 저장한다.
void sort(Comparator c) 지정된 비교자(comparator)로 List를 정렬한다.
List subList(int fromIndex, int toIndex) 지정된 범위(fromIndex 부터 toIndex)에 있는 객체를 반환한다.
(일부만 뽑아내어 새로운 List를 만듦)

 

자세한 내용은 "자바의 정석" p581를 참고하자.

 

1.4 Set 인터페이스

Set 인터페이스저장 순서가 유지되지 않고 중복을 허용하지 않는 컬렉션을 구현하는데 사용된다.

 

Set 인터페이스에 정의된 메서드 - Collection 인터페이스와 동일

 

집합과 관련된 메서드

 

 

1.5 Map 인터페이스

Map 인터페이스저장 순서가 유지되지 않고 키는 중복을 허용하지 않고 값은 중복을 허용하는 컬렉션을 구현하는데 사용된다.

 

* 저장 순서를 유지 해야되는 경우, Map 인터페이스의 구현체 중에서 LinkedHashMap를 사용한다.

 

 

메서드 설 명
Object put(Object key, Object value)  key 객체에 value 객체를 연결(mapping)하여 Map에 저장한다.  추가 
void putAll(Map t)  지정된 Map의 모든 key-value 쌍을 추가한다.  추가 
boolean containsKey(Object key)  지정된 key 객체와 일치하는 Map의 key 객체가 있는지 확인한다.  검색 
boolean containsValue(Object value)  지정된 value 객체와 일치하는 Map의 value 객체가 있는지 확인한다.  검색 
Object get(Object key)  지정된 key 객체와 일치하는 value 객체를 찾아서 반환한다.  검색  
Object remove(Object key)  지정된 key 객체와 일치하는 key-value 객체를 삭제한다.  삭제  
Set entrySet()  Map에 저장되어 있는 key-value 쌍을 Map.Entry 타입의 객체로 저장한 Set으로 반환한다.
Set keySet()  Map에 저장된 모든 key 객체를 반환한다.
Collection values()  Map에 저장된 모든 value 객체를 반환한다.

 

* Entry : 키와 값의 한 쌍을 의미한다.

   Map에서 값(value)은 중복을 허용하기 때문에 반환 타입이 Collection이며 키(key)는 중복을 허용하지 않기 때문에 반환 타입이 Set이다.

 

자세한 내용은 "자바의 정석" p582를 참고하자.

 

2.1 ArrayList

ArrayList?

 

 - ArrayList는 기존의 Vector를 개선한 으로 구현 원리와 기능적인 측면에서 동일하다.

 

 - Vector는 자체적으로 동기화 처리가 되어 있으나 ArrayList는 그렇지 않다.

 

 - List 인터페이스를 구현하므로, 저장 순서가 유지되고 중복을 허용한다.

 

 - 데이터의 저장 공간으로 배열을 사용한다.

    (Object 타입의 객체 배열을 사용하므로 모든 종류의 객체를 저장 할 수 있다.)

 

public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable
{
  ...
  transient Object[] elementData; // Object 배열
  ...
}

 

 

class ArrayListEx1{
	public static void main(String[] args) {
    
		// 기본 길이(용량, capacity)가 10인 ArrayList를 생성
		ArrayList list1 = new ArrayList(10);
        
		// 원래 ArrayList에는 객체만 저장 가능
		// 컴파일러가 auto boxing에 의해 기본형을 참조형으로 자동 변환
		// list1.add(5) ---> list1.add(new Integer(5))
		list1.add(new Integer(5));
		list1.add(new Integer(4));
		list1.add(new Integer(2));
		list1.add(new Integer(0));
		list1.add(new Integer(1));
		list1.add(new Integer(3));

		// 아래 코드는 ArrayList(Collection c)를 사용
		ArrayList list2 = new ArrayList(list1.subList(1,4)); 
		print(list1, list2);

		// Collection은 인터페이스, Collections는 클래스
		Collections.sort(list1);	// list1과 list2를 오름차순으로 정렬한다.
		Collections.sort(list2);	// Collections.sort(List l)
		print(list1, list2);

		System.out.println("list1.containsAll(list2):" + list1.containsAll(list2));

		list2.add("B");
		list2.add("C");
		list2.add(3, "A");
		print(list1, list2);

		list2.set(3, "AA");
		print(list1, list2);
		
		// list1에서 list2와 겹치는 부분만 남기고 나머지는 삭제한다.
		System.out.println("list1.retainAll(list2):" + list1.retainAll(list2));	
		print(list1, list2);
		
		//  list2에서 list1에 포함된 객체들을 삭제한다.
		/*
		    아래 코드를 자세히 설명하자면
		    1) get(i)로 list2에서 하나씩 꺼낸다.
		    2) contains()로 꺼낸 객체가 list1에 있는지 확인
		    3) remove(i)로 해당 객체를 list2에서 삭제
		*/
		for(int i= list2.size()-1; i >= 0; i--) {
			if(list1.contains(list2.get(i)))
				list2.remove(i); // 인덱스가 i인 객체를 삭제한다.
                // list2.remove(new Integer(1)); // 내용이 1인 것을 삭제 (삭제할 때 조심하자!)
		}
		print(list1, list2);
        
	} // main의 끝

	static void print(ArrayList list1, ArrayList list2) {
		System.out.println("list1:"+list1);
		System.out.println("list2:"+list2);
		System.out.println();		
	}
} // class

 

ArrayList에 저장된 객체의 삭제 과정(1/2)

 

- ArrayList 저장된 번째 데이터(data[2]) 삭제하는 과정.

   list.remove(2); 호출

 

 

삭제할 데이터 아래의 데이터를 칸씩 위로 복사해서 삭제할 데이터를 덮어쓴다.

System.arraycopy(data, 3, data, 2, 2)

    data[3]에서 data[2]로 2개의 데이터를 복사하라는 의미이다.

 

 

데이터가 모두 칸씩 이동 했으므로 마지막 데이터는 null 변경한다.

data[size-1] = null;

 

데이터가 삭제 되어 데이터의 개수가 줄었으므로 size 값을 감소시킨다.

size--;

마지막 데이터를 삭제하는 경우, ①의 과정(배열의 복사) 필요없다.

 

 

ArrayList에 저장된 객체의 삭제 과정(2/2)

 

ArrayList 저장된 번째 객체 부터 삭제하는 경우 (배열 복사 발생)

for(int i=0; i<list.size(); i++)
	list.remove(i);

 

ArrayList 저장된 마지막 객체 부터 삭제하는 경우 (배열 복사 발생 안함) 

for(int i=list.size()-1; i>=0; i--)
	list.remove(i);

2.2 LinkedList

배열의 장점과 단점

 

1) 장점

    - 배열은 구조가 간단하고 데이터를 읽는 걸리는 시간(접근시간, access time)이 짧다.

 

2) 단점

     2-1) 배열은 한번 생성하면 크기를 변경할 수 없다.

             - 크기를 변경해야 하는 경우 새로운 배열을 생성 데이터를 복사한 다음, 참조를 변경해야 함.

             - 크기 변경을 피하기 위해 충분히 배열을 생성하면, 메모리가 낭비됨.

 

     2-2) 비순차적인 데이터의 추가, 삭제에 시간이 많이 걸린다. (배열의 중간에 값을 추가 또는 삭제하는 경우)

             - 데이터를 추가하거나 삭제하기 위해, 다른 데이터를 옮겨야 .

             - 그러나 순차적인 데이터 추가(끝에 추가) 삭제(끝부터 삭제) 빠르다.

 

LinkedList?

 

- 배열의 단점을 보완하기 위해 나타난 자료구조LinkedList이다.

 

- 연결 리스트(LinkedList)불연속적으로 존재하는 데이터를 서로 연결(link)한 형태로 구성되어 있다.

 

LinkedList와 배열 비교

 

1) 장점

 

- 데이터의 삭제 : 단 한 번의 참조 변경만으로 가능

- 데이터의 추가 : 한 번의 Node 객체 생성과 두 번의 참조 변경만으로 가능

2) 단점

 

- 데이터의 접근성이 나쁨

   (연결 리스트에 5개의 노드가 있다고 가정하면 세 번째 노드에 접근하려면 첫 번째 노드 부터 차례대로 찾아가야 한다. 배열처럼 한 번에 갈 수 없음)

 

LinkedList의 종류

 

1) 단일 연결 리스트(Singly Linked List)

 

    - 단일 연결 리스트이동 방향이 단방향이기 때문에 다음 요소에 대한 접근은 쉽지만 이전 요소에 대한 접근은 어렵다.

2) 이중 연결 리스트(Doubly Linked List)

 

    - 단일 연결 리스트의 접근성을 향상 시킨 것이 이중 연결 리스트이다.

    - 이중 연결 리스트는 참조 변수를 하나 더 추가하여 다음 요소에 대한 참조 뿐만 아니라 이전 요소에 대한 참조가 가능하도록 하였다.
    - Java의 LinkedList는 이중 연결 리스트로 구현 되어 있다.

3) 이중 원형 연결 리스트(Doubly Circular Linked List)

 

    - 이중 연결 리스트의 접근성을 향상 시킨 것이 이중 원형 연결 리스트이다.

    - 이중 연결 리스트의 첫 번째 요소와 마지막 요소를 서로 연결 시킨 것이다.

ArrayList vs LinkedList 성능 비교

 

  순차적으로 데이터를 추가/삭제 - ArrayList 빠름

  비순차적으로 데이터를 추가/삭제 - LinkedList 빠름

  접근 시간(access time) - ArrayList 빠름

 

       * 접근 시간 == 읽기

인덱스가 n인 데이터의 주소 = 배열의 주소 + n * 자료형의 크기

LinkedList의 메서드

 

- 자세한 내용은 "자바의 정석" p598~599를 참고하자.

 

2.3 스택과 큐(Stack & Queue)

스택과 큐?

 

1) 스택(Stack)

    - LIFO구조. 마지막에 저장된 것을 제일 먼저 꺼내게 된다.

 

    - 수식 계산, 수식 괄호 검사, 실행 취소 / 다시 실행 (undo / redo), 뒤로 / 앞으로 (웹 브라우저)

 

 

2) 큐(Queue)

    - FIFO구조. 제일 먼저 저장한 것을 제일 먼저 꺼내게 된다.

 

    - 최근 사용 문서, 인쇄 작업 대기 목록, 버퍼(buffer)

 

    - Queue는 인터페이스이다.

       그래서 Queue 인터페이스를 구현한 클래스를 사용하면 된다.

       "All Known Implementing Classes" 중 하나를 선택해서 사용하면 된다.

 

 

class StackQueueEx {
	public static void main(String[] args) {
		Stack st = new Stack();
		Queue q = new LinkedList();	 // Queue인터페이스의 구현체인 LinkedList를 사용
		
		st.push("0");
		st.push("1");
		st.push("2");

		q.offer("0");
		q.offer("1");
		q.offer("2");

		System.out.println("= Stack =");
		while(!st.empty()) {
			System.out.println(st.pop());
		}

		System.out.println("= Queue =");
		while(!q.isEmpty()) {
			System.out.println(q.poll());
		}
	}
}

 

스택과 큐의 활용

 

- 자세한 내용은 "자바의 정석" p608~612를 참고하자.

 

Queue 변형

 

1) 덱(Deque)

 

- 양쪽 끝에서 저장(offer) 삭제(poll)가 가능하다.

- Deque은 인터페이스이며 구현체로는 ArrayDeque, LinkedList이다. (Deque은 Queue의 자손이다)

 

2) 우선 순위 큐(Priority Queue)

 

- 저장 순서에 관계 없이 우선 순위가 높은 것부터 꺼낸다. (제일 작은 값 부터 꺼냄)

- null은 저장 할 수 없다. (우선 순위를 판단할 수 없기 때문)

- 우선 순위 큐는 저장 공간으로 배열을 사용하며, 각 요소를 힙(Heap)이라는 자료구조의 형태로 저장한다.

- 예를 들어, 입력[3, 1, 5, 2, 4] → 출력[1, 2, 3, 4, 5]

 

 

2.4 Iterator, ListIterator, Enumeration

Iterator, ListIterator, Enumeration

 

- 컬렉션에 저장된 데이터를 접근하는데 사용되는 인터페이스

 

Enumeration

 

- Enumeration Iterator 구버전이다.

 

- 이전 버전으로 작성된 소스와의 호환을 위해서 남겨 두고 있을 뿐이므로 가능하면 Enumeration 대신 Iterator를 사용하자.

 

 

Iterator

 

- 컬렉션에 저장된 요소들을 읽어오는 방법을 표준화한

 

- 컬렉션에 iterator() 호출해서 Iterator 구현한 객체를 얻어서 사용. 

 

- Iterator는 1 회용이므로 다 사용 했다면 다시 얻어 와야 한다.

 

메서드 설 명
boolean hasNext() 읽어 올 요소가 남아있는지 확인한다.   확인 
있으면 true, 없으면 false를 반환한다.
Object next() 다음 요소를 읽어 온다.   읽기 
next()를 호출하기 전에 hasNext()를 호출해서 읽어 올 요소가 있는지
확인하는 것이 안전하다.
void remove() next()로 읽어 온 요소를 삭제한다.
next()를 호출한 다음에 remove()를 호출 해야 한다 (선택적 기능).
void forEachRemaining(Consumer<? super E> action) 컬렉션에 남아있는 요소들에 대해 지정된 작업(action)을 수행한다.
람다식을 사용하는 디폴트 메서드. (JDK 1.8 부터 추가)

 

class IteratorEx1 {
	public static void main(String[] args) {
		ArrayList list = new ArrayList();
		list.add("1");
		list.add("2");
		list.add("3");
		list.add("4");
		list.add("5");

		Iterator it = list.iterator();
		while(it.hasNext()) {
			Object obj = it.next();
			System.out.println(obj);
		}
	} // main
}

 

HashSet c = new HashSet(); // 해당 코드 보다는 아래 코드가 더 유연한 코드이다.

/* 참조변수가 Collection 타입이면 구현체를 다른 타입으로 변경하더라도 추가적으로 코드를 검토할 필요가 없다. 
   왜냐하면 Collection 인터페이스에 정의된 멤버 이외에는 사용하지 않았을 것을 확신 할 수 있기 때문이다. */
   
Collection c = new HashSet(); 

 

ListIterator

 

- Iterator 접근성을 향상시킨 것이 ListIterator이다. (단방향 양방향)

 

- listIterator() 통해서 얻을 있다.(List 구현한 컬렉션 클래스에 존재)

 

Map과 Iterator

 

- Map은 Collection의 자손이 아니므로 Iterator()가 없다.

 

- 그래서 Map은 keySet(), entrySet(), values()를 호출한 다음, Iterator()를 호출해야 한다.

 

Map map = new HashMap();

Iterator it = map.entrySet().iterator();

 

 

2.5 Arrays 클래스 - 배열을 다루기 편리한 static 메서드 제공

1) 일차원 배열의 비교와 출력 - equals(), toString()

 

     - toString()배열의 모든 요소를 문자열로 출력한다. (일차원 배열에만 사용 가능)

     - equals()두 배열에 저장된 모든 요소를 비교해서 같으면 true, 다르면 false를 반환한다. (일차원 배열에만 사용 가능)

 

2) 다차원 배열의 비교와 출력 – deepEquals(), deepToString()

 

     - deepToString()배열의 모든 요소를 문자열로 출력한다. (다차원 배열에 사용 가능)

     - deepEquals()두 배열에 저장된 모든 요소를 비교해서 같으면 true, 다르면 false를 반환한다. (다차원 배열에 사용 가능)

 

int[] arr = {0, 1, 2, 3, 4};
int[][] arr2D = { {11, 12}, {21, 22} };

System.out.println(Arrays.toString(arr));
System.out.println(Arrays.deepToString(arr2D));
String[][] str2D = new String[][]{ {"aaa", "bbb"}, {"AAA", "BBB"} };
String[][] str2D2 = new String[][]{ {"aaa", "bbb"}, {"AAA", "BBB"} };

System.out.println(Arrays.equals(str2D, str2D2)); // false
System.out.println(Arrays.deepEquals(str2D, str2D2)); // true

 

3) 배열의 복사 – copyOf(), copyOfRange()

 

     - copyOf()배열 전체를 복사해서 새로운 배열을 만들어 반환한다.

     - copyOfRange()배열의 일부를 복사해서 새로운 배열을 만들어 반환한다.

 

int[] arr  =  {0,1,2,3,4};
int[] arr2 = Arrays.copyOf(arr, arr.length); // arr2=[0,1,2,3,4]
int[] arr3 = Arrays.copyOf(arr, 3); // arr3=[0,1,2]          
int[] arr4 = Arrays.copyOf(arr, 7); // arr4=[0,1,2,3,4,0,0]         
int[] arr5 = Arrays.copyOfRange(arr, 2, 4); // arr5=[2,3]
int[] arr6 = Arrays.copyOfRange(arr, 0, 7); // arr6=[0,1,2,3,4,0,0]

 

 

4) 배열 채우기 – fill(), setAll()

 

     - fill()배열의 모든 요소를 지정된 값으로 채운다.

     - setAll()배열을 채우는데 사용할 함수형 인터페이스를 매개변수로 받는다.

        (이 메서드를 호출할 때는 함수형 인터페이스를 구현한 객체를 매개변수로 지정하던가 아니면 람다식을 지정해야 한다.)

int[] arr =  new int[5];
Arrays.fill(arr, 9);  // arr=[9,9,9,9,9]

// 람다식은 1~5의 범위에 속한 임의의 정수를 반환한다.
Arrays.setAll(arr, (i) -> (int) (Math.random() * 5) + 1); 
System.out.println("arr="+Arrays.toString(arr));

 

5) 배열을 List 변환 – asList(Object... a)

 

- asList()배열을 List에 담아서 반환한다.

 

- asList()반환한 List의 크기는 변경할 수 없다.   즉, 추가 또는 삭제가 불가능하다. (저장된 내용은 변경 가능)

List list = Arrays.asList(new Integer[]{1, 2, 3, 4, 5}); // list = [1, 2, 3, 4, 5]
List list = Arrays.asList(1, 2, 3, 4, 5); // list = [1, 2, 3, 4, 5]
list.add(6); // UnsupportedOperationException 예외 발생

- 만약 크기를 변경할 수 있는 List가 필요하다면 다음과 같이 하면 된다.

List list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));

 

6) 배열의 정렬과 검색 – sort(), binarySearch()

 

- sort()배열을 정렬할 때 사용하고 binarySearch()는 배열에 저장된 요소를 검색할 때 사용한다.

 

- binarySearch()배열에서 지정된 값이 저장된 위치(index)를 찾아서 반환하는데, 반드시 배열이 정렬된 상태이어야 올바른 결과를 얻는다.

   그리고 만일 검색한 값과 일치하는 요소들이 여러 개 있다면, 이 중에서 어떤 것의 위치가 반환될지는 알 수 없다.

 

int[] arr = { 3, 2, 0, 1, 4};
int idx = Arrays.binarySearch(arr, 2); // idx = -5 ← 잘못된 결과

Arrays.sort(arr); // 1) 배열 arr을 정렬한다.
System.out.println(Arrays.toString(arr)); // [0, 1, 2, 3, 4]
int idx = Arrays.binarySearch(arr, 2); // 2) binarySearch() 호출 
                                      // idx = 2 ← 올바른 결과

 

7) 람다와 스트림 관련 - parallelXXX(), spliterator(), stream()

 

     자세한 내용은 자바의 정석 14장에서 살펴보자

 

2.6 Comparator Comparable

- 객체를 정렬 하는데 필요한 메서드를 정의한 인터페이스이다. (정렬 기준 제공)

 

Comparable - 기본 정렬 기준을 구현하는데 사용.
Comparator - 기본 정렬 기준 외에 다른 기준으로 정렬하고자 할 때 사용.

 

public interface Comparator {
  int compare(Object o1, Object o2); // 두 객체(o1, o2)를 비교 
  boolean equals(Object obj);
}

public interface Comparable {
  public int compareTo(Object o); // 주어진 객체(o)를 자신(this)과 비교
}

 

- compare()와 compareTo()두 객체의 비교 결과를 반환 하도록 구현 해야 한다.

   비교하는 두 객체가 같으면 0, 왼쪽이 크면 양수, 왼쪽이 작으면 음수를 반환

   (리턴 값이 음수일 때 자리 교환이 발생함)

public final class Integer extends Number implements Comparable {
  ...
  public int compareTo(Integer anotherInteger) {
    int v1 = this.value;
    int v2 = anotherInteger.value;
    
    // 같으면 0, 왼쪽이 크면 1, 왼쪽(this)이 작으면 -1을 반환한다.
    return (v1 < v2 ? -1 : (v1==v2 ? 0 : 1));
  }
  ...  
}

 

class ComparatorEx {
	public static void main(String[] args) {
		String[] strArr = {"cat", "Dog", "lion", "tiger"};

		Arrays.sort(strArr); // String의 Comparable 구현에 의한 정렬
		System.out.println("strArr=" + Arrays.toString(strArr));

		// strArr : 정렬 대상 , String.CASE_INSENSITIVE_ORDER) : 정렬 기준
		Arrays.sort(strArr, String.CASE_INSENSITIVE_ORDER); // 대소문자 구분 안함
		System.out.println("strArr=" + Arrays.toString(strArr));

		Arrays.sort(strArr, new Descending()); // 역순 정렬
		System.out.println("strArr=" + Arrays.toString(strArr));
	}
}

// Comparator 인터페이스를 구현한 클래스를 작성한다.
class Descending implements Comparator { 
	public int compare(Object o1, Object o2){
		if( o1 instanceof Comparable && o2 instanceof Comparable) {
			Comparable c1 = (Comparable)o1;
			Comparable c2 = (Comparable)o2;
            
            // -1을 곱해서 String 클래스가 가지고 있는 기본 정렬 기준을 역으로 변경한다.
            // 또는 c2.compareTo(c1)와 같이 순서를 바꿔도 된다.
			return c1.compareTo(c2) * -1 ;                              
		}
		return -1; // 비교하는 두 객체가 Comparable이 아니면 -1을 반환
	} 
}

 

-Arrays.sort()는 배열을 정렬할 때 Comparator를 지정하지 않으면 객체 배열에 저장된 객체가 구현한 Comparable에 의해 정렬된다.

 

static void sort(Object[] a) // 객체 배열에 저장된 객체가 구현한 Comparable에 의한 정렬
static void sort(Object[] a, Comparator c) // sort(정렬 대상, 정렬 기준) 지정한 Comparator에 의한 정렬

 

- String의 Comparable 구현은 문자열이 사전 순으로 정렬 되도록 작성되어 있다.

  String의 오름차순 정렬(사전 순서)은 공백, 숫자, 대문자, 소문자의 순으로 정렬되는 것을 의미

 

- String은 대소문자를 구분하지 않고 비교하는 Comparator를 상수 형태로 제공한다.

   이 Comparator를 이용하면 문자열을 대소문자 구분 없이 정렬 할 수 있다.

public static final Comparator CASE_INSENSITIVE_ORDER

 

- 정렬(sort)는 두 대상을 비교하여 자리 바꿈을 반복 하는 것이다.

   (정렬 방법은 변경 되지 않으며 정렬 기준만 달라지기 때문에 정렬 기준만 전달하면 된다.)

 

2.7 HashSet TreeSet – 순서X, 중복X

 

1) HashSet

 

- HashSet 저장 순서를 유지 하지 않으며 중복을 허용하지 않는다.

- 내부적으로 HashMap을 이용해서 만들어졌으며 Set 인터페이스를 구현한 대표적인 컬렉션 클래스이다.

- 저장 순서를 유지하려면 LinkedHashSet 클래스를 사용하면 된다.

- Set은 정렬 할 수 없기 때문에 Set의 모든 요소를 List에 담아서, Collections.sort()로 정렬한다.

 

2) TreeSet

 

- TreeSet이진 탐색 트리라는 자료 구조의 형태로 데이터를 저장하는 컬렉션 클래스이다.

- 내부적으로 TreeMap을 이용해서 만들어졌으며 범위 검색과 정렬에 유리하다.

- HashSet 보다 데이터 추가, 삭제에 시간이 걸림

 

2.8 HashSet

HashSet의 생성자, 메서드

 

 

class HashSetEx1 {
	public static void main(String[] args) {
		Object[] objArr = {"1",new Integer(1),"2","2","3","3","4","4","4"};
		Set set = new HashSet();

		for(int i=0; i < objArr.length; i++) {
			set.add(objArr[i]);	// HashSet에 objArr의 요소들을 저장한다.
		}
        
		// set은 순서 X, 중복 X (물론, 순서가 유지되는 것처럼 보일 때도 있다. 그러나 이것을 보장 할 수는 없음)
		// HashSet에 저장된 요소들을 출력한다.
		System.out.println(set);
        
		// HashSet에 저장된 요소들을 출력한다. (Iterator 이용)
		Iterator it = set.iterator();
        
		while(it.hasNext()){ // 읽어올 요소가 있는지 확인
			System.out.println(it.next()); // 요소를 하나 꺼내오기
		}
	}
}

 

HashSet의 add()

 

- HashSet은 객체를 저장 하기 전에 기존에 같은 객체가 있는지 확인한다.

  같은 객체가 없으면 저장하고, 있으면 저장하지 않는다.

 

- HashSet의 add()는 새로운 객체를 추가 하기 전에 기존에 저장된 객체와 같은 것인지 확인하기 위해

   저장할 객체의 equals() hashCode() 호출한다.

   그래서 equals()와 hashCode()가 오버라이딩되어있어야함

 

  equals()는 인스턴스 변수(iv)를 비교 하도록 오버라이딩 한다.

  hashCode()는 Objects 클래스의 hash()를 이용하여 오버라이딩 한다.

 

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

		set.add(new String("abc"));
		set.add(new String("abc"));
		set.add(new Person2("David",10));
		set.add(new Person2("David",10));

		System.out.println(set);
	}
}

class Person2 {
	String name; // iv
	int age; // iv

	Person2(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public boolean equals(Object obj) {
		if(obj instanceof Person2) { // obj가 Person2 인스턴스이면
			Person2 tmp = (Person2)obj;
			return name.equals(tmp.name) && age==tmp.age; // iv 값을 비교 하도록 오버라이딩 (이름과 나이가 같은지 비교)
		}

		return false;
	}

	public int hashCode() {
		//return (name+age).hashCode(); // String 클래스의 hashcode()를 호출하도록 오버라이딩 [예전 방식]
        return Objects.hash(name, age); // JDK 1.8 부터
	}

	public String toString() {
		return name +":"+ age; 
	}
}

 

2.9 TreeSet

 

TreeSet?

 

- TreeSet 이진 탐색 트리라는 자료 구조의 형태로 데이터를 저장하는 컬렉션 클래스이다.

 

- 내부적으로 TreeMap을 이용해서 만들어졌으며 범위 검색과 정렬에 유리하다.

 

- 이진 트리는 모든 노드가 최대 개의 자식 노드를 갖는다.

 

- 요소(node)  나무(tree)형태로 연결되어 있다. (LinkedList의 변형)

 

 

class TreeNode{
    TreeNode left;  // 왼쪽 자식 노드
    Object element; // 저장할 객체
    TreeNode right; // 오른쪽 자식 노드
}

 

이진 탐색 트리(binary search tree)

 

- 모든 노드가 최대 두 개의 자식 노드를 갖는다.

- 부모 보다 작은 값은 왼쪽, 큰 값은 오른쪽에 저장한다.

- 중복된 값을 저장하지 못한다.

- 범위 검색과 정렬에 유리하다.

- 데이터가 많아질 수록 데이터 추가, 삭제에 시간이 걸린다. (부모 보다 큰지 작은지를 비교하는 횟수가 증가하기 때문)

 

TreeSet의 add()

 

- TreeSetcompare()를 호출해서 비교하여 기존에 같은 객체가 있는지 확인한다. (중복을 허용하지 않음)

 

HashSet은 equals(), hashCode()로 비교
TreeSet은 compare()를 호출해서 비교

 

TreeSet의 생성자, 메서드

 

TreeSet의 범위 검색

TreeSet - 트리 순회(전위, 중위, 후위)

 

-이진 트리의 모든 노드를 한번씩 읽는 것을 트리 순회라고 한다.

 

-전위, 중위, 후위 순회법이 있으며, 중위 순회하면 오름차순으로 정렬된다.

 

2.10 HashMap TreeMap – 순서 X, 중복(X, O)

 

- HashMap(동기화 X) Hashtable(동기화 O) 신버전이므로 HashMap을 사용할 것을 권장한다.

   * 동기화 O : 동기화가 되어 있음

 

2.11 HashMap

HashMap?

 

- HashMap은 Map 인터페이스를 구현하였으며 데이터를 키와 값의 쌍(entry)으로 저장한다.

 

- HashMap(동기화 X)은 HashTable(동기화 O)의 새로운 버전이다.

 

- 해싱(hashing)을 사용하기 때문에 많은 양의 데이터를 검색하는데 있어서 뛰어난 성능을 보인다.

 

- HashMap은 Entry라는 내부 클래스를 정의하고 다시 Entry 타입의 배열을 선언하고 있다.

 

public class HashMap extends AbstractMap implements Map, Cloneable, Serializable
{
    transient Entry[] table;
    
    	. . .
        
    static class Entry implements Map.Entry{
    	final Object key;
        Object value;
        . . .
    }
}

 

- 저장 순서를 유지하려면, LinkedHashMap클래스를 사용하면 된다.

 

HashMap의 생성자, 메서드

 

 

HashMap의 예시

 

class HashMapEx2 {
	public static void main(String[] args) {
		HashMap map = new HashMap();
		map.put("김자바", new Integer(90));
		map.put("김자바", new Integer(100));
		map.put("이자바", new Integer(100));
		map.put("강자바", new Integer(80));
		map.put("안자바", new Integer(90));

		Set set = map.entrySet();
		Iterator it = set.iterator();

		while(it.hasNext()) {
			Map.Entry e = (Map.Entry)it.next();
			System.out.println("이름 : "+ e.getKey() + ", 점수 : " + e.getValue());
		}

		set = map.keySet();
		System.out.println("참가자 명단 : " + set);

		Collection values = map.values();
		it = values.iterator();

		int total = 0;

		while(it.hasNext()) {
			Integer i = (Integer)it.next();
			total += i.intValue();
		}

		System.out.println("총점 : " + total);
		System.out.println("평균 : " + (float)total/set.size());
		System.out.println("최고점수 : " + Collections.max(values));
		System.out.println("최저점수 : " + Collections.min(values));
	}
}

 

해싱(Hashing)?

 

- 해싱(Hashing)해시 함수(hash function)를 이용해서 해시 테이블(hash table)에 데이터를 저장하거나 검색하는  기법

 

- 해시 함수(hash function)키를 전달하면 데이터가 저장된 위치(index)를 알려준다.

 

* 해시 코드(hash code) = 배열의 인덱스(index)

 

키로 해시 함수를 호출해서 해시코드를 얻는다.

 배열에서 해시코드(해시 함수의 반환 값)와 일치하는 연결리스트(Linked List) 찾는다.

연결리스트(Linked List)에서 키와 일치하는 데이터를 찾는다.

 

- 해싱에서 데이터를 저장하는 공간을 해시 테이블(Hash Table)이라 한다.

   해시 테이블은 배열과 연결 리스트(Linked List)가 조합된 형태이다.

 

-해시 함수는 같은 키에 대해 항상 같은 해시 코드를 반환 해야 한다.

  서로 다른 키 일지라도 같은 값의 해시 코드를 반환할 수도 있다.

 

2.12 TreeMap

- TreeMap은 이진 탐색 트리의 구조로 키와 값의 쌍으로 이루어진 데이터를 저장한다.

 

- HashMap보다 데이터 추가, 삭제에 시간이 더 걸린다.

 

- 다수의 데이터에서 개별적인 검색은 TreeMap보다 HashMap 빠르다. 

 

- Map 필요할 때는 주로 HashMap 사용하고, 범위 검색이나 정렬 필요 경우에 TreeMap 사용한다.

 

class TreeMapEx1 {
	public static void main(String[] args) {
		String[] data = { "A","K","A","K","D","K","A","K","K","K","Z","D" };

		TreeMap map = new TreeMap();

		for(int i=0; i < data.length; i++) {
			if(map.containsKey(data[i])) { // 키가 이미 존재하는 경우
				Integer value = (Integer)map.get(data[i]);
				map.put(data[i], new Integer(value.intValue() + 1)); // 해당 키의 값을 꺼내서 1을 더한 다음 다시 저장
			} else { // 키가 존재하지 않는 경우
				map.put(data[i], new Integer(1)); 		
			}
		}

		Iterator it = map.entrySet().iterator();

		System.out.println("= 기본정렬 =");
		while(it.hasNext()) {
			Map.Entry entry = (Map.Entry)it.next();
			int value = ((Integer)entry.getValue()).intValue();
			System.out.println(entry.getKey() + " : " + printBar('#', value) + " " + value );
		}
		System.out.println();

		// Map에서 entrySet을 얻은 다음
        // set을 ArrayList에 담아서 Collectons.sort()로 정렬한다.
		Set set = map.entrySet();
		List list = new ArrayList(set);	// ArrayList(Collection c) 
		
		// static void sort(List list, Comparator c)  
		Collections.sort(list, new ValueComparator());

		it = list.iterator();

		System.out.println("= 값의 크기가 큰 순서로 정렬 =");		
		while(it.hasNext()) {
			Map.Entry entry = (Map.Entry)it.next();
			int value = ((Integer)entry.getValue()).intValue();
			System.out.println(entry.getKey() + " : " + printBar('#', value) + " " + value );
		}

	} // 	public static void main(String[] args) 

	// 값을 기준으로 내림차순 정렬해서 저장할 수 있도록 ValueComparator 작성
	static class ValueComparator implements Comparator {
		public int compare(Object o1, Object o2) {
			if(o1 instanceof Map.Entry && o2 instanceof Map.Entry) {
				Map.Entry e1 = (Map.Entry)o1;
				Map.Entry e2 = (Map.Entry)o2;

				int v1 = ((Integer)e1.getValue()).intValue();
				int v2 = ((Integer)e2.getValue()).intValue();

				return  v2 - v1;
			} 
			return -1;
		}
	}	// 	static class ValueComparator implements Comparator {

	// #을 출력하는 메서드
	public static String printBar(char ch, int value) { 
		char[] bar = new char[value]; 

		for(int i=0; i < bar.length; i++) { 
			bar[i] = ch; 
		} 

		return new String(bar); 
	} 
}

2.13 Properties

- Properties는 HashMap의 구버전인 Hashtable을 상속 받아 구현한 것으로 key와 value를 String으로 저장한다.

   즉, Properties는 (String, String)의 형태로 저장한다.

 

- 주로 애플리케이션의 환경 설정에 관련된 속성을 저장 하는데 사용되며 파일로부터 편리하게 값을 읽고 있는 메서드를 제공한다.

 

class PropertiesEx2 {
	public static void main(String[] args) {
		// commandline에서 inputfile을 지정해주지 않으면 프로그램을 종료한다.
		if(args.length != 1) {
			System.out.println("USAGE: java PropertiesEx2 INPUTFILENAME");
			System.exit(0);
		}

		Properties prop = new Properties();

		String inputFile = args[0];

		try {
			prop.load(new FileInputStream(inputFile));
		} catch(IOException e) {
			System.out.println("지정된 파일을 찾을 수 없습니다.");
			System.exit(0);
		}

		String   name = prop.getProperty("name");
		String[] data = prop.getProperty("data").split(",");
		int max = 0;
		int min = 0;
		int sum = 0;

		for(int i=0; i < data.length; i++) {
			int intValue = Integer.parseInt(data[i]);
			if (i==0) max = min = intValue;

			if (max < intValue) {
				max = intValue;
			} else if (min > intValue) {
				min = intValue;
			}

			sum += intValue;
		}

		System.out.println("이름 :"  + name);		
		System.out.println("최대값 :" + max);
		System.out.println("최소값 :" + min);
		System.out.println("합계 :"  + sum);
		System.out.println("평균 :"  + (sum*100.0/data.length)/100);
	}
}

 

 

2.14 Collections

- Collections 클래스는 컬렉션를 위한 static 메서드를 제공한다.

 

1) 컬렉션 채우기, 복사, 정렬, 검색 – fill(), copy(), sort(), binarySearch() 등의 메서드는 Arrays에서 이미 배웠으므로 설명을 생략한다.

 

2) 컬렉션의 동기화 – synchronizedXXX()

 

- synchronizedXXX()는 동기화 처리를 한다.

 

static Collection synchronizedCollection(Collection c)
static List synchronizedList(List l)
static Set synchronizedSet(Set s)
static Map synchronizedMap(Map m)
static SortedSet synchronizedSortedSet(SortedSet s)
static SotredMap synchronizedSortedMap(SotredMap m)

 

3) 변경 불가(readOnly) 컬렉션 만들기  unmodifiableXXX() 

 

- unmodifiableXXX()는 저장된 데이터를 변경 할 수 없는 컬렉션을 만든다.

 

static Collection unmodifiableCollection(Collection c)
static List unmodifiableList(List list)
static Set unmodifiableSet(Set s)
static Map unmodifiableMap(Map m)
static NavigableSet unmodifiableNavigableSet(NavigableSet s)
static SortedSet unmodifiableSortedSet(SortedSet s)
static NavigableMap unmodifiableNavigableMap(NavigableMap m)
static SotredMap unmodifiableSortedMap(SotredMap m)

 

4) 싱글톤 컬렉션 만들기 – singletonXXX()

 

- singletonXXX()는 단 하나의 객체만을 저장하는 컬렉션을 만든다.

 

static List singletonList(Object o) // 매개변수로 저장할 객체를 지정
static Set singleton(Object o) // singletonSet()가 아님
static Map singletonMap(Object key, Object value)

 

5) 종류의 객체만 저장하는 컬렉션 만들기  checkedXXX() 

 

- checkedXXX()는 한 종류의 객체만 저장하는 컬렉션을 만든다.

 

- 컬렉션에 저장할 요소의 타입을 제한하는 것은 제네릭으로 간단히 처리할 수 있지만 이러한 메서드들을 제공하는 이유는 호환성 때문이다. 

 

static Collection checkedCollection (Collection c, Class type)
static List checkedList(List list, Class type)
static Set checkedSet(Set s, Class type)
static Map checkedMap(Map m, Class keyType, Class valueType)
static Queue checkedQueue(Queue queue, Class type)
. . .

 

List list = new ArrayList();
List checkedList = checkedList(list, String.class); // String만 저장 가능
checkedList.add("abc"); // OK.
checkedList.add(new Integer(3)); // 에러. ClassCastException 발생

 

 

댓글