어서와 Java는 처음이지! 제15장 제네릭과 컬렉션
제네릭이란? 제네릭 프로그래밍(generic programming)이란 다양한 종류의 데이터를 처리할 수 있는 클래스와 메소드를 작성하는 기법이 다
기존의 방법 일반적인 객체를 처리하려면 Object 참조 변수를 사용 예제로 하나의 데이터를 저장하는 Box 클래스를 살펴보자.
예제 public class Box { private Object data; public void set(Object data) { this.data = data; } public Object get() { return data; } } Box b = new Box(); b.set("Hello World!"); // ① 문자열 객체를 저장 String s = (String)b.get(); // ② Object 타입을 String 타입으로 형변환 b.set(new Integer(10)); // ③ 정수 객체를 저장 Integer i = (Integer)b.get( ); // ④ Object 타입을 Integer 타입으로 형변환
제네릭을 이용한 방법 class Box<T> { private T data; public void set(T data) { this.data = data; } public T get() { return data; } } Box<String> b = new Box<String>(); Box<Integer> b = new Box<Integer>();
예제 설명
LAB: SimplePair 클래스 작성하기 public class SimplePairTest { public static void main(String[] args) { SimplePair<String> pair = new SimplePair<String>("apple", "tomato"); System.out.println(pair.getFirst()); System.out.println(pair.getSecond()); } apple tomato
SOLUTION public class SimplePair<T> { private T data1; public SimplePair(T data1, T data2) { this.data1 = data1; this.data2 = data2; } public T getFirst() { return data1; } public T getSecond() { return data2; } public void setFirst(T data1) { this.data1 = data1; } public void setSecond(T data2) { this.data2 = data2; }
다중 타입 매개 변수(Multiple Type Parameters) public class OrderedPair<K, V> { private K key; private V value; public OrderedPair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; }
다중 타입 매개 변수(Multiple Type Parameters) public class OrderedPairTest { public static void main(String[] args) { OrderedPair<String, Integer> p1 = new OrderedPair<String, Integer>("mykey", 12345678); OrderedPair<String, String> p2 = new OrderedPair<String, String>("java", "a programming laguage"); System.out.println(p1.getKey() + " " + p1.getValue()); System.out.println(p2.getKey() + " " + p2.getValue()); } mykey 12345678 java a programming laguage
제네릭 메소드 하지만 일반 클래스의 메소드에서도 타입 매개 변수를 사용하 여서 제네릭 메소드를 정의할 수 있다. 이 경우에는 타입 매개 변수의 범위가 메소드 내부로 제한된다. public class MyArrayAlg { public static <T> T getLast(T[] a) { return a[a.length - 1]; } public class MyArrayAlgTest { public static void main(String[] args) { String[] language = { "C++", "C#", "JAVA" }; String last = MyArrayAlg.getLast(language); // last는 “JAVA" System.out.println(last); }
LAB 배열 안에서 i번째 요소와 j번째 요소를 교환하는 swap(int i, int j) 메소드를 제네릭 메소드로 작성하여 보자. public class MyArrayAlgTest { public static void main(String[] args) { String[] language = { "C++", "C#", "JAVA" }; MyArrayAlg.swap(language, 1, 2); for(String value : language) System.out.println(value); }
SOLUTION public class MyArrayAlg { public static <T> void swap(T[] a, int i, int j) { T tmp = a[i]; a[i] = a[j]; a[j] = tmp; }
LAB 다음 코드와 같이 정수 배열, 실수 배열, 문자 배열을 모두 출력 할 수 있는 제네릭 메소드 printArray()를 작성하여 보자. public class GenericMethodTest { public static void main(String args[]) { Integer[] iArray = { 10, 20, 30, 40, 50 }; Double[] dArray = { 1.1, 1.2, 1.3, 1.4, 1.5 }; Character[] cArray = { 'K', 'O', 'R', 'E', 'A' }; printArray(iArray); printArray(dArray); printArray(cArray); }
SOLUTION public class GenericMethodTest { public static void main(String args[]) { Integer[] iArray = { 10, 20, 30, 40, 50 Double[] dArray = { 1.1, 1.2, 1.3, 1.4, 1.5 }; Character[] cArray = { 'K', 'O', 'R', 'E', 'A' }; printArray(iArray); printArray(dArray); printArray(cArray); } public static <T> void printArray(T[] array) { for (T element : array) { System.out.printf("%s ", element); System.out.println();
한정된 타입 매개 변수 때때로 특정한 종류의 객체들만을 받게 하고 싶은 경우가 있다. public class MyArrayAlg { public static <T extends Comparable> T getMax(T[] a) if (a == null || a.length == 0) return null; T largest = a[0]; for (int i = 1; i < a.length; i++) if (largest.compareTo(a[i]) < 0) largest = a[i]; return largest; }
제네릭과 상속 제네릭과 상속에 대하여 생각해보자. 자바 라이브러리에는 Number 클래스를 상속받아서 Integer와 Double 클래스를 정의하고 있다. Box<Number> box = new Box<Number>(); box.add(new Integer(10)); // Number 객체 대신에 Integer 객체를 주어도 된다. box.add(new Double(10.1)); // Number 객체 대신에 Double 객체를 주어도 된다. public void process(Box<Number> box) { ... } 어떤 타입을 받을 수 있을까?
제네릭과 상속 Integer가 Number의 자식이긴 하지만, Box<Integer>는 Box<Number>의 자식은 아니다.
상한이 있는 와일드 카드 어떤 클래스 A의 자손 클래스들을 와일드 카드로 표시하려면 <? extends A>와 같이 표시한다. 이것을 상한이 있는 와일드 카드(Upper Bounded Wildcard)라 고 한다. public static double sumOfList(List<? extends Number> list) { double s = 0.0; for (Number n : list) s += n.doubleValue(); return s; } List<Integer> li = Arrays.asList(1, 2, 3) System.out.println("sum = " + sumOfList(li))
제한없는 와일드 카드 제한없는 와일드 카드(Unbounded Wildcard)는 단순히 ?으로 만 이루어진다. 예를 들면 List<?>와 같다. 와일드 카드 ?은 모든 타입에 매치된다. import java.util.List; public class MyList { public static void printList(List<?> list) { for (Object elem : list) System.out.print(elem + " "); System.out.println(); }
import java.util.Arrays; import java.util.List; public class WildCardTest { public static void main(String[] args) { List<Integer> li = Arrays.asList(1, 2, 3); List<String> ls = Arrays.asList("one", "two", "three"); MyList.printList(li); MyList.printList(ls); } 1 2 3 one two three
하한이 있는 와일드 카드 어떤 클래스의 조상 클래스들을 와일드 카드로 나타내려면 <? super A>와 같은 문법을 사용한다. public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); }
정리 아래 그림을 완벽하게 이해할 수 있으면 어느 정도 제네릭 공부 는 된 셈이다.
컬렉션 컬렉션(collection)은 자바에서 자료 구조를 구현한 클래스 자료 구조로는 리스트(list), 스택(stack), 큐(queue), 집합(set), 해 쉬 테이블(hash table) 등이 있다.
컬렉션의 역사 초기 버전: Vector, Stack, HashTable, Bitset, Enumeration이 그 것이다. 버전 1.2부터는 풍부한 컬렉션 라이브러리가 제공 인터페이스와 구현을 분리 (예) List 인터페이스를 ArrayList와 LinkedList 클래스가 구현
컬렉션의 예: Vector 클래스 Vector 클래스는 java.util 패키지에 있는 컬렉션의 일종으로 가 변 크기의 배열(dynamic array)을 구현하고 있다.
예제 import java.util.Vector; public class VectorTest { public static void main(String[] args) { Vector vc = new Vector(); vc.add("Hello World!"); vc.add(new Integer(10)); vc.add(20); System.out.println("vector size :" + vc.size()); for (int i = 0; i < vc.size(); i++) { System.out.println("vector element " + i + " :" + vc.get(i)); } String s = (String)vc.get(0);
예제 vector size :3 vector element 0 :Hello World! vector element 1 :10
컬렉션 인터페이스와 컬렉션 클래스 자바는 컬렉션 인터페이스와 컬렉션 클래스로 나누어서 제공한 다. 자바에서는 컬렉션 인터페이스를 구현한 클래스도 함께 제 공하므로 이것을 간단하게 사용할 수도 있고 아니면 각자 필요 에 맞추어 인터페이스를 자신의 클래스로 구현할 수도 있다.
Collection 인터페이스
Collection 인터페이스
List 인터페이스 리스트(List)는 순서를 가지는 요소들의 모임으로 중복된 요소 를 가질 수 있다.
ArrayList ArrayList를 배열(Array)의 향상된 버전 또는 가변 크기의 배열이 라고 생각하면 된다. ArrayList<String> list = new ArrayList<String>(); 원소 추가 list.add( "MILK" ); list.add( "BREAD" ); list.add( "BUTTER" );
ArrayList<String> list = new ArrayList<String>(); list.add( "MILK" ); list.add( "BREAD" ); list.add( "BUTTER" ); list.add( 1, "APPLE" ); // 인덱스 1에 "APPLE"을 삽입 list.set( 2, "GRAPE" ); // 인덱스 2의 원소를 "GRAPE"로 대체 list.remove( 3 ); // 인덱스 3의 원소를 삭제한다.
LinkedList 빈번하게 삽입과 삭제가 일어나는 경우에 사용
예제 import java.util.*; public class LinkedListTest { public static void main(String args[]) { LinkedList<String> list = new LinkedList<String>(); list.add("MILK"); list.add("BREAD"); list.add("BUTTER"); list.add(1, "APPLE"); // 인덱스 1에 “APPLE"을 삽입 list.set(2, "GRAPE"); // 인덱스 2의 원소를 “GRAPE"로 대체 list.remove(3); // 인덱스 3의 원소를 삭제한다. for (int i = 0; i < list.size(); i++) System.out.println(list.get(i)); }
반복자 사용하기 ArrayList<String> list = new ArrayList<String>(); list.add("하나“); list.add("둘“); list.add("셋“); list.add("넷“); String s; Iterator e = list.iterator(); while(e.hasNext()) { s = (String)e.next(); // 반복자는 Object 타입을 반환! System.out.println(s); } MILK APPLE GRAPE
배열을 리스트로 변환하기 List<String> list = Arrays.asList(new String[size]); 일반적인 배열을 리스트로 변환한다.
Set 집합(Set)은 원소의 중복을 허용하지 않는다.
Set 인터페이스를 구현하는 방법 HashSet TreeSet LinkedHashSet 레드-블랙 트리(red-black tree)에 원소를 저장한다. 따라서 값에 따라 서 순서가 결정되며 하지만 HashSet보다는 느리다. LinkedHashSet 해쉬 테이블과 연결 리스트를 결합한 것으로 원소들의 순서는 삽입되 었던 순서와 같다.
예제 import java.util.*; public class SetTest { public static void main(String args[]) { HashSet<String> set = new HashSet<String>(); set.add("Milk"); set.add("Bread"); set.add("Butter"); set.add("Cheese"); set.add("Ham"); System.out.println(set); } [Bread, Milk, Butter, Ham, Cheese]
예제 import java.util.*; public class FindDupplication { public static void main(String[] args) { Set<String> s = new HashSet<String>(); String[] sample = { "단어", "중복", "구절", "중복" }; for (String a : sample) if (!s.add(a)) System.out.println("중복된 단어 " + a); System.out.println(s.size() + " 중복되지 않은 단어: " + s); } 중복된 단어 중복 3 중복되지 않은 단어: [중복, 구절, 단어]
대량 연산 메소드 s1.containsAll(s2) — 만약 s2가 s1의 부분 집합이면 참이다. s1.addAll(s2) — s1을 s1과 s2의 합집합으로 만든다. s1.retainAll(s2) — s1을 s1과 s2의 교집합으로 만든다. s1.removeAll(s2) — s1을 s1과 s2의 차집합으로 만든다.
예제 public class SetTest1 { public static void main(String[] args) { Set<String> s1 = new HashSet<String>(); Set<String> s2 = new HashSet<String>(); s1.add("A"); s1.add("B"); s1.add("C"); s2.add("A"); s2.add("D"); Set<String> union = new HashSet<String>(s1); union.addAll(s2); Set<String> intersection = new HashSet<String>(s1); intersection.retainAll(s2); System.out.println("합집합 " + union); System.out.println("교집합 " + intersection); } 합집합 [D, A, B, C] 교집합 [A]
큐(queue) 큐는 후단(tail)에서 원소를 추가하고 전단(head)에서 원소를 삭 제한다.
예제 import java.util.*; public class QueueTest { public static void main(String[] args) throws InterruptedException { int time = 10; Queue<Integer> queue = new LinkedList<Integer>(); for (int i = time; i >= 0; i--) queue.add(i); while (!queue.isEmpty()) { System.out.print(queue.remove()+" "); Thread.sleep(1000); // 현재의 스레드를 1초간 재운다. } 10 9 8 7 6 5 4 3 2 1 0
우선순위큐 우선 순위큐는 원소들이 무작위로 삽입되었더라도 정렬된 상태 로 원소들을 추출한다. 즉 remove()를 호출할 때마다 가장 작은 원소가 추출된다. 우선 순위큐는 히프(heap)라고 하는 자료 구조를 내부적으로 사 용한다.
예제 import java.util.*; public class PriorityQueueTest { public static void main(String[] args) { PriorityQueue<Integer> pq = new PriorityQueue<Integer>(); pq.add(30); pq.add(80); pq.add(20); for (Integer o : pq) System.out.println(o); System.out.println("원소 삭제"); while (!pq.isEmpty()) System.out.println(pq.remove()); } 20 80 30 원소 삭제
Map Map은 많은 데이터 중에서 원하는 데이터를 빠르게 찾을 수 있 는 자료 구조이다. 맵은 사전과 같은 자료 구조이다.
예제 import java.util.*; class Student { int number; String name; public Student(int number, String name) { this.number = number; this.name = name; } public String toString() { return name;
예제 public class MapTest { public static void main(String[] args) { Map<String, Student> st = new HashMap<String, Student>(); st.put("20090001", new Student(20090001, "구준표")); st.put("20090002", new Student(20090002, "금잔디")); st.put("20090003", new Student(20090003, "윤지후")); // 모든 항목을 출력한다. System.out.println(st); // 하나의 항목을 삭제한다. st.remove("20090002"); // 하나의 항목을 대치한다. st.put("20090003", new Student(20090003, "소이정")); // 값을 참조한다. System.out.println(st.get("20090003")); // 모든 항목을 방문한다. for (Map.Entry<String, Student> s : st.entrySet()) { String key = s.getKey(); Student value = s.getValue(); System.out.println("key=" + key + ", value=" + value); } 예제
예제 {20090001=구준표, 20090002=금잔디, 20090003=윤지후} 소이정 key=20090001, value=구준표 key=20090003, value=소이정
Collections 클래스 Collections 클래스는 여러 유용한 알고리즘을 구현한 메소드들 을 제공한다. 정렬(Sorting) 섞기(Shuffling) 탐색(Searching)
정렬 정렬은 데이터를 어떤 기준에 의하여 순서대로 나열하는 것이 다.
예제 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); } [i, line, the, walk]
탐색 탐색이란 리스트 안에서 원하는 원소를 찾는 것이다.
예제 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
LAB: 영어사전의 구현 여기서는 Map을 사용하여서 영어 사전을 구현하여 보자. 사용 자가 단어를 입력하면 단어의 설명을 보여준다. 영어 단어를 입력하시오:map 단어의 의미는 지도 영어 단어를 입력하시오:school 단어의 의미는 학교 영어 단어를 입력하시오:quit
예제 import java.util.*; public class EnglishDic { public static void main(String[] args) { Map<String, String> st = new HashMap<String, String>(); st.put("map", "지도"); st.put("java", "자바"); st.put("school", "학교"); Scanner sc = new Scanner(System.in); do { System.out.print("영어 단어를 입력하시오:"); String key = sc.next(); if( key.equals("quit") ) break; System.out.println("단어의 의미는 " + st.get(key)); } while(true); }