자바/자바 입문 공부일지

자바 기초 공부 일지 35. 제네릭generic 의 기본 문법

Tomitom 2022. 11. 2. 10:46
반응형

 

 

 

● 제네릭generic이란 데이터 타입을 일반화하는 것을 의미합니다. 

즉, 제네릭이란 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법입니다. 

제네릭을 하지 않을 경우에는 안정성이 많이 떨어지고, 타입 변환과 검사에 들어가는 시간과 노력을 줄일 수 있습니다. 

프로그래밍에 실수가 있을 시 알기가 쉬운 코드가 품질이 좋은 코드이기 때문에 제네릭의 존재를 인지하고 있는 것이 좋습니다. 


아래는 제네릭을 사용하지 않았을 시의 코드입니다. 

 

class Apple {

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

}

class Orange {

   public String toString() { return "I am an orange."; }

}

// 다음 상자는 사과도 오렌지도 담을 수 있다.

class Box {   // 무엇이든 저장하고 꺼낼 수 있는 상자

   private Object ob;

  

   public void set(Object o) { ob = o; }  // 그 어떤 참조타입도 참조할 수 있는 오브젝트 타입 

   public Object get() {return ob; }

}

 

public static void main(String[] args) {

   Box aBox = new Box();    // 상자 생성

   Box oBox = new Box();    // 상자 생성

 

   aBox.set(new Apple());    // 상자에 사과를 담는다.

   oBox.set(new Orange());   // 상자에 오렌지를 담는다.

 

   Apple ap = (Apple)aBox.get();    // 상자에서 사과를 꺼낸다. 애플로의 캐스팅(형변환)을 해주어야만 오류가 발생하지 않는다. 변환한 형에 맞는 참조를 한다. Apple ap

   Orange og = (Orange)oBox.get();   // 상자에서 오렌지를 꺼낸다.

   System.out.println(ap);

   System.out.println(og);

}

 


이렇듯 제네릭을 수반하지 않을 때에는 어쩔 수 없이 형 변환의 과정이 수반되고, 

컴파일러의 오류 발견 가능성이 낮아집니다. (Apple 박스에 Orange 를 담아도 오류가 발생하지 않음.) 

 

즉, 이렇게 코드를 작성할 경우 여러가지의 문제점이 발생합니다. 

논리로는 맞는데 결과적으로 원하는 결과가 나오지 않을 가능성이 있습니다. 

 

   // 아래 두 문장에서는 사과와 오렌지가 아닌 '문자열'을 담았다. 실체가 스트링. 

   aBox.set("Apple");

   oBox.set("Orange");

 

   // 상자에 과일이 담기지 않았는데 과일을 꺼내려 한다.

   Apple ap = (Apple)aBox.get();

   Orange og = (Orange)oBox.get();

   System.out.println(ap);

   System.out.println(og);

}

 

 


● 그럼 이제 그것을 안전하게 사용할 수 있도록

이름표를 붙여 내 클래스에 확실한 매개변수의 인자를 붙여줄게요. ♬

아래는 제네릭의 사용 예입니다. 

인스턴스 생성시에 결정해도 되는 자료형의 정보를 T로 대체합니다. 

T가 쓰여진 곳은 추후에 타입이 정해진다는 것입니다.

여기에서 T는 T가 아니라 어떤 문자로 쓰여도 상관 없으며, 여러 개의 타입 변수는 쉼표(,)로 구분하여 명시할 수 있습니다. 

 

< 제네릭 사용 전 >

class Box {
   private Object ob;    // 어떤 참조형이 와도 받을 수 있도록 오브젝트 타입으로 받은 것 

   public void set(Object o) {
      ob = o;
   }

   public Object get() {
      return ob;
   }
}

<  제네릭 사용 후 >

class Box<T> {   // <T> 제네릭. 타입에 대한 매개변수. 박스는 제네릭 타입의 매개변수다. 
   private T ob;  //  오브젝트를 받는 곳에 제네릭 타입의 T를 작성합니다. 

   public void set(
T o) {

      ob = o;
   }

   public 
T get() {

      return ob;
   }
}

이후에 원하는 것으로 정의할 예정이라는 뜻입니다. 

어떤 것이든 받을 수 있는 오브젝트의 매개변수 자리에 제네릭의 매개변수 <T>를 붙여줌으로써

이후에 원하는 타입을 지정해서 받을 수 있도록 할 수 있습니다. 

class Box<T> 를 조금 더 상세하게 뜯어볼게요. 

 

• 타입 매개변수, 즉 지역변수  (Type Parameter)   Box<T>에서 T

• 타입 인자 (Type Argument)   Box<Apple>에서 Apple

