본문 바로가기

JAVA/JAVA

Generics 지네릭스 - 개념 및 활용 (1/2)

728x90

1. 개념

컴파일 시 타입을 체크해주는 기능이다.

 

아래와 같이 News 객체만 저장할 수 있는 ArrayList를 제네릭스를 활용해 구현해 봤다.

 

ArrayList<News> newslist = new ArrayList<News>();

newslist.add(new News("h")) // 가능
newslist.add(new SportsNews("h")) // 컴파일 에러

이런 식으로 제네릭스를 통해 오직 News 객체만 담을 수 있다. 

 

 

그러면 이걸 왜 사용할까? 아래의 예시를 살펴보자.

        ArrayList newslist = new ArrayList();
        newslist.add(new News("h"));

        News n = (News)newslist.get(0);

News n = newslist.get(0)으로 하면 컴파일 에러가 발생하기에 반드시 앞에 (News)로 형변환을 해줘야 한다.

 

그러면 위의 코드에 제네릭스를 적용하면 어떻게 될까? 

        ArrayList<News> newslist = new ArrayList<News>();
        newslist.add(new News("h"));

        News n = newslist.get(0);

위의 코드와 달리 형변환이 불필요하다. 

 

이를 통해 제네릭스는 '타입 안정성을 높이고 형변환의 번거로움을 줄여준다'는 사실을 알 수가 있다. 

 

 

2. 타입 변수

제네릭은 클래스를 작성할 때, Object 타입 대신에 타입 변수(E)를 선언한다.

 

public class ArrayList extends AbstractList{
    public transient Object[] elementData;
    public Object get(int index){}
}

위의 코드를 

public class ArrayList extends AbstractList<E>{
    public transient E[] elementData;
    public E get(int index){}
}

타입 변수 E를 통해 쉽게 표현할 수 있다. 

 

그리고 이 타입 변수를 실제 객체 타입으로 대입할 수 있다. guitar object를 대입해보면 아래와 같이 나온다. 

public class ArrayList extends AbstractList{
    public transient guitar[] elementData;
    public guitar get(int index){}
}

 

3. 용어 

  • T 타입 변수 or 타입 매개변수.
  • BOX 원시 타입 (일반 클래스 => 지네릭 클래스)
  • BOX<T> 지네릭 클래스 선언. 

 

(1) Iterator

public interface Iterator{
    boolean hasNext();
    Object next();
    void remove();
}

기존의 Iterator를 타입 변수를 활용해 제네릭으로 아래처럼 바꿀 수 있다. 

public interface Iterator<E>{
    boolean hasNext();
    E next();
    void remove();
}

 

(2)HashMap <K, V>

 

여기서 K: key, V: value로 바꾼 것이다. 아래처럼 HashMap을 지네릭으로 표현할 수 있다.

 

public class HashMap<K,V> extends AbstractMap<K,V>{
    public V get(Object Key){}
    public V put(K key, V value){}
    (생략)
}

 

(3) 제한된 지네릭 클래스

 

extends로 대입할 수 있는 타입을 제한할 수 있다. 

 

class guitarBox<T extends Guitar>{
	ArrayList<T> list = new ArrayList<T>();
   	(생략)
}

여기서 T는 모든 타입을 뜻한다. <T extends Guitar>로 Guitar의 자손만 타입으로 지정할 수 있다. 

 

 

electric을 Guitar의 자손이라 생각하고 car는 Guitar의 자손이 아니라 가정해보면 아래의 결과를 얻는다. 

GuitarBox<elctirc> electricBox = new GuitarBox<electric>(); //가능
GuitarBox<car> carBox = new GuitarBox<car>(); //에러

car 오브젝트는 Guitar의 자손이 아니므로 에러가 나는 것!

 

(4) 제한된 지네릭 인터페이스

클래스뿐만 아니라 인터페이스도 제한이 가능하다. 

interface playing{}
class GuitarBox<T extends playing>{...}

여기서 주의할 점은 implements가 아닌 extends를 사용해야 한다

 

4. 제약 

1) static 멤버에 타입 변수 사용이 불가하다.

class Box<T>{
    static T item; // 에러 발생
    static int compare(T t1, T t2) //에러 발생 
}

static이 안 되는 이유는 타입 변수의 대입은 인스턴스 별로 다르게 가능하기 때문이다. 반면 static모든 인스턴스의 공통으로 사용하는 멤버라 사용이 제약되는 것이다.

 

 

2) 배열/객체 생성할 때(new T) 타입 변수 불가. but 타입 변수로 배열 선언 가능. 

 

class Box<T>{
    T[] Arr1; // 가능.
    
    T[] toArray(){
    	T[] Arr2 = new T[Arr1.length]; // 불가능 
    }
}

T[] Arr1은 T타입의 배열을 위한 참조 변수라서 가능하지만, new T[Arr1.length] 같은 지네릭 배열은 불가능하다. 

 

new T는 객체 생성이나 배열을 생성할 수 없다

 

new 연산자는 뒤에 타입이 확정돼야 한다. T는 어떤 타입이 올지 모르기에 생성할 수 없다. 

 

 

지네릭스의 와일드 카드는 2편에서 소개를 하겠다. 

 

출처: 자바의 정석

728x90