본문 바로가기

JAVA/개념정리

자바/Java ArrayList<T> 제네릭스(Generics)란?

자바/Java ArrayList<T> 제네릭스(Generics)란?

자바로 코딩을 하면서 ArrayList 다음 에 <T>,<String>,<Integer>,<Class명> 라고 쓰여진 형식을 많이 접해 보셨을 겁니다. 저도 처음 자바 공부를 시작하면서 저게 뭐지? 라는 의문을 상당히 길게 품었었던 적이 있습니다. 물론 지금도 없지 않아 있을까요? ㅎㅎ

그럼 과연 제네릭스란 무엇일까요? 제네릭스란 쉽게말해서 ArrayList(컬렉션 클래스에서 사용가능하지만 쉬운 설명을 위해 대표적인 컬렉션 클래스인 ArrayList를 가지고 설명 하겠습니다.) 가 다룰 객체를 미리 명시해줌으로써 형변환을 하지 않고 사용하는 것입니다. 즉 ArrlayList가 사용할 객체의 타입이란 이야기 입니다. 예를 들어 우리가 현실에서 갤럭시S2블랙 를 공동 구매 한다고하면 사람들이 100명 200명 300명 오로지 갤럭시S2만 구입을 하려고 돈을 지불하는데요.

그럼 판매자 입장에서도 갤럭시S2블랙만 취급 하기때문에 구매한 사람들의 돈과 수량만 체크해서 물건을 보내주기만 하면 됩니다. 하지만 판매자가 여러가지 핸드폰을 팔경우 사람들이 어떤 핸드폰을 주문했는지 일일이 체크하고 돈과 수량을 또 다시 체크해야하는 번거로움을 겪을 수 있지요. 또한 주문한 물건이 다른 물건으로 바껴서 배송 될 수 도 있습니다.





자, 그럼 이제 좀 이해가 되시나요? 그렇습니다. 제네릭스를 사용하는 이유는 타입의 안정성을 제공하고(갤럭시S2블랙 공동구매시 주문한 물건이 바뀌는 일이 없음) 타입체크와 형변환을 생략(판매자가 갤럭시S2블랙 이라는 물건만 취급하기때문에 다른 핸드폰은 생각안해도됨) 할 수 있으므로 코드가 간결해 집니다.
 

<String> 제네릭스는 String 객체만, <Integer>제네릭스는 Integer클래스만 사용 할 수 있습니다. <String> 제네릭스에 Integer 자료를 넣는다면 컴파일 에러가 발생합니다.!! 두둥!! 그점 유의하시구요. 아래는 실제 <제네릭스>를 사용한 코드 예제 입니다.   


package arabiannight.tistroy.com;

import java.util.ArrayList;

public class TestGenericsClass {
	
	public static void main(String[] args) {
		// <String> 제네릭스를 사용한 경우 
		ArrayList<String> mStringList = new ArrayList<String>();
		mStringList.add("안녕하세요");
		mStringList.add("3");
		
		for(String s : mStringList){
			System.out.println(s);
		}
		
		// <Integer> 제네릭스를 사용한 경우
		ArrayList<Integer> mIntegerList = new ArrayList<Integer>();
		mIntegerList.add(1);
		mIntegerList.add(2);
		
		for(Integer i : mIntegerList){
			System.out.println(i);
		}
		
		// <Class명> 제네릭스를 사용한 경우
		ArrayList<Tv> mTvList = new ArrayList<Tv>();
		mTvList.add(new Tv());
		mTvList.add(new Tv("New Tv"));
		
		for(Tv t : mTvList){
			System.out.println(t);
		}

        // < 제네릭스를 사용하지 않은 경우 >
		// 모든타입을 "add(Object)" Object형으로 List에 Add를 
        // 시켜 주므로, List에서 자료를 꺼내올때는 각각에 맞는 형병환을
		// 꼭 해주어야 하기 때문에 번거 롭다.
		ArrayList mOriginalList = new ArrayList();
		mOriginalList.add(1);
		mOriginalList.add("string");
		mOriginalList.add(new Tv("original"));

		int a = (Integer) mOriginalList.get(0); 
		String b = (String) mOriginalList.get(1); 
		Tv c = (Tv) mOriginalList.get(2); 
	}
}

// Tv Class
class Tv {
	private String caption;
	
	public Tv(){
		this("Tv Class 입니다.");
	}
	
	public Tv(String caption){
		this.caption = caption;
	}
	
	public String toString () {
		return caption;
	}
}



위의 예제처럼 상황에 맞는 <제네릭스> 를 선택해서 사용하시면 됩니다. 그런데 한가지 궁금한점이 드는데요.ㅎ 위 <제네릭스>에서는 string 대신 String , int 대신 Integer를 사용 했을까요? 그것은 <제네릭스>를 선언할 수 있는 타입이 객체 타입이여서 입니다. int나 string는 기본자료형이기 때문에 <제네릭스>타입 자료에는 들어갈수가 없는 것입니다.

