[Java 활용] 3.1 제네릭(Generic)

제네릭(Generic)은 여러 형식에 맞게 재사용할 수 있는 코드를 정의하는 기술을 말합니다. 제네릭 코드를 정의할 때는 가상의 형식 이름을 <가상 형식 이름> 처럼 정의하고 사용하는 곳에서 어떠한 형식에 관한 코드를 사용할 것인지 명시하여 사용하는 기법입니다.

먼저 제네릭 클래스를 정의할 때는 클래스명 뒤에 <가상 형식 이름>을 명시하여 만들 수 있습니다.

class 클래스명<가상 형식 이름>{

}

사용하는 곳에서는 가상 형식 이름 대신 구체적으로 사용할 형식을 결정하여 <사용할 형식 이름>을 명시하여 사용합니다.

클래스명<사용할 형식 이름> 변수명 = new 클래스명<사용할 형식 이름>();

다음은 정수 형식을 보관하는 정수 적 배열 클래스와 제네릭 형식으로 원하는 형식을 사용자가 결정할 수 있는 제네릭 동적 배열 클래스를 정의한 것입니다.

▷ 소스 3.1 정수 동적 배열과 제네릭 동적 배열 예

//IntDArray.java
//정수 동적 배열
public class IntDArray {
	int[] buffer;
	int capacity;
	int usage;
	public IntDArray(int capacity){
		this.capacity = capacity;
		buffer = new int[capacity];
		usage = 0;
	}	
	public boolean isEmpty(){
		return usage == 0;
	}
	public boolean isFull(){
		return usage == capacity;
	}
	public int size(){
		return usage;
	}
	public boolean add(int value){
		if(isFull()){
			return false;
		}			
		buffer[usage] = value;
		usage++;
		return true;
	}
	public void viewAll(){
		String outstr = String.format("저장소 크기:%d 보관개수:%d",capacity,usage);
		System.out.println(outstr);
		for(int i = 0; i<usage;i++){
			System.out.print(buffer[i]+" ");
		}
		System.out.println();
	}
}
//DArray.java
//제네릭 동적 배열
public class DArray<datatype> {
	Object[] buffer;
	int capacity;
	int usage;
	public DArray(int capacity){
		this.capacity = capacity;		 
		buffer = new Object[capacity];
		usage = 0;
	}	
	public boolean isEmpty(){
		return usage == 0;
	}
	public boolean isFull(){
		return usage == capacity;
	}
	public int size(){
		return usage;
	}
	public boolean add(datatype value){
		if(isFull()){
			return false;
		}			
		buffer[usage] = value;
		usage++;
		return true;
	}
	public void viewAll(){
		String outstr = String.format("저장소 크기:%d 보관개수:%d",capacity,usage);
		System.out.println(outstr);
		for(int i = 0; i<usage;i++){
			System.out.print(buffer[i]+" ");
		}
		System.out.println();
	}
}
//Program.java
//정수 동적 배열과 제네릭 동적 배열
public class Program {
	public static void main(String[] args){
		System.out.println("==Test IntDArray===");
		IntDArray idarr = new IntDArray(10);
		idarr.viewAll();
		idarr.add(3);
		idarr.viewAll();
		idarr.add(2);
		idarr.viewAll();
		idarr.add(6);
		idarr.viewAll();
		
		System.out.println("==Test DArray<integer>===");
		DArray<Integer> darr = new DArray<Integer>(10);
		darr.viewAll();
		darr.add(3);
		darr.viewAll();
		darr.add(2);
		darr.viewAll();
		darr.add(6);
		darr.viewAll();
		
		System.out.println("==Test DArray<String>===");
		DArray<String> darr2 = new DArray<String>(10);
		darr2.viewAll();
		darr2.add("Hello");
		darr2.viewAll();
		darr2.add("언제나 휴일");
		darr2.viewAll();
		darr2.add("ehpub.co.kr");
		darr2.viewAll();
	}
}

