java&spring

[Java] 제네릭(generic) 정리

sungjine 2017. 2. 22. 21:42
반응형

제네릭이란 클래스 내부에서 사용할 데이터 타입을 외부에서 정할 수 있게 하는 기법으로 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 고칠 수 있게 해준다. 주로 Collections에서 많이 쓰이며 Java 5 버전에 추가되었다.

 

우선 간단하게 사용법을 보자

 

List<String> list = new ArrayList<String>();

list.add("S");

list.add(1);  // 에러

String s = list.get(0);

String i = list.get(1);  // 에러 나지 않음

 

위 코드에서 <String> <<--- 이게 제네릭이다. List에 String Type만 넣을 수 있다고 선언해 놓은 것이다. 그래서 String 변수에 형변환을 하지 않아도 바로 넣을 수 있다.

 

만약 위에 있는 코드를 아래 코드와 같이 제네릭을 사용하지 않고 데이터를 저장해보자

 

List list = new ArrayList();

list.add("S");

list.add(1);  // 에러 나지 않음

String s = list.get(0); // 에러

String i = (String)list.get(1);  // 에러 나지 않음

 

데이터를 저장할 때 Object 클래스로 저장된다. 그래서 불러올 때 형변환을 하지 않는다면 에러가 난다.

 

물론 제네릭을 사용하지 않으면 넣고 싶은 Type을 마음껏 넣을 수 있는 장점이 있지만 사용할 때마다 list에 넣은 타입의 수 만큼 타입을 확인해야 하는 불편함이 있다.

 

그리고 제네릭을 선언할 때는 클래스로 선언해야 하므로 기본형(primitive) 타입으로 선언할 수 없고 Wrapper Class를 사용해야 한다.

 

List<int> list1 = new ArrayList<int>();  // 에러

List<Integer> list2 = new ArrayList<Integer>(); // 에러 나지 않음

 

이제 클래스에 제네릭을 사용해보자

public class GenericTest <T> {

     T value;

     GenericTest(T value) {
         this.value = value;
     }

     public static void main(String[] args) {
         GenericTest<String> g1 = new GenericTest<String>("!");
         GenericTest<Integer> g2 = new GenericTest<Integer>(1);
         System.out.println(g1.value);
         System.out.println(g2.value);
     }
}

 

위 코드는 new GenericTest<String> 과 같이 String을 제네릭으로 지정하면 public class GenericTest <T> 에서 T에 String이 들어가고 T value; 는 String value; 와 같게 되는 것이다. 출력 값은 ! 와 1 이다.

 

인터페이스도 클래스와 마찬가지로 제네릭을 사용할 수 있다.

 

public interface GenericTest <T> {
     T get();
}

 

이번에는 제네릭을 두 개 써보자

 

public class GenericTest <T, S> {

	T t;
	S s;

	GenericTest(T t, S s) {
		this.t = t;
		this.s = s;
	}

	public static void main(String[] args) {
		GenericTest<String, Integer> g1 = new GenericTest<String, Integer>("!", 1);
		System.out.println(g1.t);
		System.out.println(g1.s);
	}
}

 

위 코드에는 제네릭을 두 개만 사용했지만 몇 개를 작성해도 상관없다. 출력 값은 ! 와 1 이다.

 

메소드에 사용하고자 하면 아래와 같이 사용하면 된다.

주의할 점은 메소드에 제네릭을 쓰면 해당 메소드 안에서만 사용할 수 있다는 것이다.

 

public class GenericTest {

	T t; // 에러

	<T> T setT(T t) {
		return t;
	}

	public static void main(String[] args) {
		GenericTest g1 = new GenericTest();
		GenericTest g2 = new GenericTest();
		System.out.println(g1.setT(1));
		System.out.println(g2.setT("1123"));

	}
}

 

만약 제네릭 타입을 제한하고자 하면 상속관계를 통해 제한할 수 있다.

 

public class GenericTest {

	<T extends Number> T setT(T t){
		return t;
	}

	public static void main(String[] args){
		GenericTest g1 = new GenericTest();
		GenericTest g2 = new GenericTest();
		System.out.println(g1.setT(1));
		System.out.println(g2.setT("1123")); // 에러
	}
}

 

위 코드에서 <T extends Number>를 보면 되는데 int형, float형 등등 Number 클래스이거나 Number 클래스를 상속받는 자식 클래스만 제네릭 타입으로 사용할 수 있게 한다. 주의할 점은 인터페이스도 extends를 사용한다는 점이다.

 

아래 코드는 제네릭을 상속받을 때이다.

 

class test1 <T> {
	T t;
}

public class GenericTest <T, S> extends test1<T> {
	S s;
    
	public static void main(String[] args) {
		GenericTest<Integer, Float> g1 = new GenericTest<Integer, Float>();
		g1.s = 1.1F;
		g1.t = 1;
	}
}

 

부모 클래스에서 <T>가 있으므로 자식 클래스에서 <T>를 포함해 줘야 한다.

 

제네릭은 T나 S대신 어떤 문자를 사용해도 상관없다(ex : <a>, <ge>). 하지만 묵시적 약속이 있는데 아래와 같다.

 

E - Element

K - Key

N - Number

T - Type

V - Value

S - Second

U - Third

V - Fourth

 

** 자바 7 버전부터는 GenericTest<String, Integer> g1 = new GenericTest<>("!", 1); 와 같이 객체생성 코드에서는 <>로 생략할 수 있다.

반응형