🏷️타입 매개변수 제한1 - 시작
public class AnimalHospitalMain {
public static void main(String[] args) {
DogHospital dogHospital = new DogHospital();
CatHospital catHospital = new CatHospital();
Dog dog = new Dog("멍멍이", 100);
Cat cat = new Cat("야옹이", 300);
dogHospital.setAnimal(dog);
dogHospital.checkup();
Dog findDog = dogHospital.bigger(new Dog("멍멍이2", 200));
System.out.println(findDog);
catHospital.setAnimal(cat);
catHospital.checkup();
Cat findCat = catHospital.bigger(new Cat("야옹이2", 100));
System.out.println(findCat);
}
}
- ✔️요구사항 : 개 병원은 개만 받을 수 있고 고양이 병원은 고양이만 받을 수 있다.
- 개 병원과 고양이 병원을 별도의 클래스로 만들었다.
- 각 클래스 별로 타입이 명확하기 때문에 개 병원은 개만 받을 수 있고 고양이 병원은 고양이만 받을 수 있다.
- 타입 안전성은 보장할 수 있으나 코드의 재사용성 측면에서 보았을 때 중복이 많다.
🏷️타입 매개변수 제한2 - 다형성 시도
public class AnimalHospitalMainV2 {
public static void main(String[] args) {
AnimalHospital dogHospital = new AnimalHospital();
AnimalHospital catHospital = new AnimalHospital();
Dog dog = new Dog("멍멍이", 100);
Cat cat = new Cat("야옹이", 100);
dogHospital.setAnimal(dog);
dogHospital.checkup();
dogHospital.setAnimal(cat);
catHospital.setAnimal(cat);
catHospital.checkup();
dogHospital.setAnimal(cat);
dogHospital.setAnimal(dog);
Dog findDog = (Dog) dogHospital.bigger(new Dog("멍멍이2", 500));
System.out.println(findDog);
}
}
- ✔️정리
- 다형성을 통해 코드 재사용이 가능하도록 했다.
- 하지만 개 병원에 고양이를 전달이 가능하고 고양이 병원에 개를 전달하는 것이 가능한 문제가 발생한다.
- 다형성을 적용했기 때문에 상위 타입인
Animal
타입을 반환하게 되는데 Dog
, Cat
으로 반환하려면 다운캐스팅을 해야 한다.
- 실수로 고양이를 입력했는데 개를 반환하는 상황이라면 캐스팅 예외가 발생한다.
🏷️타입 매개변수 제한3 - 제네릭 도입과 실패
public class AnimalHospitalV3<T> {
private T animal;
public T getAnimal() {
return animal;
}
public void setAnimal(T animal) {
this.animal = animal;
}
}
- ✔️정리
<T>
를 사용해서 제네릭 타입을 선언했다.
- 제네릭 타입을 선언하면 자바 컴파일러 입장에서
T
에 어떤 값이 들어올지 예측할 수 없다.
<T>
에는 타입 인자로 Integer
가 들어올 수도 있고 Dog
가 들어올 수도 있다. 물론 Object
도 들어올 수 있다.
- 자바 컴파일러는 어떤 타입이 들어올 지 알 수 없기 때문에
T
를 어떤 타입이든 받을 수 있는 모든 객체의 최종 부모인 Object
타입으로 가정한다.
- 따라서
Object
가 제공하는 메서드만을 호출할 수 있는 것이다.
- 원하는 기능을 사용하려면
Animal
타입이 제공하는 기능들이 필요한데 이 기능을 모두 사용할 수 없게 된 것이다.
- 최종 목표는 동물과 전혀 관계 없는 타입이 들어올 우려가 있는 것인데 최소한
Animal
이나 그 자식을 타입 인자로 제한하려고 하는 것이다.
🏷️타입 매개변수 제한4 - 타입 매개변수 제한
public class AnimalHospitalV4<T extends Animal> {
private T animal;
public T getAnimal() {
return animal;
}
public void setAnimal(T animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
}
public T bigger(T target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
- ✔️정리
- 타입 매개변수에 입력될 수 있는 상한선(
extends
)을 지정해서 문제를 해결했다.
- 제네릭 클래스 안에서
Animal
의 기능을 사용할 수 있다.
- 타입 안전성X 문제 해결
- 개 병원에 고양이를 전달하는 문제가 발생한다. → 해결
Animal
타입을 반환하기 때문에 다운캐스팅을 해야 한다. → 해결
- 실수로 고양이를 입력했는데 개를 반환하는 상황이라면 캐스팅 예외가 발생한다. → 해결
- 제네릭 도입 문제 해결
- 제네릭에서 타입 매개변수를 사용하면 어떤 타입이든 들어올 수 있다. → 해결
- 어떤 타입이든 수용할 수 있는
Object
로 가정하고 Object
의 기능만 사용할 수 있다. → 해결
🏷️제네릭 메서드
public class GenericMethod {
public static Object objectMethod(Object object) {
System.out.println("object = " + object);
return object;
}
public static <T> T genericMethod(T obj) {
System.out.println("obj = " + obj);
return obj;
}
public static <T extends Number> T numberMethod(T t) {
System.out.println("t = " + t);
return t;
}
}
- ✔️제네릭 타입
- 정의 :
GenericClass<T>
- 타입 인자 전달 : 객체를 생성하는 시점
- ✔️제네릭 메서드
- 정의 :
<T> T genericMethod(T t)
- 타입 인자 전달 : 메서드를 호출하는 시점
- 제네릭 메서드는 클래스 전체가 아니라 특정 단위로 메서드를 도입할 때 사용한다.
- 제네릭 메서드를 정의할 때는 메서드 반환 타입 왼쪽에
<T>
와 같이 타입 매개변수를 사용한다.
- 제네릭 메서드는 실제 호출하는 시점에 다이아몬드를 사용해서
<Integer>
와 같이 타입을 정하고 호출한다.
- ✔️제네릭 메서드 - 인스턴스 메서드, static 메서드
- 제네릭 메서드의 타입 인자 전달 시점은 메서드를 호출하는 시점이다.
- 따라서 둘 다 적용이 가능하다.
- ✔️제네릭 타입 - 인스턴스 메서드, static 메서드
- 제네릭 타입의 타입 인자 전달 시점은 객체를 생성하는 시점이다.
- 하지만 static 메서드는 클래스 단위로 작동하기 때문에 제네릭 타입과는 무관하다.
- 따라서 static 메서드에 제네릭을 도입하려면 제네릭 메서드를 사용해야 한다.
🏷️제네릭 메서드 활용
public class AnimalMethod {
public static <T extends Animal> void checkup(T t) {
System.out.println("동물 이름: " + t.getName());
System.out.println("동물 크기: " + t.getSize());
}
public static <T extends Animal> T bigger(T t1, T t2) {
return t1.getSize() > t2.getSize() ? t1 : t2;
}
}
public class MethodMain2 {
public static void main(String[] args) {
Dog dog = new Dog("멍멍이", 100);
Cat cat = new Cat("야옹이", 50);
AnimalMethod.checkup(dog);
AnimalMethod.checkup(cat);
Dog targetDog = new Dog("멍멍이2", 300);
Cat targetCat = new Cat("야옹이2", 300);
System.out.println(AnimalMethod.bigger(dog, targetDog));
System.out.println(AnimalMethod.bigger(cat, targetCat));
}
}
🏷️와일드카드1
public class WildCardEx {
static <T> void printGenericV1(Box<T> box) {
System.out.println("T = " + box.getValue());
}
static void printWildCardV1(Box<?> box) {
System.out.println("? = " + box.getValue());
}
static <T extends Animal> void printGenericV2(Box<T> box) {
T t = box.getValue();
System.out.println("T = " + box.getValue());
System.out.println("이름: " + t.getName());
System.out.println("크기: " + t.getSize());
}
static void printWildCardV2(Box<? extends Animal> box) {
Animal value = box.getValue();
System.out.println("value = " + value);
}
static <T extends Animal> T printGenericV3(Box<T> box) {
T t = box.getValue();
System.out.println("이름: " + t.getName());
return t;
}
static Animal printAndReturnGeneric(Box<? extends Animal> box) {
Animal value = box.getValue();
System.out.println("value = " + value);
return value;
}
}
public class WildCardMain {
public static void main(String[] args) {
Box<Object> objectBox = new Box<>();
Box<Dog> dogBox = new Box<>();
Box<Cat> catBox = new Box<>();
dogBox.setValue(new Dog("멍멍이", 100));
WildCardEx.printGenericV1(dogBox);
WildCardEx.printGenericV2(dogBox);
Dog dog = WildCardEx.printGenericV3(dogBox);
System.out.println(dog);
catBox.setValue(new Cat("야옹이", 100));
WildCardEx.printWildCardV1(catBox);
WildCardEx.printWildCardV2(catBox);
Animal animal = WildCardEx.printAndReturnGeneric(catBox);
System.out.println("animal = " + animal);
}
}
- ✔️와일드카드
- 와일드카드라는 뜻은 컴퓨터 프로그래밍에서
*
, ?
와 같이 하나 이상의 문자들을 상징하는 특수 문자를 뜻한다.
- 쉽게 이야기해서 여러 타입이 들어올 수 있다.
- 와일드카드는 이미 만들어진 제네릭 타입을 활용할 때 사용하는 것이다.
- 와일드카드인
?
는 모든 타입을 다 받을 수 있다는 뜻이다.
- 다음과 같이 해석할 수 있다. →
?
== <? extends Object>
- 이렇게
?
만 사용해서 제한 없이 모든 타입을 다 받을 수 있는 와일드카드를 비제한 와일드카드라 한다.
- ✔️제네릭 메서드 vs 와일드카드
- 제네릭 메서드에는 타입 매개변수가 존재한다.
- 특정 시점에 타입 매개변수에 타입 인자를 전달해서 타입을 결정해야 한다.
- 와일드카드는 일반적인 메서드에 사용할 수 있고 단순히 매개변수로 제네릭 타입을 받을 수 있다는 것 뿐이다.
- 제네릭 메서드처럼 타입을 결정하거나 복잡하게 작동하지 않는다.
- 단순히 일반 메서드에 제네릭 타입을 받을 수 있는 매개변수가 하나 있는 것 뿐이다.
- 제네릭 타입이나 제네릭 메서드를 정의하는 꼭 필요한 상황이 아니라면 단순한 와일드카드 사용을 권장한다.
🏷️와일드카드2
public class WildCardEx {
static <T> void printGenericV1(Box<T> box) {
System.out.println("T = " + box.getValue());
}
static void printWildCardV1(Box<?> box) {
System.out.println("? = " + box.getValue());
}
static <T extends Animal> void printGenericV2(Box<T> box) {
T t = box.getValue();
System.out.println("T = " + box.getValue());
System.out.println("이름: " + t.getName());
System.out.println("크기: " + t.getSize());
}
static void printWildCardV2(Box<? extends Animal> box) {
Animal value = box.getValue();
System.out.println("value = " + value);
}
static <T extends Animal> T printGenericV3(Box<T> box) {
T t = box.getValue();
System.out.println("이름: " + t.getName());
return t;
}
static Animal printAndReturnGeneric(Box<? extends Animal> box) {
Animal value = box.getValue();
System.out.println("value = " + value);
return value;
}
}
- ✔️상한 와일드카드
- 제네릭 메서드와 마찬가지로 와일드카드에도 상한 제한을 둘 수 있다.
<? extends Animal>
을 지정했다.
Animal
과 같이 그 하위 타입만 입력을 받는다. 만약 다른 타입을 입력하면 컴파일 오류가 발생한다.
box.getValue()
을 통해 꺼낼 수 있는 타입의 최대 부모는 Animal
이 된다. 따라서 Animal
타입으로 조회할 수 있다.
- 결과적으로
Animal
타입의 기능을 호출할 수 있다.
- ✔️타입 매개변수가 꼭 필요한 경우
- 제네릭 타입이나 제네릭 메서드가 꼭 필요한 상황이면
<T>
를 사용하고, 그렇지 않은 상황이라면 와일드카드를 사용하는 것을 권장한다.
- ✔️하한 와일드카드
- 하한을
Animal
로 제한했기 때문에 Animal
을 상속받는 자식들은 전달할 수 없다.
public class WildCardMain1 {
public static void main(String[] args) {
Box<Object> objectBox = new Box<>();
Box<Animal> animalBox = new Box<>();
Box<Cat> catBox = new Box<>();
Box<Dog> dogBox = new Box<>();
writeBox(objectBox);
writeBox(animalBox);
}
static void writeBox(Box<? super Animal> box) {
box.setValue(new Dog("멍멍이", 100));
System.out.println("box.value = " + box.getValue());
box.setValue(new Cat("고양이", 100));
System.out.println("box.value = " + box.getValue());
}
}
🏷️타입 이레이저
- ✔️타입 이레이저
- 제네릭은 자바 컴파일 단계에서만 사용되고, 컴파일 이후에는 제네릭 정보가 삭제된다.
- 제네릭에 사용한 타입 매개변수가 모두 사라지는 것이다.
- 컴파일 전인
.java
에서는 제네릭의 타입 매개변수가 존재하지만, 컴파일 이후인 자바 바이트코드 .class
에는 타입 매개변수가 존재하지 않는다.