Power Java 제 22 장제네릭과컬렉션
이번장에서학습할내용 제네릭클래스 제네릭메소드 컬렉션 ArrayList LinkedList Set Queue Map Collections 클래스 일반적인하나의코드로다양한자료형을처리하는기법을살펴봅시다.
제네릭이란? 제네릭프로그래밍 (generic programming) 다양한타입의객체를동일한코드로처리하는기법 제네릭은컬렉션라이브러리에많이사용 그림 22-1. 제네릭프로그래밍의개념
기존의방법 먼저단하나의데이터만을저장할수있는 Box 라는간단한클래스를작성하여보자. 그림 22-2. Box 클래스는다양한타입의객체한개를저장할수있는클래스이다.
예제 실제로 Box 클래스는여러가지다양한타입의데이터를저장할수있다. 문자열을저장하고서도부주의하게 Integer 객체로형변환을할수도있으며이것은실행도중에오류를발생한다.
제네릭을이용한방법 제넥릭클래스 (generic class) 에서는타입을변수로표시한다. 이것을타입매개변수 (type paramter) 라고하는데타입매개변수는객체생성시에프로그래머에의하여결정된다. Box 클래스를제네릭으로다시작성하여보면다음과같다. "public class Box" 을 "public class Box<T>" 으로변경하면된다. 여기서는 T 가타입매개변수가된다. 타입매개변수의값은객체를생성할때구체적으로결정된다. 예를들어서문자열을저장하는 Box 클래스의객체를생성하려면 T 대신에 String 을사용하면된다.
제네릭을이용한방법 만약정수를저장하는 Box 클래스의객체를생성하려면다음과같이 T 대신에 <Integer> 를사용하면된다. 하지만 int 는사용할수없는데, int 는기초자료형이고클래스가아니기때문이다 그림 22-3. Box 클래스에저장하는데이터의타입은객체생성시에결정된다.
제네릭을이용한방법 문자열을저장하는객체를생성하여사용하면다음과같다 만약 Box<String> 에정수타입을추가하려고하면컴파일러가컴파일단계에서오류를감지할수있다. 따라서더안전하게프로그래밍할수있다.
타입매개변수의표기 E Element( 요소 : 자바컬렉션라이브러리에서많이사용된다.) K - Key N - Number T - Type V Value S, U, V 등 - 2번째, 3번째, 4번쨰타입
다이아몬드 자바 SE 7 버전부터는제네릭클래스의생성자를호출할때, 타입인수를구체적으로주지않아도된다. 컴파일러는문맥에서타입을추측한다.
다중타입매개변수 (Multiple Type Parameters) 위의정의를이용하여서객체를생성해보면다음과같다
Raw 타입 Raw 타입은타입매개변수가없는제네릭클래스의이름이다. 앞의 box 클래스를다음과같이사용하면 Raw 타입이된다. Raw 타입은 JDK 5.0 이전에는제네릭이없었기때문에이전코드와호환성을유지하기위하여등장 타입을주지않으면무조건 Object 타입으로간주
중간점검
제네릭메소드 메소드에서도타입매개변수를사용하여서제네릭메소드를정의할수있다. 타입매개변수의범위가메소드내부로제한된다. 제네릭메소드를호출하기위해서는실제타입을꺽쇠안에적어준다. 여기서메소드호출시에는 <String> 는생략하여도된다. 왜냐하면컴파일러는이미타입정보를알고있기때문이다. 즉다음과같이호출하여도된다.
한정된타입매개변수 배열원소중에서가장큰값을반환하는제네릭메소드를작성하여보자 타입매개변수 T 가가리킬수있는클래스의범위를 Comparable 인터페이스를구현한클래스로제한하는것이바람직하다
중간점검
제네릭과상속 우리는다형성에의하여 Integer 객체를 Object 객체변수로가리키게할수있음을알고있다. Integer 가 Object 로부터상속받았기때문이다. Number 를타입매개변수로주어서객체를생성하였으면 Number 의자식클래스인 Integer, Double 의객체도처리할수있다. 하지만다음과같은제네릭메소드를고려해보자. 어떤타입의인수를받을수있을까?
제네릭과상속 자바튜토리얼에보면다음과같은그림을사용하여서설명하고있다 Integer 가 Number 의자식이긴하지만, Box<Integer> 는 Box<Number> 의자식은아니다.
제네릭클래스의상속 ArrayList<String> 는 List<String> 의자식클래스가된다. List<String> 는 Collection<String> 의자식클래스가된다.
외일드카드 물음표 (?) 는와일드카드 (wild card) 라고불린다. 와일드카드는어떤타입이든지나타낼수있다. 위의메소드는다음과같이호출이가능하다
한도가없는와일드카드 리스트안의모든요소들을출력하는 printlist() 메소드를다음과같이작성하여보자. 올바르게작성된 printlist() 는다음과같다. 어떤타입 A 에대하여 List<A> 는 List<?> 의자손클래스가되므로다음과같이 printlist() 를이용하여서다양한타입의리스트들을출력할수있다.
하한이있는와일드카드 List<Integer>, List<Number>, List<Object> 와같은 Integer 값을가지고있는모든객체에대하여작동시킬려고한다.
컬렉션 컬렉션 (collection) 은자바에서자료구조를구현한클래스 자료구조로는리스트 (list), 스택 (stack), 큐 (queue), 집합 (set), 해쉬테이블 (hash table) 등이있다. 그림 22-4. 자료구조의예
컬렉션의예 : Vector 클래스 Vector 클래스는 java.util 패키지에있는컬렉션의일종으로가변크기의배열 (dynamic array) 을구현
예제
실행결과
컬렉션의종류
Collection 인터페이스 그림 22-4. 인터페이스들의계층구조
Collection 인터페이스
중간점검
ArrayList 그림 22-5. 리스트
ArrayList 의기본연산 ArrayList 는타입매개변수를가지는제네릭클래스로제공된다. 생성된 ArrayList 객체에데이터를저장하려면 add() 메소드를사용한다. add() 메소드는 Collection 인터페이스에정의된메소드로서 ArrayList 클래스가구현한메소드이다.
ArrayList 의기본연산 만약에기존의데이터가들어있는위치를지정하여서 add() 를호출하면새로운데이터는중간에삽입된다.
ArrayList 의기본연산 만약특정한위치에있는원소를바꾸려면 set() 메소드를사용한다.
ArrayList 의기본연산 데이터를삭제하려면 remove() 메소드를사용한다. ArrayList 객체에저장된객체를가져오는메소드는 get() 이다. get() 은인덱스를받아서그위치에저장된원소를반환한다. 예를들어서 list.get(1) 이라고하면인덱스 1 에저장된데이터가반환된다.
예제
실행결과
ArrayList 의추가연산 ArrayList 는동일한데이터도여러번저장될수있으므로맨처음에있는데이터의위치가반환된다. 검색을반대방향으로하려면 lastindexof() 를사용한다.
반복자사용하기 ArrayList 에있는원소에접근하는또하나의방법은반복자 (iterator) 를사용하는것이다.
반복자사용하기 반복자객체의 hasnext() 와 next() 메소드를이용하여서컬렉션의각원소들을접근하게된다.
중간점검
LinkedList 그림 22-6. 배열의중간에삽입하려면원소들을이동하여야한다.
LinkedList 그림 22-7. 연결리스트중간에삽입하려면링크만수정하면된다.
예제
실행결과
반복자사용하기 LinkedList 도반복자를지원한다. 다음과같은형식으로사용하면된다 ArrayList 나 LinkedList 와같은리스트에서사용하기가편리한반복자는다음과같이정의되는 ListIterator 이다.
배열을리스트로변경하기 Arrays.asList() 메소드는배열을받아서리스트형태로반환한다.
Set 집합 (Set) 은원소의중복을허용하지않는다. 그림 22-8. 집합
Set 그림 22-9. 해쉬테이블
Set 인터페이스를구현하는방법 HashSet HashSet 은해쉬테이블에원소를저장하기때문에성능면에서가장우수하다. 하지만원소들의순서가일정하지않은단점이있다. TreeSet 레드 - 블랙트리 (red-black tree) 에원소를저장한다. 따라서값에따라서순서가결정되며하지만 HashSet 보다는느리다. LinkedHashSet 해쉬테이블과연결리스트를결합한것으로원소들의순서는삽입되었던순서와같다.
예제
실행결과 만약 LinkedHashSet 을사용한다면다음과같은결과가얻어진다. 입력된순서대로출력됨에주의하라. 만약 TreeSet 을사용한다면다음과같은결과가얻어진다. 알파벳순으로정렬되는것에주의하자.
예제
대량연산메소드 s1.containsall(s2): 만약 s2 가 s1 의부분집합이면참이다. s1.addall(s2): s1 을 s1 과 s2 의합집합으로만든다. s1.retainall(s2): s1 을 s1 과 s2 의교집합으로만든다. s1.removeall(s2): s1 을 s1 과 s2 의차집합으로만든다. String 타입의집합 s1 과 s2 를합하려고하면먼저 s1 을가지고새로운집합 union 을생성하고여기에 s2 를더해야한다.
예제
실행결과
Queue 큐는먼저들어온데이터가먼저나가는자료구조 FIFO(First-In First-Out) 그림 22-10. 큐
Queue 인터페이스
예제
우선순위큐 우선순위큐는원소들이무작위로삽입되었더라도정렬된상태로원소들을추출한다. remove() 를호출할때마다가장작은원소가추출된다. 우선순위큐는히프 (heap) 라고하는자료구조를내부적으로사용한다. 히프는이진트리의일종으로서 add() 와 remove() 를호출하면가장작은원소가효율적으로트리의루트로이동하게된다. 우선순위큐의가장대표적인예는작업스케쥴링 (job scheduling) 이다. 각작업은우선순위를가지고있고가장높은우선순위의작업이큐에서먼저추출되어서시작된다
예제
실행결과
Map 사전과같은자료구조 키 (key) 에값 (value) 이매핑된다. 그림 22-11. Map 의개념
예제 #1
예제 #1
예제 #2
실행결과
중간점검
Collections 클래스 정렬 (Sorting) 섞기 (Shuffling) 탐색 (searching) 그림 22-12. 안정된정렬
Collections 클래스 Collections 클래스의 sort() 메소드는 List 인터페이스를구현하는컬렉션에대하여정렬을수행한다.
정렬의예제 #1 문자열을정렬하는간단한예를살펴보자.
실행결과
정렬의예제 #2
정렬의예제 #2
섞기 (Shuffling)
탐색 (Searching) 그림 22-13. 제네릭프로그래밍의개념
탐색 (Searching)
기타메소드 min(), max(): 리스트에서최대값과최소값을찾는다. reverse(): 리스트의원소들의순서를반대로한다. fill(): 지정된값으로리스트를채운다. copy(): 목적리스트와소스리스트를받아서소스를목적지로복사한다 swap(): 리스트의지정된위치의원소들을서로바꾼다. addall(): 컬렉션안의지정된모든원소들을추가한다 frequency(): 지정된컬렉션에서지정된원소가얼마나많이등장하는지를반환한다 disjoint(): 두개의컬렉션이겹치지않는지를검사한다
Q & A