[Java] 제네릭 (Generic) 3
22. 제네릭 (Generic)
☑️ 와일드카드
와일드카드란?
- 컴퓨터 프로그래밍에서
*
,?
와 같이 하나 이상의 문자들을 상징하는 특수 문자이다. - 와일드카드는 이미 만들어진 제네릭 타입을 활용하여 사용한다.
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
- 예시를 위해
Box<T>
라는 제네릭 타입을 만들었다.
비제한 와일드카드
static void printWildcard(Box<?> box) {
System.out.println("? = " + box.get());
}
- 제네릭 타입인
Box<T>
는Box<Dog>
,Box<Cat>
와 같이 사용된다. - 와일드카드는 타입 인자가 정해진 제네릭 타입을 전달받아서 활용할 때 사용한다.
- 와일드카드인
?
를 사용하면 제한 없이 모든 타입을 다 받을 수 있다.
Box<Dog> dogBox = new Box<>();
dogBox.set(new Dog("멍멍이", 100));
WildcardEx.printWildcard(dogBox); // ? = Animal{name='멍멍이', size=100}
- 와일드카드를 사용하지 않는 일반 메서드의 경우, 특정시점에 타입 매개변수에 타입 인자를 전달해서 타입을 결정해야 하는 복잡한 과정을 거친다.
- 하지만
printWildcard()
와 같이 와일드카드인?
를 사용한다면 모든 타입을 받을 수 있기 때문에 타입을 결정하거나 복잡하게 작동하지 않는다.
상한 와일드카드
static void printWildcard(Box<? extends Animal> box) {
Animal animal = box.get();
System.out.println("이름 = " + animal.getName());
}
<? extends Animal>
와 같이extends
를 사용하여 와일드카드에 상한 제한을 둘 수 있다.- 위의 코드에서는
Animal
과 그 하위타입만 입력받고, 이외의 타입 입력 시 컴파일 오류가 발생한다.
하한 와일드카드
static void writeBox(Box<? super Animal> box) {
box.set(new Dog("멍멍이", 100));
}
<? super Animal>
와 같이super
을 사용하여 와일드카드에 하한 제한을 둘 수 있다.- 위의 코드에서는
Animal
과 그 상위 타입만 입력받을 수 있다.
Box<Object> objBox = new Box<>();
Box<Animal> animalBox = new Box<>();
Box<Dog> dogBox = new Box<>();
writeBox(objBox);
writeBox(animalBox);
// writeBox(dogBox); // 하한이 Animal
Animal
타입의 하위 타입인Box<Dog>
는 전달할 수 없고,Box<Animal>
과 상위 타입인Box<Object>
는 전달할 수 있다.
권장 방식
static <T extends Animal> T printAndReturnGeneric(Box<T> box) {
T t = box.get();
System.out.println("이름 = " + t.getName());
return t;
}
Box<Dog> dogBox = new Box<>();
Dog dog = WildcardEx.printAndReturnGeneric(dogBox);
- 제네릭타입을 사용한
printAndReturnGeneric()
의 경우,Box<Dog>
의 타입인Dog
객체를 명확하게 반환한다.
static Animal printAndReturnWildcard(Box<? extends Animal> box) {
Animal animal = box.get();
System.out.println("이름 = " + animal.getName());
return animal;
}
Box<Dog> dogBox = new Box<>();
Animal animal = WildcardEx.printAndReturnWildcard(dogBox);
- 하지만, 와일드카드를 사용한
printAndReturnWildcard()
의 경우,Box<Dog>
를 인자로 전달하여도Animal
타입으로 반환이 된다. - 와일드카드는 이미 만들어진 제네릭 타입을 전달받아서 활용하는 것이기 때문에 타입 인자를 통해서 메서드의 타입을 변경할 수 없다.
- 메서드의 타입을 특정 시점에서 변경하고자 한다면 제네릭 타입이나, 제네릭 메서드를 이용하는 것이 좋다.
- 이런 경우가 아니라면, 더 단순한 와일드카드 사용을 권장한다고 한다.
☑️ 타입 이레이저
타입 이레이저란?
- 자바에는 컴파일 전인
.java
와 컴파일 이후인 자바 바이트코드.class
가 존재하며, 런타임에는.class
로 자바를 실행한다. - 자바 컴파일러는 컴파일이 끝나면 제네릭과 관련된 정보를 삭제한다.
- 즉, 제네릭 타입은 컴파일 시점에만 존재하고, 런타임 시에는 제네릭 정보가 지워진다.
상한 제한이 없는 경우
컴파일 전 Main.java
void main() {
Box<Integer> box = new Box<Integer>();
box.set(10);
Integer result = box.get();
}
- 와일드카드에서 예시로 사용한
Box<T>
제네릭 타입에Integer
타입을 전달한다.
컴파일 후 Box.class
public class Box<Object> {
private Object value;
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}
.java
에서 상한 제한 없이 선언된 타입 매개변수T
의 경우, 컴파일 후.class
에서Object
로 변환된다.
컴파일 후 Main.class
void main() {
Box box = new Box();
box.set(10);
Integer result = (Integer) box.get(); // 컴파일러가 캐스팅 추가
}
box.get()
메서드의 반환 타입은Object
로 변환되었지만, 컴파일러가 자동으로(Integer)
캐스팅을 삽입하여 타입 호환성을 유지한다.
상한 제한이 있는 경우
- 예를 들어,
.java
에서AnimalHospital<T extends Animal>
과 같이Animal
타입으로 상한 제한한다. - 컴파일 후에
.class
에T
의 타입 정보가 제거된다 하더라도, 상한으로 지정된Animal
타입으로 대체되어Animal
과 관련된 기능을 사용할 수 있다. - 또한, 타입 인자로 지정한 타입으로 컴파일러가 다운 캐스팅을 대신 처리해주기 때문에 아무 문제도 발생하지 않는다.
한계
- 컴파일 이후에는 제네릭의 타입 정보가 존재하지 않는다. 따라서, 런타임에 타입을 활용하는 코드를 작성할 수 없다.
- 예를 들어,
param instanceof T
,new T()
는 컴파일 이후 항상param instanceof Object
,new Object()
로 변환된다. - 이는 개발자가 의도한 것과 다르기 때문에 자바는 타입 매개 변수에
instanceof
와new
를 허용하지 않는다.
김영한의 실전 자바 중급 2편을 참고하였습니다