제네릭 타입
어느 특정 데이터 형식에 의존하지 않고, 하나의 값이 여러 데이터 타입들을 가질 수 있도록 하는 방법
ex) ArrayList => ArrayList<타입> 객체명 new ArrayList<타입>();
제네릭 타입은 불공변
제네릭 타입은 하위 객체가 상위 객체의 하위 타입이더라도 호환이 되지 않습니다.
아래와 같이 사용하면 컴파일 오류가 납니다. 이를 불공변 하다고 합니다.
ArrayList<Object> objectList = new ArrayList<String>();
제네릭 타입이 불공변하기 때문에 컴파일 타임에서 타입 안전성을 보장해줄 수 있습니다.
제네릭 타입을 사용했을 때 좋은 점
- 잘못된 타입이 들어왔을 때 컴파일 단계에서 오류를 확인 할 수 있습니다. 제일 좋은 오류는 컴파일 단계에서 알 수 있는 오류이기 때문에 제네릭 타입을 사용한다면 좋은 효과를 가져올 수 있습니다.
- 클래스 외부에서 타입을 지정해주기 때문에 타입 체크에 자유롭고 관리가 편합니다.
- 코드의 재사용성이 높아집니다. 예를 들어 여러 객체에 대한 List를 구성할 때 사용한다면 좋을 것입니다.
제네릭 타입을 사용했을 때 주의할 점
- 클래스와 인터페이스만 적용되기 때문에 자바 기본타입은 사용할 수 없습니다. 참조 타입만을 사용해야 합니다. 참조 타입은 Integer, Character, Long, String, 사용자 정의 클래스 etc 입니다.
- 제네릭 타입을 사용한 객체 생성은 불가능 합니다. 즉 제네릭 타입의 객체는 생성이 불가능합니다.
예를 들어 T t = new T() 같은 형태는 불가능합니다. - 배열에 대한 제한이 있습니다.
배열과 제네릭의 차이점
<컴파일 시 차이점>
컴파일 시에 배열은 실체화가 되지만 제네릭 타입은 소거가 됩니다.
<배열>
Object[] objects = new String[10];
배열은 런타임에 실체화가 되어 objects는 String[] 으로 동작합니다.
<제네릭 타입>
ArrayList<String> stringList = new ArrayList<String>();
//컴파일 시 아래와 같이 변경
ArrayList stringList = new ArrayList();
제네릭 타입은 런타임 타입을 지정한 부분이 사라지므로 ArrayList의 타입 식별이 불가능합니다.
<배열 생성 시 실제 문제점>
ArrayList<String>[] stringLists = new ArrayList<String>[1];
//String ArrayList 배열 생성 -> 제네릭 타입 소거
ArrayList<Integer> intList = new ArrayList<Integer>;
//Integer 배열 생성 -> 제네릭 타입 소거
intList.add(1);
Object[] objects = stringLists;
objects[0] = intList;
//컴파일 단계에서 보면 제네릭 타입이 소거되어 둘다 ArrayList이므로 저장
String s = stringLists[0].get(0)
//여기서 문제! StringList[0].get(0)을 꺼내면 String이어야 하는데 Integer이기 때문에 문제!
위와 같은 상황에는 런타임 에러가 발생할 수 있다
제네릭 타입의 실체화를 불가능하게 만든 이유
- 만약 제네릭 타입이 런타임에 실체화 된다면 제네릭이 생기기전에 사용했던 클래스들과 호환이 불가능하게 됩니다. 이렇듯 하위 호환성을 보장하기 위해서 실체화를 불가능하게 하였습니다.
- 제네릭이 런타임에 자신의 타입을 소거하더라고 개발자가 선언한 제네릭의 타입 파라미터에 명시한 타입만 들어갈 수 있도록 타입 안전성을 보장해줘야 합니다. (개발자의 의무)
- 제네릭 타입의 목적은 타입 안정성을 보장하기 위함이므로 타입 안정성을 보장해줘야 합니다. 제네릭 배열이 가능하도록 하면 타입 안전성을 보장할 수 없기 때문에 제네릭 배열을 직접 생성할 수 없습니다.
와일드카드 타입이나 형변환을 이용하면 배열을 생성할 수 있지만 되도록 원칙을 지키도록 코딩해야합니다.
제네릭 타입 종류
표시 | 의미 |
<T> | Type |
<E> | Element |
<K> | Key |
<V> | Value |
<N> | Number |
제네릭 클래스
class GenericClass<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
제네릭 클래스에 대한 예제 파일을 제작해보았습니다. T는 타입이 들어가는 위치이고 set메서드 get메서드도 구성해보았습니다.
제네릭 메서드
class GenericClass<T> {
private T content;
GenericClass(T content){
this.content = content;
}
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
//제네릭 메서드 정의 부분
public static<T> boolean compare(GenericClass<T>o1, GenericClass<T>o2) {
boolean contentCompare = o1.getContent().equals(o2.getContent());
return contentCompare;
}
}
제네릭 메서드를 사용할 때는 컴파일러에게 제네릭 메서드라는 것을 알려주기 위해 리턴 타입을 꼭 명시해주어야 합니다.
결론
면접 때 질문을 받았던 제네릭 타입에 대해서 명확한 개념이 잡혀있지 않아 이번 포스팅을 통해
제네릭 타입에 대해서 알아보았습니다. 항상 생각하지만 아는 것은 옆 사람에게 설명할 수 있을 정도가
되어야 한다고 생각합니다. 이번 포스팅을 통해 공부를 하면서 제네릭의 개념을 확립할 수 있었던 시간이었습니다.
[참고]
1. 제네릭 타입
3. 제네릭과 배열의 차이점
'JAVA' 카테고리의 다른 글
[Java] Decorator Pattern (0) | 2022.07.28 |
---|---|
[Java] Template Method Pattern (0) | 2022.07.28 |
[Java] Singleton Pattern (0) | 2022.07.28 |
[Java] 자바 메서드가 생성되는 원리 - 가상 함수 기법 (0) | 2022.07.28 |
[Java] 메모리 관리 - 스택, 힙, Garbage Collection (0) | 2022.07.21 |