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편에서 소개를 하겠다.
출처: 자바의 정석
'JAVA > JAVA' 카테고리의 다른 글
스트림(Stream) - 개념(1/3) (0) | 2023.01.03 |
---|---|
Generics 지네릭스 - 와일드 카드 및 메소드 (2/2) (0) | 2023.01.02 |
int, long 타입과 같은 Primitive type null 값이 안 들어가는 이유 (0) | 2021.07.04 |
JAVA - 리스트와 ArrayList 차이점, 메소드 설명. (2) | 2021.06.28 |
mkdirs와 mkdir 차이점 (0) | 2021.05.07 |