Power Java 제14장 제네릭과 컬렉션
일반적인 하나의 코드로 다양한 자료형을 처리하는 기법을 살펴봅시다. 이번 장에서 학습할 내용 제네릭 클래스 제네릭 메소드 컬렉션 ArrayList LinkedList Set Queue Map Collections 클래스 일반적인 하나의 코드로 다양한 자료형을 처리하는 기법을 살펴봅시다.
제네릭(Generic)이란? 제네릭(범용) 프로그래밍(generic programming) 일반적인 코드를 작성하고 이 코드를 다양한 타입의 객체에 대하여 재사용하는 프로그래밍 기법 제네릭은 컬렉션 라이브러리에 많이 사용
기존의 방법 일반적인 객체를 처리하려면 Object 참조 변수를 사용 예제로 하나의 데이터를 저장하는 Store 클래스를 살펴보자.
기존 방법(코드) 문제점 발생!!
제네릭을 이용한 버전 문자열을 저장하려면 다음과 같이 선언 C++의 템플릿(template)과 유사 문자열을 저장하려면 다음과 같이 선언 Store<String> store = new Store<String>(); 정수를 저장하려면 다음과 같이 선언 Store<Integer> store = new Store<Integer>();
제네릭 버전의 사용
중간 점검 문제 왜 데이터를 Object 참조형 변수에 저장하는 것이 위험할 수 있는가? 2. Store 객체에 Rectangle 객체를 저장하도록 제네릭을 이용하여 생성하여 보라. Store<Rectangle> store = new Store<Rectangle>(); 3. 타입 매개 변수 T를 가지는 Point 클래스를 정의하여 보라. Point 클래스는 2차원 공간에서 점을 나타낸다. Class Point<T> { T x; T y; … }
제네릭 메소드(Generic Method) 메소드에서도 타입 매개 변수를 사용하여서 제네릭 메소드를 정의할 수 있다. 타입 매개 변수의 범위가 메소드 내부로 제한된다. 반환형 매개변수 배열의 마지막 원소를 반환하는 메소드
제네릭 메소드의 사용 String[] language= { "C++", "C#", "JAVA" }; String last = Array.<String>getLast(language);// last는 “JAVA" 또는 String last = Array.getLast(language); // last는 “JAVA"
중간 점검 문제 1. 제네릭 메소드 sub()에서 매개 변수 d를 타입 매개 변수를 이용하여서 정의하여 보라. public <T> void sub(T d) { … } 2. displayArray()라는 메소드는 배열을 매개 변수로 받아서 반복 루프를 사용하여서 배열의 원소를 화면에 출력한다. 어떤 타입의 배열도 처리할 수 있도록 제네릭 메소드로 정의하여 보라. public <T> void displayArray(T[] a) { for(int i = 0; i<a.length; i++) System.out.println(a[i]); }
컬렉션 (collection) 컬렉션(collection)은 자바에서 자료 구조를 구현한 클래스 자료 구조로는 리스트(list), 스택(stack), 큐(queue), 집합(set), 해쉬 테이블(hash table) 등이 있다.
자바에서의 컬렉션 컬렉션 인터페이스 : 이 인터페이스를 직접 구현하여 자신만의 클래스를 만들어 사용가능 컬렉션 컬렉션 클래스 : 이미 위 인터페이스 중 하나를 구현하고 있는 클래스 : 바로 사용이 가능하다.
컬렉션 인터페이스
컬렉션의 역사 초기 버전: Vector, Stack, HashTable, Bitset, Enumeration이 그것이다. 버전 1.2부터는 풍부한 컬렉션 라이브러리가 제공 인터페이스와 구현을 분리 (예) List 인터페이스를 ArrayList와 LinkedList 클래스가 구현
컬렉션 인터페이스
Collection Queue Set List Map HashSet ArrayList HashMap TreeSet LinkedList TreeMap Stack HashTable Interface Vector Properties 구현 클래스
콜렉션 인터페이스가 제공하는 메소드
List 인터페이스 List 인터페이스 - 순서를 가지는 원소들의 모임 - 중복된 원소가 허용된다. - 위치 인덱스를 사용하여 원소에 접근한다. (0부터 시작) 구현한 클래스 종류 - ArrayList: 리스트를 배열로 구현 - LinkedList: 리스트를 연결리스트로 구현 - Vector: 멀티쓰레드 환경에서 동기화가 지원됨
배열리스트(ArrayList) ArrayList를 배열(Array)의 향상된 버전 또는 가변 크기의 배열이라고 생각하면 된다. ArrayList<String> list = new ArrayList<String>(); 원소 추가 list.add( "MILK" ); list.add( "BREAD" ); list.add( "BUTTER" );
저장된 것을 가져올 때 String s = list.get(1);
예제 for (String s : list) System.out.println(s);
반복자(iterator) 반복자(iterator): 반복자는 컬렉션의 원소들을 하나씩 처리하는데 사용 For-each 문이 더 편리함! Iterator 객체 생성하는 방법 Iterator e = list.iterator(); Iterator 객체 e에 정의된 3 개의 메소드
반복자 ListIterator List를 구현한 클래스인 ArrayList, LinkedList, Vector에만 사용 컬렉션의 원소들을 하나씩 처리하는데 사용하지만, 앞으로 검색이 가능하기도 하며 뒤로도 검색 가능 컬렉션 객체의 원소를 지니고 있는 Iterator 객체 생성하는 방법 ListIterator e2 = list.listIterator(); ListIterator 객체 e2에 정의된 주요 메소드 hasNext() next() hasPrevious(): 이전의 원소가 있는지 판단 previous(): 이전의 원소를 돌려줌
ListIterator 사용 예제 import java.util.*; public class ArrayListTest { … System.out.println("[ListIterator Test]"); ListIterator e2 = list.listIterator(); while(e2.hasNext()) { s = (String)e2.next(); System.out.println(s); } while(e2.hasPrevious()) { s = (String)e2.previous();
연결리스트(LinkedList) 빈번하게 삽입과 삭제가 일어나는 경우에 사용
연결리스트 Linked List 이중 연결 리스트로 구현됨 ArrayList와 사용방법 거의 동일 거의 동일한 메소드가 정의되어 있으며 효과도 동일 LinkedList 에만 추가된 메소드 addFirst(), getFirst(), removeFirst(), removeLast(), addLast(), getLast() ArrayList 를 생성할 시에는 initial capacity를 미리 지정할 수 있는 생성자가 존재하지만(default : 10) LinkedList에는 그러한 생성자가 없다. 그렇다면 ArrayList와 LinkedList 중 어느 것을 사용하는 것이 좋은 것인가?
ArrayList vs. LinkedList 내부 구현 특징 ArrayList: 메모리에 데이터가 일렬로 붙어 있음 LinkedList: 메모리상 위치는 떨어져 있지만 포인터에 의해 연결되어 있음 공간적인 측면 ArrayList는 10개의 공간을 initial capacity로 지닌 이후에 2배씩 늘림. 그러므로, 공간의 낭비가 어느 정도 있음 LinkedList는 실행 시 필요에 따라 새로운 원소를 위한 공간을 생성하므로 공간이 절약됨 접근 속도 측면 (단순 읽기) ArrayList는 인덱스와 함께 원소가 저장되는 구조이기 때문에 접근이 빠름 LinkedList는 어떤 원소에 접근하기 위하여 맨 처음부터 따라가서 찾아야 하는 어려움이 있음
ArrayList vs. LinkedList 삭제와 삽입 측면 ArrayList는 삽입시에는 오른쪽 밀림(Shift Right) 연산이 일어나고 삭제시에는 왼쪽 밀림(Shift Left) 연산이 필요하므로 효율이 좋지 않음 LinkedList는 인접 노드의 포인터만 변경하면 되기 때문에 삭제와 삽입 시간 면에서 유리 A B C D E 1 2 3 4 5 N
ArrayList vs. LinkedList 어떤 자료구조? 검색위주이면 ArrayList가 유리 삽입 삭제 위주라면 LinkedList가 유리 최대 데이터 수가 예측 불가라면 LinkedList가 유리
예제
배열을 리스트로 변환하기 List를 일반 배열 자료로 변경하기 String[] stringArray = list.toArray(new String[list.size()]); 위에서 toArray의 파라미터로 String 타입의 비어있는 배열 객체를 넣어주어 toArray 메소드로 하여금 만들어야 할 배열의 정확한 타입과 원소 개수를 알도록 해준다. 일반 배열 자료를 List로 변경하기 List<String> list = Arrays.asList(new String[size]); 일반적인 배열을 리스트로 변환한다. String[] stringArray = {"AAA", "BBB", "CCC", "DDD"}; List<String> list = Arrays.asList(stringArray);
중간 점검 문제 1. ArrayList와 LinkedList의 차이점은 무엇인가?
집합(Set ) 집합(Set)은 원소의 중복을 허용하지 않는다. 순서에 상관없이 원소 저장 중복을 허용하지 않음 List 계열 보다는 호출 가능 메소드의 개수가 적음 Collection 인터페이스에 정의된 정도의 메소드만 지원 대표적인 메소드 add(Object o) contains(Object o) isEmpty() remove(Object o) size() iterator() toArray(Object[] a)
Set 인터페이스를 구현하는 방법 HashSet TreeSet 레드-블랙 트리(red-black tree)에 원소를 저장한다. 따라서 값에 따라서 순서가 결정되며 하지만 HashSet보다는 느리다. LinkedHashSet 해쉬 테이블과 연결 리스트를 결합한 것으로 원소들의 순서는 삽입되었던 순서와 같다.
예제 순서가 일정하지 않다!
대량 연산 메소드 위에서 addAll, retainAll, removeAll 메소드는 원집합인 s1을 변경한다. 원집합의 변경을 원하지 않으면 다음과 같이 코딩한다. Set<String> union = new HashSet<String>(s1); union.addAll(s2);
예제
큐(Queue) 큐는 먼저 들어온 데이터가 먼저 나가는 자료 구조 FIFO(First-In First-Out)
Queue 인터페이스 add() vs. offer() // 용량초과 시 예외발생 vs. false remove() vs. poll() // 원소삭제 후 가져옴, 예외 vs. null element() vs. peek() // 원소 삭제없이 가져옴, 예외 vs. null - 자바에서는 스택을 Stack클래스로 구현하여 제공하고 있지만, 큐는 Queue 인터페이스로만 정의해 놓고, 클래스를 제공하지 않았다. 대신 Queue 인터페이스를 구현한 클래스(LinkedList, PriorityQueue)를 사용하면 된다.
예제: 카운트 다운 타이머
우선 순위큐 (priority queue) 우선 순위큐(priority queue): 우선순위를 가진 항목들을 저장하는 큐 (heap 사용) FIFO 순서가 아니라 우선 순위가 높은 데이터가 먼저 나가게 된다. (무작위로 삽입 되었더라도 정렬된 상태로 추출) remove() 호출 시 가장 작은 원소가 추출된다. 가장 일반적인 큐: 스택이나 FIFO 큐를 우선 순위큐로 구현할 수 있다. Job Scheduling
예제
맵(Map) 사전과 같은 자료 구조 키(key)에 값(value)이 매핑된다.
맵(Map) Map 인터페이스의 개념 사전과 같은 자료 구조 키(key)에 값(value)이 매핑된다. 중복된 키를 가질 수 없다. 키가 제시되면 값을 돌려준다. 예 Key: 학생 객체의 학번, Value: 학생 객체 Map을 구현한 클래스 HashMap TreeMap 트리구조에 데이터를 정렬 만약 키들을 정렬된 순서로 돌려받을 필요가 있을 때 사용 LinkedHashMap
Map Map 사용 방법 데이터 저장 방법 객체 추출 방법 Map<String, Student> freshman = new HashMap<String, Student>(); Student kim = new Student("20090001", "김준표", "서울 서초구"); Freshman.put("2009001", kim); 객체 추출 방법 String s = "2009001"; st = freshman.get(s);
예제
Map Map 사용 예제 2 WordFreq.java import java.util.*; public class WordFreq { public static void main(String[] args) { Map<String, Integer> m = new HashMap<String, Integer>(); String[] sample = { "to", "be", "or", "not", "to", "be", "is", "a", "problem" }; // 문자열에 포함된 단어의 빈도를 계산한다. for (String a : sample) { Integer freq = m.get(a); m.put(a, (freq == null) ? 1 : freq + 1); } System.out.println(m.size() + " 단어가 있습니다."); // 7 System.out.println(m.containsKey("to")); // true System.out.println(m.isEmpty()); // false System.out.println(m); // {not=1, to=2, …} for (String a : sample) { int freq = m.get(a); m.put(a, (freq == 0) ? 1 : freq + 1); } Runtime 시 에러. Why?
Collections 클래스 Collections 클래스의 유용한 기능 및 정적 메소드 Sorting Shuffling Collections.sort(List list) 리스트의 정렬에 사용(ArrayList, LinkedList, Vector) 배열 리스트로 변경 후 사용!!! Shuffling Collections.shuffle(List list, Random rnd) 리스트내의 원소를 섞는데 사용 Searching Collections.binarySearch(List list, Object key) 리스트내에서 특정 원소가 있는지 검사하여 그 위치의 인덱스를 돌려줌
Collection 클래스 import java.util.*; public class Sort { Collections 클래스 사용 정렬 예제 1 Sort.java import java.util.*; public class Sort { public static void main(String[] args) { String[] sample = { "i", "walk", "the", "line" }; List<String> list = Arrays.asList(sample); // 배열을 리스트로 변경 Collections.sort(list); System.out.println(list); } 역순정렬: Collections.sort(list,Collections.reverseOrder()) [i, line, the, walk]
Collection 클래스 Collections 클래스 사용 정렬 예제 2-1 SortTest.java import java.util.*; class Student implements Comparable<Student> { int number; String name; public Student(int number, String name) { this.number = number; this.name = name; } public String toString() { return name; public int compareTo(Student s) { return number - s.number; public interface Comparable<T> { public int compareTo(T o); } public int compareTo(Student s) { return name.compareTo(s.name); } 이름으로 정렬
Collection 클래스 Collections 클래스 사용 예제 2-2 SortTest.java public class SortTest { public static void main(String[] args) { Student array[] = { new Student(20090002, "이철수"), new Student(20090001, "김철수"), new Student(20090003, "박철수") }; List<Student> list = Arrays.asList(array); Collections.sort(list); System.out.println(list); } [김철수, 이철수 , 박철수] // 번호순 이름으로!!!! 성과 이름으로!!!!
Collection 클래스 Collections 클래스 사용 섞기 예제 3 Shuffle.java import java.util.*; public class Shuffle { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); for (int i = 1; i <= 10; i++) list.add(i); Collections.shuffle(list, new Random(System.currentTimeMillis())); System.out.println(list); }
Collection 클래스 Collections 클래스 사용 탐색 예제 4 Search.java import java.util.*; public class Search { public static void main(String[] args) { int key = 50; List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < 100; i++) list.add(i); int index = Collections.binarySearch(list, key); System.out.println("탐색의 반환값 =" + index); } 탐색의 반환값 =50
Collection 클래스 Collections 클래스의 다른 메소드 Object min(Collection coll), Object max(Collection coll) 컬렉션에서 최대값과 최소값을 찾는다. void reverse(List list) list 원소들의 순서를 반대로 바꾼다. void fill(List list, Object obj) list에 지정된 obj로 원소를 채운다. void copy(List dest, List src) src리스트의 원소들을 dest 리스트에 복사한다. void swap(List list, int i, int j) list의 지정된 두 위치의 원소를 바꾼다.
Exercise 3. 다음은 Stack 클래스의 일부분이다. 1) int 대신에 제네릭 타입으로 표시하라. class Stack<T> { private T[] stack; public void push(T data) {…} public T pop() {…} } 2) String 타입의 데이타를 가지는 Stack 객체를 생성하는 문장. Stack<String> stack = new Stack<String>();
LAB. 2. 카드 게임 프로그램을 컬렉션 라이브리를 이용하여 작성하라. 카드를 나타내는 Card 클래스를 작성한다. 하나의 카드는 무늬(suit)와 숫자(number)를 가진다. 생성자, ToString 메소드 정의하라. 카드 데크를 나타내는 CardDeck 클래스를 작성하라. CardDeck은 총 52 개의 Card 객체를 저장하여야 한다. ArrayList를 사용하여 카드들을 저장하라. 생성자에서 모든 카드를 생성하라. (3) CardDeck 클래스에 카드를 섞는 메소드 shuffle()을 콜렉션 클래스의 정적메소드 shuffle()을 이용하여 정의하라. (4) CardDeck 클래스에 Deck의 처음에서 카드를 제거하여서 반환하는 메소드인 deal()을 작성하라. (5) 카드 경기자를 나타내는 Player 클래스를 구현하라. Player 클래스도 카드의 리스트를 가져야 한다.(ArrayList 사용할 것) (6) Player는 카드를 얻어서 자신의 리스트에 추가하는 메소드 getCard()를 정의하라. 또한 자신의 카드를 보여주는 showCards() 메소드를 작성하라. (7) main() 함수를 가지는 클래스 CardGame을 작성하라. CardDeck 클래스의 객체를 생성하고, Deck의 카드를 섞은 다음 Player 객체를 두 명 생성한다. 카드를 7장 씩 분배하고 각자의 카드를 화면에 표시하라.
private static String getHandValue(Player hand){. if(hand private static String getHandValue(Player hand){ if(hand.isRoyalFlush() == true) return "Royal Flush"; if(hand.isStraightFlush() == true) return "Straight Flush"; if(hand.isFourOfAKind() == true) return "Four of a Kind"; if(hand.isFullHouse() == true) return "Full House"; if(hand.isFlush() == true) return "Flush"; if(hand.isStraight() == true) return "Straight"; if(hand.isThreeOfAKind() == true) return "Three of a Kind"; if(hand.isTwoPair() == true) return "Two Pair"; if(hand.isPair() == true) return "Pair"; return "Nothing"; }
Q & A