• 매개변수화 타입 (Parameterized Type)   Box<Apple>

 

그럼 아까 사과 박스에 오렌지가 들어가거나 문자열인 '사과' 가 들어가지 않도록 하기 위해서는 

박스의 이름에 이것은 사과 전용의 박스라는 의미의 '사과'형 타입을 넣어주면 됩니다. 

 

Box<Apple> aBox = new Box<Apple>();

  → TApple로 결정하여 인스턴스 생성

  → 따라서 Apple 또는 Apple을 상속하는 하위 클래스의 인스턴스 저장 가능

 

Box<Orange> oBox = new Box<Orange>();

  → TOrange로 결정하여 인스턴스 생성

  → 따라서 Orange 또는 Orange를 상속하는 하위 클래스의 인스턴스 저장 가능

 

박스라는 제네릭 클래스에다 애플을 넣은 제네릭 타입 -> 이것 또한 하나의 타입. 애플 형이 되는 것입니다. 

애플 형 참조변수 애플을 생성합니다.

 


아래는 위에 적었던 일반 오렌지와 애플 박스 코드를 제네릭을 사용한 결과입니다.

 

class Box<T> {  // <> 타입의 매개변수를 T로 선언. 박스를 만듭니다. 이제 이 박스에 이름표를 붙여줄 거예요. 

   private T ob;

   public void set(T o) {  // 인자에 대한 셋팅 준비 

      ob = o;

   }

   public T get() {  // 인자에 대한 반환 준비 

      return ob;

   }

}

 

public static void main(String[] args) {

   Box<Apple> aBox = new Box<Apple>();    // TApple로 결정 Apple 형 사과박스가 만들어졌어요. 

   Box<Orange> oBox = new Box<Orange>();    // TOrange로 결정

 

   aBox.set(new Apple());   // 사과를 상자에 담는다.

   oBox.set(new Orange());   // 오렌지를 상자에 담는다.

  

   Apple ap = aBox.get();   // 사과를 꺼내는데 형 변환 하지 않는다왜냐하면 사과형 박스에서 꺼낸 것이기 때문에 

하위의 인스턴스는 무조건 사과가 나오기 때문입니다.

 

   Orange og = oBox.get();   // 오렌지를 꺼내는데 형 변환 하지 않는다. 왜냐하면 오렌지형 박스에서 꺼낸 것이기 때문에 

하위의 인스턴스는 무조건 사과가 나오기 때문입니다.

 

   System.out.println(ap);

   System.out.println(og);

}


 

제네릭에 대한 사용 예시를 읽으면서 확인해볼게요. 

 

package day18;

class Pencil{}
class Eraser{}

/*
 * 제네릭은 사용 시에 타입을 정해서 쓰는 일반화 방법.
 * 일반적으로 타입 매개변수는 대문자 한 글자로 쓴다. 
 */
class Drawer<T>{	//<> 타입 매개변수는 마음대로 이름을 지어줄 수 있지만 T가 일반적

	private T ob;
	
	void set(T o) {this.ob = o;} //  객체를 셋팅받도록 합니다. 
	T get() {return this.ob;}	// 객체를 반환하도록 합니다. 
}

// 제네릭 변수 받기
public class Generic {
	public static void main(String[] args) {
	
	Drawer<Pencil> pd = new Drawer<Pencil>(); //펜슬(타입 인자) pencil을 사용하는 변수가 됩니다. 
	pd.set(new Pencil());  // 펜슬 변수를 셋팅받도록 설정이 되어 있는 상태
	Pencil p = pd.get(); //펜슬 변수를 참조해서 인스턴스를 생성해도 오류 x
	}

}

● 다중의 매개변수 기반 제네릭 클래스를 정의할 때에는 쉼표 로 구분하여 기재합니다. 

 

class DBox<L, R> {

   private L left;   // 왼쪽 수납 공간

   private R right;   // 오른쪽 수납 공간

   public void set(L o, R r) {

      left = o;

      right = r;

   }

   @Override

   public String toString() {

      return left + " & " + right;

   }

}

 

public static void main(String[] args) {

   DBox<String, Integer> box = new DBox<String, Integer>();

   box.set("Apple", 25);

   System.out.println(box);

}

 

● 제네릭 기반의 개체 생성 시에 사용하는 것은 다이아몬드 기호 <> 입니다. 

Box<Apple> aBox = new Box<Apple>();

위 문장을 다음과 같이 쓸 수 있습니다.

  Box<Apple> aBox = new Box<>();

컴파일러가 자동으로 유추하여 제네릭 기반의 개체로 인식합니다.

 

반응형