그래서 int 나 string을 가지고 객체화 시킨 wrapper클래스를 사용해서 사용 합니다. wrapper클래스란 기본형변수들에 기능을 좀더 추가해서 객체화 시킨 클래스들 인데요. 여기서는 자세하게는 다루지 않겠습니다. 그냥 기본형변수들의 객체라고 생각 하시면 됩니다.


                                             출처 : 
http://www.babobox.co.kr/
Wrapper 클래스는 자료형의 기능을 포함한 객체 클래스 이다.



<제네릭스>도 다형성을 사용 할 수 있을까요? 네 맞습니다. <제네릭스>도 다형성을 사용 할 수 있습니다. 다형성이란 '부모의 참조변수로 자식 객체를 참조할수 있는 것' 쯤으로 생각하시면 될텐데요 자바에서 다형성이란 아주 중요한 개념이고 간단히 설명하기는 어렵기 때문에 wrapper클래스와 마찬가지로 자세하게 다루지는 않겠습니다. 단, <제네릭스>에서도 다형성을 사용한다는 점을 중점적으로 다뤄 보겠습니다.
package arabiannight.tistroy.com;

import java.util.ArrayList;

public class TestGenericsExtends {

	public static void main(String[] args) {
		
		// product클래스의 자손객체들을 저장할 수 있는 ArrayList를 생성
		ArrayList<Product> list = new ArrayList<Product>();
		list.add(new Product());
		list.add(new Vcr());
		list.add(new Audio());
		
		Product p = list.get(0);
		Vcr v = (Vcr)list.get(1);
	}
}

class Product{};
class Vcr extends Product{};
class Audio extends Product{};



ArrayList가 Product타입의 객체를 저장하도록 지정하면, 이들의 자손인 Vcr과 Audio타입의 객체도 저장할 수 있습니다. 다만 꺼내올 때 원래의 타입으로 형변환 해야 합니다.!!
// 자손의 객체를 꺼내 올때는 형변환이 필요하다.
Product p = list.get(0);
Vcr v = (Vcr)list.get(1);

하지만 매개변수 타입의 ArrayList<Product>로 선언된 경우, 이 메서드의 매개변수로 ArrayList<Product>타입의 객체만 사용할 수 있습니다. 그렇지 않으면 컴파일 에러가 발생 합니다.

public class TestGenericsEx01 {

	public static void main(String[] args) {
		ArrayList<Product> pArrayList = new ArrayList<Product>();
		ArrayList<Vcr> vArrayList = new ArrayList<Vcr>();
		
		printAll(pArrayList);
		printAll(vArrayList); // 컴파일 에러!!!!!
	}
	
	public static void printAll(ArrayList list){
		for(Product p : list){
			System.out.println(p);
		}
	}
}



이런 그럼 매개변수의 다형성을 이용할 수 없는 것일까요? 아닙니다. 바로 와일드 카드 '?' 를 사용 하면 됩니다. 보통 제네릭에서는 단 하나의 타입을 지정하지만 와일드 카드는 하나 이상의 타입을 지정 하는것 을 가능 하게 해 줍니다. 그럼 와일드 카드는 어떻게 사용 하는 것일까요?

와일드 카드의 사용 모습 입니다. (ArrayList<? extends Product> list)
(만일 Product가 클래스가 아닌 인터페이스라 할지라도 키워드로 'implements'를 사용하지 않고 클래스와 동일하게 'extends'를 사용한다는 것에 주의 하자.) 

// Product 또는 그 자손들이 담긴 ArrayList를 매개변수로 받는 메서드
	public static void printAll(ArrayList<? extends Product> list){
		for(Product p : list){
			System.out.println(p);
		}
	}


와일드 카드는 (ArrayList<? extends Product> list) 말고도 또다른 형식으로도 쓰입니다.
바로 아래의 메서드 형식 입니다.

public static <T extends Product> void printAll(ArrayList<T> list) 
// 와일드 카드의 또다른 형식	
public static <T extends Product> void printAll(ArrayList<T> list){
		for(Product p : list){
			System.out.println(p);
		}
	}

와일드 카드를 쓰실때는 Product를 상속받은 클래스(Vcr , Audio)를 <제네릭>으로 사용하는 ArrayList를 생성한 후 메서드에 넣어 주시면 됩니다.
<ex> 
ArrayList<Vcr> vcrList = new ArrayList<Vcr>();
vcrList.add(new Vcr());
printAll(vcrList); 




이렇게 해서 Java <제네릭스>에 대해서 공부해 봤는데요. 어떠신가요? 이제 좀 이해가 되시나요?ㅎ 저도 많은 공부가 되었네요 ㅎㅎ 또한 공부를 하면서 포스팅을 하고 있는거라 생각보다 오랜 시간이 걸렸습니다 ㅎ 아무튼 이번 <제네릭스>포스팅을 보시고 Java에 한걸음 더 다가가시는 계기가 됬으면 좋겠습니다. 이것으로 <제네릭스> 포스팅을 마치겠습니다.


ps. 제네릭 관련한 유용한 내용이 있을경우 틈틈이 추가 하겠습니다.



Test 파일 입니다.

TesGenerics.zip





출처 : 자바의정석
자바의정석 내용을 참고하여 작성 하였습니다.