* 제네릭 클래스를 작성하는 방법은 기존의 클래스 작성 방법과 유사한데, 클래스 이름 다음에 일반화된 타입(generic type)의 매개변수를 <와 > 사이에 추가한다는 차이가 있다. 간단한 제네릭 클래스를 작성해보자.
1) 제네릭 클래스 작성
타입 매개변수 T를 가진 제네릭 클래스 MyClass
public class MyClass<T> { // 제네릭 클래스 Myclass, 타입 매개변수 T
T val; // 변수 val의 타입은 T
void set(T a){
val = a; // T 타입의 값 a를 val에 지정
}
T get(){
return val; // T 타입의 값 val 리턴
}
}
2) 제네릭 클래스에 대한 레퍼런스 변수 선언
제네릭 클래스의 레퍼런스 변수를 선언할 때는 타입 매개변수에 구체적인 타입을 적는다.
MyClass<String> s; // <T>를 String으로 구체화
List<Integer> li; // <E>를 Integer로 구체화
Vector<String> vs; // <E>를 String으로 구체화
3) 제네릭 객체 생성 - 구체화(specialization)
구체화: 제네릭 클래스에 구체적인 타입을 대입하여 구체적인 객체를 생성하는 과정.
이 과정은 자바 컴파일러에 의해 이루어진다.
Ex)
MyClass<String> s = new MyClass<String>(); // 제네릭 타입 T를 String으로 구체화
s.set("hello");
System.out.println(s.get()); "hello" 출력
MyClass<Integer> n = new MyClass<Integer>(); // 제네릭 타입 T를 Integer로 구체화
n.set(5);
System.out.println(n.get()); // 숫자 5 출력
* 구체화된 MyClass<String>의 코드 (컴파일러에 의해)
public class MyClass<String> {
String val; // 변수 val의 타입은 String
void set(String a){
val = a; // String 타입의 문자열 a를 val에 지정
}
String get(){
return val; // String 타입의 문자열 val 리턴
}
}
* 타입 매개변수
제네릭 클래스 내에서 제네릭 타입을 가진 객체의 생성은 허용되지 않는다.
컴파일러가 MyVector<E> 클래스의 new E() 라인을 컴파일 할 때, E에 대한 구체적인 타입을 알 수 없어, 호출될 생성자를 결정할 수 없고, 또한 객체 생성 시 어떤 크기의 메모리르 할당해야 할 지 전혀 알 수 없기 때문이다.
* 제네릭 스택 만들기
package Ch7_Ex;
import java.util.*;
class GStack<T> {
// 제네릭 스택 선언. 제네릭 타입 T
int tos;
Object [] stck; // 스택에 요소를 저장할 공간 배열
public GStack() {
tos = 0;
stck = new Object [10];
}
public void push(T item) {
if(tos == 10) // 스택이 꽉 차서 더 이상 요소를 삽입할 수 없음
return;
stck[tos] = item;
tos++;
}
public T pop() {
if(tos == 0) // 스택이 비어 있어 꺼낼 요소가 없음
return null;
tos--;
return (T)stck[tos]; // 타입 매개변수 타입으로 캐스팅
}
}
public class MyStack {
public static void main(String[] args) {
GStack<String> stringStack = new GStack<String>(); // String 타입의 GStack 생성
stringStack.push("Seoul");
stringStack.push("busan");
stringStack.push("LA");
for(int n = 0; n<3; n++)
System.out.println(stringStack.pop()); //stringStack 스택에 있는 3 개의 문자열 팝
GStack<Integer> intStack = new GStack<Integer>(); // Integer 타입의 GStack 생성
intStack.push(1);
intStack.push(3);
intStack.push(5);
for(int n=0; n<3; n++)
System.out.println(intStack.pop()); // intStack 스택에 있는 3개의 정수 팝
}
}
제네릭 클래스 내에서 제네릭 타입의 객체를 생성할 수 없는 것과 같은 이유로 배열도 생성할 수 없으므로, (오류) stck = new T[10]; -->> Object의 배열로 생성해야 한다. stck = new Object[10];
* 제네릭 메소드
클래스의 일부 메소드만 제네릭으로 구현할 수도 있다.
ex)
class GenericMethodEx {
static <T> void toStack(T[] a, GStack<T> gs){
for (int i=0; i<a.length; i++){
gs.push(a[i]);
}
}
}
타입 매개변수는 메소드의 리턴 타입 앞에 선언된다.
제네릭 메소드를 호출할 떄는 컴파일러가 메소드의 인자를 통해 타입을 유추할 수 있어 제네릭 클래스나 인터페이스와는 달리 타입을 명시하지 않아도 된다.
* 스택의 내용을 반대로 만드는 제네릭 메소드 만들기
package Ch7_Ex;
import java.util.*;
public class GenericMethodEx {
public static <T> GStack<T> reverse(GStack<T> a) {
// T가 타입 매개변수읜 제네릭 메소드
GStack<T> s = new GStack<T>(); // 스택 a를 반대로 저장할 목적 GStack 생성
while (true) {
T tmp;
tmp = a.pop();
if (tmp==null) // 스택이 비었음
break;
else
s.push(tmp); // 새 스택에 요소를 삽입
}
return s; // 새 스택을 리턴
}
public static void main(String[] args) {
GStack<Double> gs = new GStack<Double>(); // Double 타입의 GStack 생성
for (int i=0; i<5; i++) {
// 5개의 요소를 스택에 push
gs.push(new Double(i));
}
gs = reverse(gs);
for(int i=0;i<5;i++)
System.out.println(gs.pop());
}
}
* 제네릭의 장점
1) 동적으로 타입이 결정되지 않고 컴파일 시에 타입이 결정되므로 보다 안전한 프로그래밍 가능
2) 런타임 타입 충돌 문제 방지
3) 개발 시 타입 캐스팅 절차 불필요
4) ClassCastException 방지
'IT > 자바공부' 카테고리의 다른 글
문자 스트림과 파일 입출력 (0) | 2022.06.13 |
---|---|
스트림 입출력 Stream, 명품 자바 프로그래밍 8장 (0) | 2022.06.13 |
LinkedList, Collections 클래스 활용, 자바 명품 프로그래밍 7장 (0) | 2022.06.11 |
HashMap, 자바 명품 프로그래밍 7장 (0) | 2022.06.11 |
Iterator 순차 검색, 명품 자바 프로그래밍 7장 (0) | 2022.06.10 |