▷ 소스 3.1 실행 결과

==Test IntDArray===
저장소 크기:10 보관개수:0

저장소 크기:10 보관개수:1
3 
저장소 크기:10 보관개수:2
3 2 
저장소 크기:10 보관개수:3
3 2 6 
==Test DArray<integer>===
저장소 크기:10 보관개수:0

저장소 크기:10 보관개수:1
3 
저장소 크기:10 보관개수:2
3 2 
저장소 크기:10 보관개수:3
3 2 6 
==Test DArray<String>===
저장소 크기:10 보관개수:0

저장소 크기:10 보관개수:1
Hello 
저장소 크기:10 보관개수:2
Hello 언제나 휴일 
저장소 크기:10 보관개수:3
Hello 언제나 휴일 ehpub.co.kr 

또한 메소드의 특정 인자 형식을 제네릭 형태로 표현할 수도 있습니다. 이 때 메소드 리턴 형식 이름 앞에 <가상 형식 이름>으로 명시하여 작성합니다. 이러한 메서드를 제네릭 메서드라 부르며 호출하는 곳에서는 실제 값 형식을 알고 있기 때문에 형식 이름을 명시할 필요가 없습니다.

다음은 제네릭 메서드를 정의하여 사용한 예입니다. 여기서 제네릭 메서드는 전달받은 값을 출력한 후에 다시 그 값을 반환하는 메서드로 큰 의미는 없습니다. 단지 제네릭 메서드를 보여주기 위함입니다.

▷ 소스 3.2 제네릭 메서드

//제네릭 메서드 
public class Program {
	static <T> T Foo(T value){
		System.out.print(value);
		System.out.println();
		return value;		
	}
	public static void main(String[] args){
		int i = 10;
		int re = Foo(i);
		System.out.println("re:"+re);
	}
}

▷ 소스 3.2 실행 결과

10
re:10

제네릭을 표현할 때 특정한 규약을 따르는 형식만 사용할 때도 있습니다. 만약 정렬 메서드를 제공하려면 개체의 값을 비교할 수 있어야 할 것입니다. Java 라이브러리에는 Comparable 인터페이스를 정의하고 있고 비교할 수 있는 형식을 정의할 때 Comparable 인터페이스를 기반으로 구현 클래스를 정의하는 것을 권장합니다. 따라서 정렬 메서드를 제공할 때 최소한 Comparable 인터페이스를 기반의 구현 클래스로 한정할 필요가 있습니다. 주의할 점은 인터페이스일 때도 <가상 형식 이름 extends 특정 형식 이름> 형태로 표현해야 합니다.

다음은 정적 메서드 Sort의 입력 매개 변수로 Comparable 인터페이스로 한정한 제네릭 메서드를 구현한 예제입니다.

▷ 소스 3.3 Comparable 인터페이스로 한정한 제네릭 메서드 Sort

//Comparable 인터페이스로 한정한 제네릭 메서드 Sort
public class Program {
	static <dt extends Comparable> void Sort(dt[] arr){
		
		for(int i = arr.length; i>1; i--){
			for(int j=1; j<i;j++){
				if(arr[j].compareTo(arr[j-1])<0){
					dt temp = arr[j-1];
					arr[j-1] = arr[j];
					arr[j] = temp;
				}
			}
		}
	}
	public static void main(String[] args){
		String[] arr = {"홍길동", "강감찬", "을지문덕", "김구", "이순신"};
		System.out.println("정렬 전");
		for(int i = 0; i<arr.length;i++){
			System.out.print(arr[i]+" ");
		}
		System.out.println();
		
		Sort(arr);		
		
		System.out.println("정렬 후");
		for(int i = 0; i<arr.length;i++){
			System.out.print(arr[i]+" ");
		}
		System.out.println();
	}
}

▷ 소스 3.3 실행 결과

정렬 전
홍길동 강감찬 을지문덕 김구 이순신 
정렬 후
강감찬 김구 을지문덕 이순신 홍길동