- 동물 병원 생성
- 요구사항 : 개 병원은 개만 받을 수 있고, 고양이 병원은 고양이만 받을 수 있음
package generic.ex3;
import generic.animal.Dog;
public class DogHospital {
private Dog animal;
public void set(Dog animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Dog bigger(Dog target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
Dog 타입을 가짐checkup() : 개의 이름과 크기를 출력하고, 개의 sound() 메서드를 호출함bigger() : 다른 개와 크기를 비교하고, 둘 중에 큰 개를 반환함package generic.ex3;
import generic.animal.Cat;
public class CatHospital {
private Cat animal;
public void set(Cat animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Cat getBigger(Cat target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
Cat 타입을 가짐checkup() : 고양이의 이름과 크기를 출력하고, 고양이의 sound() 메서드를 호출함bigger() : 다른 고양이와 크기를 비교하고, 둘 중 큰 고양이를 반환함package generic.ex3;
import generic.animal.Cat;
import generic.animal.Dog;
public class AnimalHospitalMainV0 {
public static void main(String[] args) {
DogHospital dogHospital = new DogHospital();
CatHospital catHospital = new CatHospital();
Dog dog = new Dog("멍멍이1", 100);
Cat cat = new Cat("냐옹이1", 300);
// 개 병원
dogHospital.set(dog);
dogHospital.checkup();
// 고양이 병원
catHospital.set(cat);
catHospital.checkup();
// 문제1: 개 병원에 고양이 전달
// dogHospital.set(cat); // 다른 타입 입력: 컴파일 오류
// 문제2: 개 타입 반환
dogHospital.set(dog);
Dog biggerDog = dogHospital.bigger(new Dog("멍멍이2", 200));
System.out.println("biggerDog = " + biggerDog);
}
}
실행 결과
동물 이름: 멍멍이1
동물 크기: 100
멍멍
동물 이름: 냐옹이1
동물 크기: 300
냐옹
biggerDog = Animal{name='멍멍이2', size=200}
위 코드는 처음에 제시한 요구사항을 명확하게 지킴
여기서 개 병원과 고양이 병원을 각각 별도의 클래스로 만듦
각 클래스 별로 타입이 명확하기 때문에 개 병원은 개만 받을 수 있고, 고양이 병원은 고양이만 받을 수 있음
따라서 개 병원에 고양이를 전달하면 컴파일 오류가 발생함
그리고 개 병원에서 bigger()로 다른 개를 비교하는 경우 더 큰 개를 Dog 타입으로 반환함
- 코드 재사용성 X : 개 병원과 고양이 병원은 중복이 많이 보임
- 타입 안전성 O : 타입 안전성이 명확하게 지켜짐
Dog,Cat은Animal이라는 명확한 부모 타입이 있음- 다형성을 사용하여 중복 제거 시도
package generic.ex3;
import generic.animal.Animal;
public class AnimalHospitalV1 {
private Animal animal;
public void set(Animal animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Animal getBigger(Animal target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
-Animal() 타입을 받아서 처리
checkup(), getBigger()에서 사용하는 animal.getName(), animal.getSize(), animal.sound() 메서드는 모두 Animal 타입이 제공하는 메서드package generic.ex3;
import generic.animal.Cat;
import generic.animal.Dog;
public class AnimalHospitalMainV1 {
public static void main(String[] args) {
AnimalHospitalV1 dogHospital = new AnimalHospitalV1();
AnimalHospitalV1 catHospital = new AnimalHospitalV1();
Dog dog = new Dog("멍멍이1", 100);
Cat cat = new Cat("냐옹이1", 300);
// 개 병원
dogHospital.set(dog);
dogHospital.checkup();
// 고양이 병원
catHospital.set(cat);
catHospital.checkup();
// 문제1: 개 병원에 고양이 전달
dogHospital.set(cat); // 매개변수 체크 실패: 컴파일 오류가 발생하지 않음
// 문제2: 개 타입 반환, 캐스팅 필요
dogHospital.set(dog);
Dog biggerDog = (Dog) dogHospital.getBigger(new Dog("멍멍이2", 200));
System.out.println("biggerDog = " + biggerDog);
}
}
실행 결과
동물 이름: 멍멍이1
동물 크기: 100
멍멍
동물 이름: 냐옹이1
동물 크기: 300
냐옹
biggerDog = Animal{name='멍멍이2', size=200}
- 코드 재사용 O : 다형성을 통해
AnimalHospitalV1: 하나로 개와 고양이를 모두 처리함- 타입 안전성 X
- 개 병원에 고양이를 전달하는 문제가 발생함
Animal타입을 반환하기 때문에 다운 캐스팅을 해야 함- 실수로 고양이를 입력했는데, 개를 반환하는 상황이라면 캐스팅 예외가 발생함
- 제네릭을 도입해서 코드 재사용을 늘리고, 타입 안전성 문제도 해결 시도
package generic.ex3;
public class AnimalHospitalV2<T> {
private T animal;
public void set(T animal) {
this.animal = animal;
}
public void checkup() {
// T의 타입을 메서드를 정의하는 시점에는 알 수 없다. Object의 기능만 사용 가능
animal.toString();
animal.equals(null);
// 컴파일 오류
//System.out.println("동물 이름: " + animal.getName());
//animal.sound();
}
public T getBigger(T target) {
// 컴파일 오류
//return animal.getSize() > target.getSize() ? animal : target;
return null;
}
}
<T>를 사용해서 제네릭 타입을 선언함
제네릭을 선언하면 자바 컴파일러 입장에서 T에 어떤 값이 들어올지 예측할 수 없음
Animal 타입의 자식이 들어오기를 기대했지만, 위 코드 어디에도 Animal에 대한 정보는 없음T에는 타입 인자로 Integer가 들어올 수도 있고, Dog가 들어올 수도 있음Object가 들어올 수도 있음다양한 타입 인자
AnimalHospitalV2<Dog> dogHospital = new AnimalHospitalV2<>();
AnimalHospitalV2<Cat> catHospital = new AnimalHospitalV2<>();
AnimalHospitalV2<Integer> integerHospital = new AnimalHospitalV2<>();
AnimalHospitalV2<Object> objectHospital = new AnimalHospitalV2<>();
자바 컴파일러는 어떤 타입이 들어올지 알 수 없기 때문에 T를 어떤 타입이든 받을 수 있는 모든 객체의 최종 부모인 Object 타입으로 가정함
Object가 제공하는 메서드만 호출할 수 있음Animal 타입이 제공하는 기능들이 필요한데, 이 기능을 모두 사용할 수 없음추가적인 문제점은 동물 병원에 Integer, Object 같은 동물과 전혀 관계없는 타입을 타입 인자로 전달할 수 있다는 점
Animal이나 그 자식을 타입 인자로 제한해야 하고 싶은 상태package generic.ex3;
import generic.animal.Cat;
import generic.animal.Dog;
public class AnimalHospitalMainV2 {
public static void main(String[] args) {
AnimalHospitalV2<Dog> dogHospital = new AnimalHospitalV2<>();
AnimalHospitalV2<Cat> catHospital = new AnimalHospitalV2<>();
AnimalHospitalV2<Integer> integerHospital = new AnimalHospitalV2<>();
AnimalHospitalV2<Object> objectHospital = new AnimalHospitalV2<>();
}
}
- 제네릭에서 타입 매개변수를 사용하면 어떤 타입이든 들어올 수 있음
- 따라서 타입 매개변수를 어떤 타입이든 수용할 수 있는
Object로 가정하고,Object의 기능만 사용할 수 있음
Animal로 제한하지 않았기 때문임Animal과 그 자식만 들어올 수 있게 제한해야 함
- 타입 매개변수를 특정 타입으로 제한할 수 있음
package generic.ex3;
import generic.animal.Animal;
public class AnimalHospitalV3<T extends Animal> {
private T animal;
public void set(T animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public T getBigger(T target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
<T extends Animal>T를 Animal과 그 자식만 받을 수 있도록 제한을 두는 것Animal과 그 자식으로 제한됨AnimalHospitalV3<Animal>
AnimalHospitalV3<Dog>
AnimalHospitalV3<Cat>
T에 입력될 수 있는 값의 범위를 예측할 수 있음Animal, Dog, Cat만 들어올 수 있음Animal을 T의 타입으로 가정해도 문제가 없음Animal이 제공하는 getName(), getSize() 같은 기능을 사용할 수 있음package generic.ex3;
import generic.animal.Cat;
import generic.animal.Dog;
public class AnimalHospitalMainV3 {
public static void main(String[] args) {
AnimalHospitalV3<Dog> dogHospital = new AnimalHospitalV3<>();
AnimalHospitalV3<Cat> catHospital = new AnimalHospitalV3<>();
Dog dog = new Dog("멍멍이1", 100);
Cat cat = new Cat("냐옹이1", 300);
//개 병원
dogHospital.set(dog);
dogHospital.checkup();
//고양이 병원
catHospital.set(cat);
catHospital.checkup();
// 문제1 해결: 개 병원에 고양이 전달
// dogHospital.set(cat); // 다른 타입 입력: 컴파일 오류
// 문제2 해결: 개 타입 반환
dogHospital.set(dog);
Dog biggerDog = dogHospital.getBigger(new Dog("멍멍이2", 200));
System.out.println("biggerDog = " + biggerDog);
}
}
실행 결과
동물 이름: 멍멍이1
동물 크기: 100
멍멍
동물 이름: 냐옹이1
동물 크기: 300
냐옹
biggerDog = Animal{name='멍멍이2', size=200}
AnimalHospitalV3<Integer>와 같이 동물과 전혀 관계없는 타입 인자를 컴파일 시점에 막음Animal의 기능을 사용할 수 있음Animal 타입을 반환하기 때문에 다운 캐스팅을 해야 함 -> 해결Object로 가정하고, Object의 기능만 사용할 수 있음 -> 해결Animal을 상한으로 두어서 Animal의 기능을 사용할 수 있음