[Java] ☕ 제네릭 (Generic)
📖 제네릭 (Generic)
제네릭은 컴파일 타임 유형 안정성을 제공하면서 다양한 유형의 객체에서 작동하는 유형 또는 메소드
를 허용하기 위해 추가되었다.
- 파라미터 타입이나 리턴 타입의 정의를 미룬다.
- 타입에 대한 유연성과 안정성을 확보
- 런타임 환경에 아무런 영향이 없는 컴파일 시점의 전처리 기술
🍄 📖 제네릭 사용법
class GenericClass<T> {
// 제네릭 메소드
private T t;
public void setT(T t) {
this.t = t;
}
public T getT() {
return t;
}
}
public void main(String[] args){
GenericClass<Integer> genericClass = new GenericClass<>();
genericClass.setT(1);
System.out.println(genericClass.getT());
}
원하는 타입을 클래스 선언시에 선언해주는 방식이기 떄문에, 다른 타입이지만 같은 기능에 대한 확정성 좋다.
<T>
와 같이 <>
부호 안에 타입 파라미터를 위치시킨다.
원하는 반환타입이나 받는 매개인자를 제네릭을 사용하여 정의해줄 수 있다.
🍄 📖 자주 사용하는 타입인자
- 일반적으로 대문자이며, 위와 같이 사용한다.
- 하지만, 꼭 위의 인자를 지켜야 선언되는 것은 아니다.
📖 와일드카드
와일드 카드는 자바의 공변성을 일종의 편법으로 해결하기 위해 나온 개념이다.
🍄 자바의 공변성
변성
이란 타입의 상속 계층 관계에서 서로 다른 타입 간에 어떤 관계가 있는지를 나타내는 지표이다.
- 공변 -> S가 T의 하위 타입이면
- S[]는 T[]의 하위 타입
- class
<S>
는 class<T>
의 하위 타입
따라서, 다음 다운캐스팅같은 것이 가능하다.
Object[] object = new Integer[5];
하지만, 제네릭 배열 코드는 다음과 같은 것이 불가능하다.
//불가능
GenericClass<Object> genericClass = new GenericClass<Integer>();
따라서, PriorityQueue
같은 자료구조를 사용할 때, 뒤의 <>
에는 아무것도 넣지 않는다.
PriorityQueue<Integer> pq = new PriorityQueue<>();
하지만, 이는 자바에서 다음과 같이 List
의 모든값을 출력하는 코드가 있을 때, 오류가 발생하게 된다.
public static void print(List<Object> arr) {
for (Object e : arr) {
System.out.println(e);
}
}
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
print(integers); // ! Error
}
이를 해결하기 위해서는 모든 자료형마다의 print
메서드를 만들어 줘야 하기 때문에 자바의 객체지향을 전혀 이용 못하게 되는 코드로 작성해야 한다.
이를 해결하기 위한 방법이 바로 와일드카드
이다.
🍄 와일드카드
이를 해결하기 위하여 <?>
와 같은 형태로 물음표만 가지고 정의를 한다.
내부적으로 Object로 정의되어서 사용하고 있는 모든 타입의 인자를 받을 수 있다.
public MyArrayList(Collection<?> in) {
for (T elem : in) {
element[index++] = elem;
}
}
public void clone(Collection<?> out) {
for (Object elem : element) {
out.add((T) elem);
}
}
이때, 위와 같이 단순히 ?
만 사용하여 정의하면 논리적인 오류가 발생한다.
왜냐하면 어떠한 타입도 올 수 있기 때문에 매개변수를 꺼내거나 저장하는 로직에서 논리적인 에러가 발생한다.
따라서 다음과 같이 바운디드 타입인 extends, super
을 사용하여 타입의 범위를 정해주어야 한다.
- <? extends T>
- 상한 경계 바운디드 타입
- <? super T>
- 하한 경계 바운디드 타입
🍄 Generic Type Erasure
컴파일 타임에만 타입 제약 조건을 정의하고, 런타임에는 타입을 제거한다는 것이다.
다음은 위에서 봤던 GenericClass
의 일부분이다.
class GenericClass<T> {
// 제네릭 메소드
private T t;
...
}
위 코드가 컴파일이 된 후에는 다음과 같이 코드가 변경된다.
class GenericClass {
// 제네릭 메소드
private Object t;
...
}
개인 공부 기록용 블로그입니다.
틀리거나 오류가 있을 경우 제보해주시면 감사하겠습니다.😁