본문 바로가기

자바공부

제네릭 클래스 만들기, 명품 자바 프로그래밍 7장

728x90
반응형

* 제네릭 클래스를 작성하는 방법은 기존의 클래스 작성 방법과 유사한데, 클래스 이름 다음에 일반화된 타입(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 방지

반응형