Java

[Java]Generics

꽃달린감나무 2022. 3. 1. 16:52
728x90
  • Generics 클래스는 무엇일까? 왜 필요할까?
    • 볼펜, 연필 클래스가 있고 이 필기구를 담는 볼펜 박스, 연필 박스 클래스를 정의해봅시다.
class Pen{
	String Content;
	public Pen(String Content) {this.Content = Content;}
	public void showContent() {System.out.println(Content);}
}

class PenBox{
	
	Pen pen;
	public void store(Pen pen) {this.pen = pen;}
	public Pen pullout() {return pen; }
}

class pencil{...} //위 코드와 유사함
class pencilBox{...}//위 코드와 유사함

이제 각 연필과 볼펜을 각각의 박스에 담을 수 있게 되었습니다!!

그럼 Pen과 Pencil 말고도 다른 필기구를 추가할 때마다 박스 클래스를 만들어줘야 할까요? 그러면 너무 번거로워집니다. 하나의 Object클래스를 만들어 볼펜, 펜뿐만 아니라 다양한 필기구를 담을 수 있는 박스 클래스를 만들어 볼까요??

class Box{
	Object obj;
	public void store(Object obj) {this.obj =obj;}
	public Object pullout() {return obj;}
}

Object 클래스로 만들었으니 모든 클래스는 Object 클래스를 상속하므로 여러가지 필기구를 넣을 수 있게 되었습니다.

단, 문제가 있습니다. 필기구 클래스 뿐만 아니라, 다른 문자열 클래스, 기본 데이터 형, 다른 누군가가 만든 자동차 클래스도 들어갈 수 있게 되었습니다. 저는 단지 필기구를 담는 클래스를 정의하고 싶을 뿐인데 말이죠. 하지만 문자열 클래스를 넣는다고 해도 문법적 오류는 발생하지 않습니다.

public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//개발자가 원하는 코드
		Box boxA = new Box();
		boxA.store(new Pen("4B"));
		Pen penA = (Pen)boxA.pullout();
		penA.showContent();
		
		//잘못된 사용, 문법적오류 발생 x
		Box boxB = new Box();
		boxB.store("볼펜");
		Pen penB = (Pen)boxB.pullout();
		penB.showContent();
	
	}

컴파일은 정상적으로 실행되지만, 개발자가 원하는 의도가 아니었기에 문제점을 찾기 위해 모든 코드를 일일이 분석해야 할 수밖에 없습니다. 물론 지금의 코드는 단순한 코드라 금방 문제점을 찾을 수는 있지만, 코드의 길이가 길어지면 어마어마한 시간의 낭비가 아닐 수 없습니다. 이렇게 되면, 코드의 안정성은 현저히 떨어지게 되죠. 하지만 그렇다고 해서 일일이 각 필기구를 담는 박스들을 만들기는 힘들죠. 이러한 문제점을 해결해 주는 것이 바로 제네릭 클래스 입니다.

 

  • 제너릭 클래스의 사용법
class Box<T>{
	
	T obj;
	public void store(T obj) {this.obj =obj;}
	public T pullout() {return obj;}
	
}

//두 개의 자료형 매개변수도 사용할 수 있습니다.
class Box<T, U>{
	
	T obj;
	public void store(T obj) {this.obj =obj;}
	public T pullout() {return obj;}
	
}

 

<T>는 원하는 자료형으로 바꿔 줄 수 있습니다. 예를 들어 연필과 볼펜을 넣고자 한다면,

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Box<Pen> boxA = new Box<Pen>();
		boxA.store(new Pen("0.5MM"));
		Pen penA = boxA.pullout();
		penA.showContent();
		
		Box<Pencil> boxB = new Box<Pencil>();
		boxB.store(new Pencil("4B"));
		Pencil penB =boxB.pullout();
		penB.showContent();
	
	}

단지, <T> 넣고자 하는 자료형을 써주기만 하면 됩니다. 이렇게 하면, 코드의 안정성도 올라가고, Box클래스를 여러 개 만들 이유도 없어집니다. (* T, U는 제네릭 표현 문자를 의미합니다.)

 

  • 제네릭 클래스와 Wrapper 클래스

Wrapper 클래스는 기본 자료형의 인스턴스입니다. Interger, Double, Float, Short, Byte 등이 있습니다.

제너릭 클래스에는 기본자료형은 사용하지 못하고 Wrapper 클래스만 사용이 가능합니다.

public Box<Integer>{..} // 올바른 사용법
public Box<int>{...} // 사용불가

 

  • 제너릭 메서드의 정의와 호출
public <T> void showBox(T item){..}
public <T, U> void showBox(T item){..} //자료형을 두개 넘겨줄 수도 있다.

 

제너릭 클래스의 메소드 정의는 위와 같이 <T> 사용해서 정의해주면 됩니다. 호출의 경우에는

BoxA.<Pen>showBox(Pen p); //호출방법 1
BoxA.showBox(Pen p); //호출방법 2(일반적인 호출방법)

이런 식으로 호출해주면 됩니다. (단, 호출 방법 1 보다는 호출방법 2가 일반적인 방법입니다.)

<Pen>이 지워진 이유는 컴파일러가 매개변수로 전달되는 참조 변수를 근거로 자료형을 판단할 수 있기 때문입니다.

 

  •   매개변수 자료형 제한

제네릭 클래스는 다음과 같은 형식으로 자료형을 제한할 수 있습니다. 

 

public static <T extends 인터페이스, 클래스> void showInstamceAncestor(T param)

 

 

<T extends 인터페이스, 클래스> 에서 인터페이스면, 해당 인터페이스를 구현한 클래스만, 클래스이면, 해당 클래스를 상속하는 클래스만이 해당 메소드의 매개변수로 전달될 수 있습니다.

 

* 예시 

https://github.com/wpdbs1229/Java/edit/main/Generic/BoundedTypeParam2

 

GitHub: Where the world builds software

GitHub is where over 73 million developers shape the future of software, together. Contribute to the open source community, manage your Git repositories, review code like a pro, track bugs and feat...

github.com

  • 제네릭 변수의 상속관계
public void write(Pen p){...}

write 메소드의 매개변수로 올 수 있는 자료형은 Pen의 인스턴스 또는 Pen을 상속하는 인스턴스의 참조 값이 전달 될 수 있습니다. 그럼 제네릭의 경우는 어떨까요?

public void write(Box<Pen> p){...}

아쉽게도 제너릭에서는 Box<Pen>의 인스턴스만 매개변수로 전달 될 수 있습니다. 그 이유는 생각보다 간단합니다. Pen을 상속받는 클래스 Sharp 클래스가 존재한다고 해서, Box<Pen> 클래스오 Box<Sharp>클래스가 상속관계에 놓인 것이 아닙니다. 

그럼 Pen을 상속받는 클래스인 sharp도, Pen도 매개변수로 전달하고 싶은면 어떻게 해야할까요??

그 해답은 와일드 카드에 있습니다. 와일드 카드란, 이름 또는 문자열에 제한을 가하지 않음을 명시하는 용도로 사용되는 특별한 기호인 "?" 말합니다. 사용법은 다음과 같습니다.

Box< ? extends Pen> boxA = new Box<Sharp>();
Box< ? super Sharp> boxA = new Box<Pen>();

 < ? extends Pen> 같은 경우는 Pen을 상속하는 모든 클래스면 무엇이든 올 수 있음을 명시하는 것이고,

< ? super Sharp> 는 Sharp가 상속하는 모든 클래스를 뜻합니다. 즉, super는 Sharp의 조상클래스, extends는 Pen의 자식 클래스라고 생각하시면 됩니다!

728x90