[Java] 제네릭 (Generic) 2
22. 제네릭 (Generic)
☑️ 타입 매개변수 제한
제네릭 도입 실패
public class AnimalHospital<T> {
private T animal;
public void set(T animal) {
this.animal = animal;
}
public void checkup() {
animal.toString();
animal.equals(null);
// System.out.println("동물 이름: " + animal.getName());
// animal.sound();
}
public T bigger(T target) {
// return animal.getSize() > target.getSize() ? animal : target;
return null;
}
}
<T>
를 사용해서 제네릭 타입을 선언하였고,checkup()
,bigger()
과 같은 메서드를 정의하는 시점에서는 T의 타입을 알 수 없기 때문에Object
의 기능을 사용하는 것 이외에는 컴파일 오류가 발생한다.AnimalHospital<T>
를 사용하여 객체를 생성할 때 자바 컴파일러는T
를 어느 타입이든 받을 수 있는Object
로 가정한다. 그래서Integer
,Object
와 같은 동물과 관련 없는 모든 타입을 타입인자로 전달할 수 있다.AnimalHospital<Dog> dogHospital = new AnimalHospital<>(); AnimalHospital<Cat> catHospital = new AnimalHospital<>(); AnimalHospital<Integer> integerHospital = new AnimalHospital<>(); AnimalHospital<Object> objectHospital = new AnimalHospital<>();
- 타입 인자로
Animal
과 관련된 타입만 받고싶다면 타입 매개변수에 제한을 두어야 한다.
타입 매개변수 제한
public class AnimalHospital<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 bigger(T target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
<T extends Animal>
과 같이 타입 매개변수T
를Animal
과 그 자식만 받을 수 있도록 제한을 둔다.- 자바 컴파일러는
T
의 타입 인자로Animal
,Dog
,Cat
만 인식하고,Animal
이 제공하는checkup()
,bigger()
메서드를 모두 사용 가능하다.
public static void main(String[] args) {
AnimalHospitalV3<Dog> dogHospital = new AnimalHospitalV3<>();
AnimalHospitalV3<Cat> catHospital = new AnimalHospitalV3<>();
// AnimalHospitalV3<Integer> integer = new AnimalHospitalV3<>();
Dog dog = new Dog("멍멍이1", 100);
Cat cat = new Cat("냐옹이1", 300);
dogHospital.set(dog);
dogHospital.checkup();
catHospital.set(cat);
catHospital.checkup();
// dogHospital.set(cat); // 다른 타입 입력 : 컴파일 오류
dogHospital.set(dog);
Dog biggerDog = (Dog) dogHospital.bigger(new Dog("멍멍이2", 200));
}
- 타입 매개변수
T
에Animal
로 상한을 지정하였기 때문에 전혀 관계 없는 타입 인자를 컴파일 시점에서 막는다. set()
,checkup()
과 같이Animal
의 기능을 모두 사용할 수 있게 되며, 다른 타입을 입력 시 컴파일 오류가 발생한다.dogHospital.bigger()
에서는Animal
타입이 아닌Dog
타입을 반환하기 때문에 다운 캐스팅을 하지 않아도 된다.- 즉, 제네릭에 타입 매개변수를 사용해서 타입 안전성과 코드 재사용을 보장할 수 있게 되었다.
☑️ 제네릭 메서드
public class GenericMethod {
public static <T> T genericMethod(T t) {
System.out.println("generic print: " + t);
return t;
}
}
Integer i = 10;
Integer result = GenericMethod.<Integer>genericMethod(i); // generic print: 10
제네릭 메서드란?
<T> T genericMethod(T t)
로 정의하며, 메서드를 호출하는 시점에 타입 인자를 전달한다.- 제네릭 타입 (
GenericClass<T>
)은 객체를 생성하는 시점에 타입 인자를 전달한다.
- 제네릭 타입 (
- 클래스 전체가 아닌, 특정 메서드 단위에 제네릭을 도입하기 위해 사용한다.
인스턴스 메서드와 static 메서드에 모두 적용할 수 있다.
class Box<T> { static <V> V staticMethod(V t) {} <Z> Z instanceMethod(Z z) {} }
- static 메서드는 인스턴스 단위가 아닌, 클래스 단위로 작동한다.
- 즉, 특정 메서드 단위로 제네릭을 도입해서 사용하는 제네릭 메서드는 static 메서드에 도입 가능하다.
하지만 제네릭 타입은 객체를 생성하는 시점에 타입이 정해지기 때문에 static 메서드에 도입 불가능하다.
static T staticMethod(T t) {} // 제네릭 타입의 T 사용 불가능
타입 매개변수 제한
public class GenericMethod {
public static <T extends Number> T numberMethod(T t) {
System.out.println("bound print: " + t);
return t;
}
}
Integer integerValue = GenericMethod.<Integer>numberMethod(10); // bound print: 10
Double doubleValue = GenericMethod.<Double>numberMethod(20.0); // bound print: 20.0
// GenericMethod.numberMethod("Hello"); // 컴파일 오류
- 제네릭 메서드는
<T extends Number> T numberMethod(T t)
와 같이 타입 매개변수를 제한할 수 있다.
타입 추론
Integer i = 10;
Integer result = GenericMethod.genericMethod(i);
Integer integerValue = GenericMethod.numberMethod(10);
Double doubleValue = GenericMethod.numberMethod(20.0);
- 자바 컴파일러는 메서드에 전달되는 인자의 타입을 추론할 수 있다.
- 그래서 타입 인자 전달을 생략해도 된다.
제네릭 타입과 제네릭 메서드의 우선순위
public class ComplexBox<T extends Animal> {
private T animal;
public void set(T animal) {
this.animal = animal;
}
public <T> T printAndReturn(T t) {
System.out.println("animal.className: " + animal.getClass().getName());
System.out.println("t.className: " + t.getClass().getName());
return t;
}
}
- 제네릭 타입인
ComplexBox<T extends Animal>
와 제네릭 메서드인<T> T printAndReturn(T t)
의 타입 매개변수를 둘 다T
로 설정해보자. printAndReturn()
내부에서 바로t.getName()
는 호출 불가다.- 해당 제네릭 메서드의 타입 매개변수는 상한이 존재하지 않아,
T
가Object
로 취급되기 때문이다.
- 해당 제네릭 메서드의 타입 매개변수는 상한이 존재하지 않아,
Dog dog = new Dog("멍멍이", 100);
Cat cat = new Cat("냐옹이", 50);
ComplexBox<Dog> hospital = new ComplexBox<>();
hospital.set(dog);
Cat returnCat = hospital.printAndReturn(cat);
System.out.println("returnCat = " + returnCat);
실행 결과
animal.className: generic.animal.Dog
t.className: generic.animal.Cat
returnCat = Animal{name='냐옹이', size=50}
animal.className
은ComplexBox
클래스의 제네릭 타입T
를 사용하기 때문에Dog
클래스의 타입을 출력한다.t.className
은 메서드 레벨의 제네릭 타입<T>
를 따르며, 이 경우 메서드 호출 시 전달된 매개변수인cat
객체의 타입 (Cat
)을 출력한다.- 즉, 제네릭 타입보다 제네릭 메서드가 높은 우선순위를 가진다.
- 타입 매개변수의 이름이 겹치는 건 피하는 게 좋다.
김영한의 실전 자바 중급 2편을 참고하였습니다