Inor

[Java] Generic (제네릭) 본문

Computer Engineering/Java

[Java] Generic (제네릭)

Inor 2017. 11. 26. 20:02

- Generic


 Generic은 클래스 내부에서 사용할 변수의 형태를 외부에서 지정하는 방법입니다. 주로 멤버 변수의 타입이나 메소드 반환 타입을 외부에서 지정해줄때 사용합니다. Generic을 사용 할때 개발자가 얻는 장점이 있습니다. 이 글에서는 장점을 중심으로 Generic의 기초적인 사용법을 알아보도록 하겠습니다.




- 사용법


class Box<T> {

	T item;
	
	public Box(T item) {
		this.item = item;
	}
	
}

public class mGeneric {
	
	public static void main(String[] args){
		Box<String> mBox = new Box<String>(new String("선물"));
	}

}


 기본 적인 사용법은 위의 코드와 같습니다. Box 클래스를 정의했고 main 메소드에서 Box 클래스를 생성하고 있습니다. Box 클래스에 보면 <T>라는 키워드와 멤버 변수의 타입으로 T가 선언되어있습니다. 여기서 <T>는 클래스를 객체로 만들때 지정할 데이터 타입을 의미합니다. main 메소드에서는 <String>으로 mBox 객체를 생성했고 mBox의 T는 String이 됩니다. mBox 객체에서 T는 String으로만 사용 가능합니다. Generic 타입은 생성 시점에서 결정됩니다. 즉, Generic으로 타입을 결정하면 타입 안전성이 보장되는 장점과 불필요한 캐스팅을 막아주는 장점이 생깁니다.




- 불필요한 캐스팅


class Box {

	Object item;
	
	public Box(Object item) {
		this.item = item;
	}
	
	public void setItem(Object item){
		this.item = item;
	}
	
	public Object getItme(){
		return item;
	}
}

public class mGeneric {
	
	public static void main(String[] args){
		//A 개발자가 개발
		Box mBox = new Box(new String("선물"));
		String mItemName = (String)mBox.getItme();
		System.out.println(mItemName);
	}
}


 위의 코드에서는 Box 클래스의 멤버 변수인 item을 제네릭이 아닌 Object 타입의 변수로 선언하였습니다. Object 타입은 자바에서 모든 객체가 상속받는 클래스이기 때문에 어떠한 클래스도 item 변수에 할당할 수 있습니다.(업캐스팅) main 메소드에서 mBox 객체를 생성할때 생성자에 String 객체를 넣어줘도 컴파일 에러를 발생시키지 않습니다. 업 캐스팅의 경우에는 별도의 캐스팅이 필요 없기 때문입니다.

 다운 캐스팅의 경우에는 명시적으로 캐스팅을 해줘야합니다. main 메소드의 String mItemName = (String)mBox.getItme(); 에서 다운 캐스팅하는 부분을 보면 확인할 수 있습니다. getItem의 반환 타입이 Object이기 때문입니다. Object는 모든 클래스의 부모 객체이기 때문에 명시적 다운 캐스팅이 필요합니다. Object를 사용하지 않고 제네릭을 사용한다면 명시적 다운 캐스팅이 필요없게 됩니다.


class Box<T> {

	T item;
	
	public Box(T item) {
		this.item = item;
	}
	
	public void setItem(T item){
		this.item = item;
	}
	
	public T getItme(){
		return item;
	}
}

public class mGeneric {
	
	public static void main(String[] args){
		//A 개발자가 개발
		Box<String> mBox = new Box<String>(new String("선물"));
		String mItemName = mBox.getItme();
		System.out.println(mItemName);
	}

}


 위의 코드에서 Box 클래스을 다시 제네릭 클래스로 변경했습니다. 주의 깊게 봐야할 부분은 Box 클래스 멤버 변수인 item의 타입과 String mItemName = mBox.getItme(); 부분입니다. getItem()을 호출하는 부분에서 캐스팅 연산이 없어도 컴파일 에러를 발생시키지 않습니다. mBox의 T는 생성 시점부터 String으로 고정이 됩니다. 이는 객체를 생성할때 제네릭 T 타입을 String으로 선언해줬기 때문입니다. 이를 통해서 불필요한 형변환이 발생하지 않고 타입 안전성이 보장됩니다. 타입 안전성이 왜 보장되는지 알아보겠습니다.




- 타입 안전성


class Box<T> {

	T item;
	
	public Box(T item) {
		this.item = item;
	}
	
	public void setItem(T item){
		this.item = item;
	}
	
	public T getItme(){
		return item;
	}
}

public class mGeneric {
	
	public static void main(String[] args){
		//A 개발자가 개발
		Box<String> mBox = new Box<String>(new String("선물"));
		String mItemName = mBox.getItme();
		System.out.println(mItemName);
		//B 개발자가 협업 시작
		mBox.setItem(1);
	}

}


 설명을 위해 상황을 설정하겠습니다. 상황은 A 개발자와 B 개발자가 협업하는 상황이고 A와 B로 칭하겠습니다. A가 먼저 개발을 시작했습니다. Box 클래스를 제네릭을 사용해서 정의하고 String형 item을 갖는 객체인 mBox를 선언해서 사용했습니다. 그리고 B에게  코드를 인수인계 했습니다. 하지만 B는 제대로 인수인계 내용을 확인하지 않고 코드를 사용했습니다. B는 mBox 객체를 사용했고 item을 재설정하는 코드를 작성했습니다. 이 과정에서 컴파일 에러가 발생했고 B는 곧바로 문제 원인을 찾았습니다.

 B가 문제를 발견할 수 있었던 이유는 코드 작성 중에 발생한 컴파일 에러 때문이었습니다. 컴파일 에러가 발생한 이유는 Box 클래스가 제네릭 클래스이기 때문입니다. 초기에 A가 Box 클래스의 객체인 mBox를 생성할때 제네릭 타입을 String으로 고정했습니다. 컴파일러는 mBox의 item 타입인 T가 String으로 알고 있었는데, mBox.setItem(1);에서 B가 item에 int 타입을 할당하려 했습니다. 컴파일러는 이상한 점을 발견했고 바로 컴파일 에러를 발생시켰습니다. 제네릭은 개발자가 의도하지 않은 타입의 변환으로 발생하는 에러를 막았고 타입 안전성을 보장해줬습니다.

Comments