제네릭(Generic)

Ⅰ. 제네릭

1. 제네릭

 - 미리 정해둘 수 없는 참조 타입을 비워두는 기법

    → 즉, 어디에 어떤 타입이 쓰일지 모를 때, 그 타입의 자리를 비워둠

 - 제네릭을 이용하면 타입이 정해지지 않은 클래스와 인터페이스를 만들 수 있음

 

2. 제네릭 선언 방법

 -  < > 기호를 사용 (< > 안에 T를 기입)

    ※ T : 타입 매개변수 (비워두고 싶은 부분 표시)

    ※ 매개변수화 타입 : 제네릭에 인자를 넣어서 완성된 타입

 

3. 타입 매개변수의 일반적인 이름 규칙

 - 대문자로 알파벳 하나만 쓰기

 

4. 제네릭 관련 예제1

// Example1 (제네릭이 필요한 이유)
package sample;

class Apple {
	// toString() : 오브젝트클래스로부터 상속되는 오버라이딩 메소드
	// :객체를 선언하고 그 객체 이름을 print 하였을때,
	//  객체의 시스템 주소 값이 아닌 의미 있는 값으로 재설정 시키는 역할을 수행
	public String toString() {
		return "I am an apple";
	}
}

class Banana {
	public String toString() {
		return "I am a banana";
	}
}

class Box {
	private Object fruit; // 위 2개의 클래스는 Object의 자녀 클래스 (다형성을 활용)
	public void set(Object f) {
		this.fruit = f;
	}
	public Object get() {
		return this.fruit;
	}
}

public class Main {
	public static void main(String[] args) {
		Box appleBox = new Box();
		Box bananaBox = new Box();
		
		// 현재 상태는, 실수를 유발하기 쉬운 상태
		appleBox.set(new Apple());
		bananaBox.set("바나나");			// 다형성으로 인한 실수 유발
		
		Apple a = (Apple)appleBox.get();	// Object를 다운캐스팅
		Banana b = (Banana)bananaBox.get();	// 문제를 파악하기조타 쉽지 않음
		
		System.out.println(a);
		System.out.println(b);
	}
}
// 다형성을 활용하는 것은 좋으나, 어떤 자료를 받을지 명시가 되어 있지 않아 주의하지 않으면 실수 발생

 

// Example2
package sample;

class Apple {
	public String toString() {
		return "I am an apple";
	}
}

class Banana {
	public String toString() {
		return "I am a banana";
	}
}

// 제네릭
class Bucket<T> {
	private T fruit;
	public void set(T f) {
		this.fruit = f;
	}
	public T get() {
		return this.fruit;
	}
}

public class Main {
	public static void main(String[] args) {
		// 제네릭에 인자 넣어서 완성된 타입 : 매개변수화 타입
		Bucket<Apple> appleBucket = new Bucket<>();
		Bucket<Banana> bananaBucket = new Bucket<>();
		
		appleBucket.set(new Apple());
		bananaBucket.set(new Banana());
		
		Apple a = appleBucket.get();
		Banana b = bananaBucket.get();
		
		System.out.println(a);
		System.out.println(b);
		
		
	}
}

 

5. 다중 매개변수 기반 제네릭 크래스

// Example3
package sample;

// 서랍의 위아래 칸에 객체 넣기 예제
// 서랍의 Top & Bottom
class Drawer<T, B> {
	private T top;
	private B bottom;
	public void set(T t, B b) {
		this.top = t;
		this.bottom = b;
	}
	public String toString() {
		return this.top + " & " + this.bottom;
	}
}

public class Main {
	public static void main(String[] args) {
    	// <> 안에 int가 아닌 Integer 즉, 참조자료형으로 써야 함
		Drawer <String, Integer> d = new Drawer<>();
		d.set("pencil", 10);
		System.out.println(d);
	}
}

 

6. 제네릭의 특징

 - 타입 인자를 받아서 사용 (단, 참조 자료형이어야 함)

 - 자바에서는 기본 자료형의 객체화 타입인 '래퍼 클래스' 라는 것을 지원

    ※ 래퍼 클래스 : 기본 자료형 값을 보유한 포장지를 만드는 클래스

        - 모든 기본 자료형은 대응하는 래퍼 클래스가 존재

        - 박싱 : 기본 자료형 값을 래퍼 클래스로 초기화 하는 작업

        - 언박싱 : 래퍼 클래스는 기본 자료형 값을 다시 내보낼 수 있음

    ※ 래퍼 클래스는 좋지만 기본 자료형보다 훨씬 큰 메모리 크기를 사용해야 하므로 권장하지 않음

// Example
package sample;

public class Main {
	public static void main(String[] args) {
		int a = 33;				// 일반 변수. 이는 곧 값이며, 객체가 아님
		
		// 래퍼 클래스는 객체를 생성
		// 박싱 작업 실행
		Integer num1 = new Integer(33);		// 33과 같은 취급을 받는 객체
		Double num2 = new Double(3.14);		// 3.14와 같은 취급을 받는 객체
		Integer num3 = 50;			// 래퍼클래스는 그냥 값만 써도 '오토 박싱'을 함
		
		// 래퍼클래스는 기본자료형 값을 다시 내보낼 수 있음 (언박싱)
		int b = num3.intValue();	
	}
}

 

 - 매개변수화 타입타입임 → 따라서 타입 인자가 될 수 있음

// Example
package sample;

// 매개변수화 타입도 타입임 → 따라서 타입 인자가 될 수 있음
class Container<T> {
	private T data;
	public void set(T d) {
		this.data = d;
	}
	public T get() {
		return this.data;
	}
}

public class Main {
	public static void main(String[] args) {
		Container<String> a = new Container<>();
		a.set("자바 열공!");
		
		// 매개변수화 타입으로 생성한 객체를 제네릭으로 변수화 할 수 있음
		Container<Container<String>> b = new Container<>();
		b.set(a);
		
		Container<Container<Container<String>>> c = new Container<>();
		c.set(b);
		
		System.out.println(c.get().get().get());  // 3번 중첩을 하였으므로 3번 get을 해야 함
		
	}
}

 

7. 제네릭 관련 예제2

// Example1
package sample;

// 아래 코드가 실행되도록 클래스 Basic을 정의해보자
class Basic<T> {
	private T data;
	public void setData(T d) {
		this.data = d;
	}
	public void showYourData() {
		System.out.println(this.data); 
	}
}

public class Main {
	public static void main(String[] args) {
		Basic<String> base1 = new Basic<>();
		Basic<Integer> base2 = new Basic<>();
		Basic<Double> base3 = new Basic<>();
		base1.setData("하하");
		base2.setData(10);
		base3.setData(10.11);
		
		// 콘솔 출력 println();
		base1.showYourData();	// 하하
		base2.showYourData();	// 10
		base3.showYourData();	// 10.11
	}
}

 

// Example2
package sample;

// 아래 코드가 실행 가능하도록 클래스를 정의해보자
class MyArray<T> {
	T[] arr;
	void setArr(T[] a) {
		this.arr = a;
	}
	void viewArr() {
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i]);
			System.out.print(" ");
		}
		System.out.println("");
	}
}

public class Main {
	public static void main(String[] args) {
		MyArray<Integer> arr1 = new MyArray<>();
		MyArray<String> arr2 = new MyArray<>();
		
		Integer[] i_arr = {1, 2, 3 ,4, 5};
		arr1.setArr(i_arr);
		String[] s_arr = {"hi", "bye"};
		arr2.setArr(s_arr);
		
		// println();
		arr1.viewArr();	// 1 2 3 4 5
		arr2.viewArr();	// hi bye
	}
}