앞서 배운 제네릭을 도입해서 코드 재사용성은 늘리고, 타입 안정성 문제를 해결해보자.
T
에 어떤 값이 들어올지 예측할 수 없다.T
에는 타입 인자로 Integer, Dog, Object 등의 타입이 들어올 수 있기 때문에 예측이 불가능하다. T
를 어떤 타입이든 받을 수 있는 모든 객체의 최종 부모인 Object
타입으로 가정한다.최소한
Animal
이나 그 자식을 타입 인자로 제한하려면 어떻게 해야할까?
<T extends Animal>
T
를 Animal
과 그 자식(Dog, Cat)만 받을 수 있도록 제한T
의 상한이 Animal
이 된다.이제 자바 컴파일러는
T
에 입력될 수 있는 값의 범위를 예측가능 !!
= Animal이 제공하는 메서드 사용 가능 !
문제 해결
1. 동물과 전혀 관계없는 타입 인자를 컴파일 시점에 막는다.
2. 제네릭 크래스 안에서Animal
의 기능을 사용할 수 있다.
3. 타입 안정성 문제 해결
4. 제네릭에서 타입 변수를 사용했을 때 어떤 타입이든 허용했던 문제
5. Object의 기능만 사용할 수 있었던 문제
제네릭에 타입 매개변수 상한을 사용해서
1. 타입 안정성을 지키면서
2. 상위 타입의 원하는 기능까지 사용
3. 코드 재사용과 타입 안정성이라는 두 마리 토끼를 동시에 잡았다 !
특정 메서드에 제네릭을 적용하는 방법
제네릭 타입
GenericClass<T>
new GenericClass<String>
제네릭 메서드
<T> T GenericMethod(T t)
GenericMethod.<Integer>genericMethod(i)
<T>
와 같이 타입 매개변수를 적어준다.<Integer>
와 같이 타입을 정하고 호출한다.제네릭 메서드의 핵심은
메서드를 호출하는 시점에 타입 인자를 전달해서 타입을 지정하는 것.
= 타입을 지정하면서 메서드를 호출한다.
제네릭 메서드는 인스턴스 메서드와 static 메서드 모두 적용할 수 있다.
class Box<T> { //제네릭 타입
static <V> V staticMethod2(V t) {} //static 메서드에 제네릭 메서드 도입
<Z> Z instanceMethod2(Z z) {} //인스턴스 메서드에 제네릭 메서드 도입 가능
}
class Box<T> {
T instanceMethod(T t) {} //가능
static T staticMethod1(T t) {} //제네릭 타입의 T 사용 불가능
}
제네릭 타입은 static 메서드에 타입 매개변수를 사용할 수 없다.
제네릭 타입은 객체를 생성하는 시점에 타입이 정해지는데, static 메서드는 인스턴스 단위가 아니라 클래스 단위로 작동하기 때문에 제네릭 타입과는 무관하다.
따라서 static 메서드에 제네릭을 도입하려면 제네릭 메서드를 사용해야 한다.
헷갈리는 부분
T instanceMethod1(T t) {
return t;
}
<T> T instanceMethod2(T t) {
return t;
}
이 두 메서드의 차이점이 뭘까?
T instanceMethod1(T t) {...}
메서드는 "클래스"의 제네릭 타입과 파라미터 T를 사용하고 있다.T
라는 제네릭 타입 파라미터를 정의하고 있어야만 사용할 수 있다.class MyClass<T> {
T instanceMethod1(T t) {
return t;
}
}
MyClass<String> myClass = new MyClass<>();
String result = myClass.instanceMethod1("Hello"); // T는 String으로 지정됨
이 경우, T
는 클래스가 인스턴스화될 때 결정되므로, 메서드 instanceMethod1은 클래스의 타입 파라미터에 종속적이다.
<T> T instanceMethod2(T t) {...}
메서드는 자체적으로 메서드 내부에서 제네릭 타입 T
를 선언하고 있는 것이다.class MyClass {
<T> T instanceMethod2(T t) {
return t;
}
}
MyClass myClass = new MyClass();
String result = myClass.instanceMethod2("Hello"); // T는 String으로 지정됨
Integer number = myClass.instanceMethod2(123); // T는 Integer로 지정됨
이 메서드는 매 호출마다 다른 타입으로 사용될 수 있고, 클래스의 타입 파라미터의 영향을 받지 않는다.
instanceMethod1(T t) {...}
: 클래스의 제네릭 타입에 의존하여, 클래스가 어떤 타입으로 선언되었느냐에 따라T
가 결정된다.
<T t>instanceMethod2(T t)
: 클래스의 제네릭 타입과 무관하게 메서드 자체가 제네릭을 선언하여, 호출할 때마다 타입을 개별적으로 지정할 수 있다.