본문 바로가기

Backend/Java

[JAVA] 제네릭(Generic)

 

 


제네릭(Generic) 알아보기


 

 

1. 제네릭의 등장


보통의 자료구조는 여러 타입의 자료를 혼용하여 저장할 수 있다. (물론 그렇게 혼용해서 저장하는 경우는 거의 없지만..) 

 

무슨 소리인가 함음...

앞서 다뤘던 ArrayList 를 잠시 소환해보자.

 

ArrayList a = new ArrayList();

// 다양한 자료형 저장 가능
a.add(10);	// Integer
a.add(1.1);	// Double
a.add("Hello");	// String

 

대표적으로 ArrayList의 경우 다양한 자료형을 저장할 수 있다.

 

이렇게 저장된 데이터들을 호출할 때에는

Integer i0 = (Integer)a.get(0);
// Integer i1 = (Integer)a.get(1); // Double형 객체를 Integer형 객체로 변환X

 

값을 강제 형변환(캐스팅 연산)을 통해 자료형을 맞춰주어야 하는데, 문제는 일치하지 않는 타입으로 캐스팅을 시도할 시 런타임 에러가 발생하여 프로그램이 강제 종료된다.

 

그렇다면 사용자가 ArrayList에 저장한 값들을 추출하기 위해서는 일일이 다 기억하고 있어야 하는 불편함이 발생한다.


이렇듯 하나의 자료구조에 (대표적으로 ArrayList) 여러 자료형을 섞어 저장할 수 있지만 위와 같은 애로 사항이 심심치 않게 발생하여 아예 자료형을 하나로 통일해서 저장하자! 고 만든 방식이 제네릭(Generic)이다. 아예 규칙을 만들어서 지정한 자료형 외에 다른 자료형이 저장되지 못하도록 하는 것이다.

 

또한 컬렉션 내부의 메소드들의 매개변수는 대부분 Object를 기반으로 하는데, Object 데이터를 특정 자료형으로 인출( .get() )하기 위해서는 강제 캐스팅 변환이 필요하다. 이러한 번거로운 작업을 개선하기 위해 JDK 1.5버전 이후로 지원하기 시작했다.

 

 

 

2. 제네릭의 사용법


ArrayList<Integer> list = new ArrayList<Integer>();

list.add(100);
// list.add("string"); // 에러

 

리스트 자료형을 선언할 때 위와 같이 대입 연산자를 기준으로 앞뒤 자료형 뒤쪽에 <꺽쇠 괄호> 를 붙여 타입을 지정하면 된다.

 

// 객체 선언 시 앞쪽 자료형에 제네릭이 명확히 작성되었다면 뒤쪽은 빈칸으로 써도 무방
ArrayList<Integer> list = new ArrayList<Integer>();

 

선언 시 앞쪽에 타입을 명확히 제시했다면 new 뒤에 오는 <> 는 빈칸으로 써도 무방하다.

 

 

이렇게 선언된 리스트에는 Integer 형을 제외한 자료형은 삽입할 수 없게 된다.

 

Integer i = list.get(0); // 자료를 꺼낼 때 강제형변환 쓰지 않아도 됨

 

또한 자료를 꺼낼 때 캐스팅 연산을 사용하지 않아도 자료형 변수에 저장할 수 있다.

 

 

ArrayList<Integer> list = new ArrayList<Integer>();

list.add(100);
list.add(200);
list.add(300);

for(Integer k : list) {
    System.out.printf("%d ", k);
}

for문에 사용한 방식은 list에 있는 값을 Integer 자료형인 k 값으로 하나하나 받아 출력하겠다는 의미다.

 

 

HashSet<Integer> h = new HashSet<Integer>();
Hashtable<String, Integer> ht = new Hashtable<>();
HashMap<Integer, String> hm = new HashMap<>();

 

이후 다루게 될 Set, Hash 클래스에서도 제네릭은 요긴하게 쓰인다.

심지어 타입 지정을 하지 않으면 자료형을 읽지 못하고 오류를 범하는 경우가 있으니 웬만해서는 제네릭으로 타입을 지정해주도록 하자.