<T extends 최상위타입>) ]숫자를 연산하는 제네릭 메소드는 매개값으로 Number 타입 또는 하위 클래스 타입 (Byte, Short, Integer, Long, Double)의 인스턴스만 가져야 한다.
이와 같이 제한된 타입 파라미터(bounded type paramter)를 사용하여 타입을 제한해야 하는 경우가 있다.
타입 파라미터 뒤에
extends키워드를 붙이고 상위 타입을 명시한다.public <T extends 상위 타입> 리턴타입 메소드(매개변수, ...) { ... }
implements를 사용하지는 않고, 똑같이 extends를 사용해야 한다.{} 안에서 타입 파라미터의 변수로 사용 가능한 것은 상위 타입의 멤버(필드, 메소드)로 제한된다. 하위 타입에만 있는 필드와 메소드는 사용할 수 없다.compare()public <T extends Number> int compare(T t1, T t2) {
double v1 = t1.doubleValue(); //Number의 doubleValue() 메소드 사용
double v2 = t2.doubleValue(); //Number의 doubleValue() 메소드 사용
return Double.compare(v1, v2);
}
doubleValue() 메소드는 Number 클래스에 정의되어 있는 메소드로 숫자를 double 타입으로 변환한다. Double.compare() 메소드는 첫 번째 매개값이 작으면 -1을, 같으면 0을, 크면 1을 리턴한다.Util (제네릭 메소드)public class Util {
public static <T extends Number> int compare(T t1, T t2) {
double v1 = t1.doubleValue(); //Number의 doubleValue() 메소드 사용
double v2 = t2.doubleValue(); //Number의 doubleValue() 메소드 사용
return Double.compare(v1, v2);
}
}
BoundedTypeParameterExample (제네릭 메소드 호출)public class BoundedTypeParameterExample {
public static void main(String[] args) {
//String str = Util.compare("a", "b"); (x) -> String은 Number 타입이 아니다
int result1 = Util.compare(10, 20); //int -> Integer (자동 Boxing)
System.out.println(result1);
int result2 = Util.compare(4.5, 3); //double -> Double (자동 Boxing)
System.out.println(result2);
}
}
<?>, <? extends ...>, <? super ... >) ]와일드카드(
wildcard)란 이름에 제한을 두지 않음을 표현하는데 사용하는 기호를 의미하는데, 자바의 제네릭에서는?기호를 사용하여 와일드카드를 사용할 수 있다.
제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드카드를 다음과 같이 세 가지 형태로 사용할 수 있다.
<?> - Unbounded Wildcards (제한 없음)<?> //타입 변수에 모든 타입을 사용할 수 있다.
모든 클래스나 인터페이스 타입이 올 수 있다.
<? extends T> - Upper Bounded Wildcards (상위 클래스 제한)<? extends T> //T 타입과 T 타입을 상속받는 자손 클래스 타입만을 사용할 수 있다.
T와 그 자손 클래스 타입을 구현한 객체들만 올 수 있다.
<? super T> - Lower Bounded Wildcards (하위 클래스 제한)<? super T> //T 타입과 T 타입이 상속받은 조상 클래스 타입만을 사용할 수 있다.
T와 그 조상 클래스 타입을 구현한 객체들만 올 수 있다.
CourseCourse는 과정 클래스로 과정 이름과 수강생을 저장할 수 있는 배열을 가지고 있다. 타입 파라미터 T가 적용된 곳은 수강생 타입 부분이다.
Course (제네릭 타입)public class Course<T> {
private String name;
private T[] students;
public Course(String name, int capacity) {
this.name = name;
students = (T[]) (new Object[capacity]); //타입 파라미터로 배열을 생성하려면 new T[n] 형태로 배열을 생성할 수 없고
//(T[]) (new Object[n])으로 생성해야 한다.
}
public String getName() { return name; }
public T[] getStudents() { return students; }
public void add(T t) { //배열에 비어있는 부분을 찾아서 수강생을 추가하는 메소드
for (int i = 0; i < students.length; i++) {
if (students[i] == null) {
students[i] = t;
break;
}
}
}
}
수강생이 될 수 있는 타입은 다음 4가지 클래스라고 가정하자. Person의 하위 클래스로 Worker와 Student가 있고, Student의 하위 클래스로 HighStudent가 있다.

Course<?>Person, Worker, Student, HighStudent)이 될 수 있다.Course<? extends Student>Student와 HighStudent만 될 수 있다.Course<? super Worker>Worker와 Person만 될 수 있다.제네릭 타입도 다른 타입과 마찬가지로 부모 클래스가 될 수 있다.
다음은 Product<T, M> 제네릭 타입을 상속해서 ChildProduct<T, M> 타입을 정의한다.
public class ChildProduct<T, M> extends Product<T, M> { ... }
자식 제네릭 타입은 추가적으로 타입 파라미터를 가질 수 있다.
다음은 세 가지 타입 파라미터를 가진 자식 제네릭 타입을 선언한 것이다.
public class ChildProduct<T, M, C> extends Product<T, M> { ... }
Product (부모 제네릭 클래스)public class Product<T, M> {
private T kind;
private M model;
public T getKind() { return this.kind; }
public M getModel() { return this.model; }
public void setKind(T kind) { this.kind = kind; }
public void setModel(M model) { this.model = model; }
}
}
class Tv { }
ChildParent (자식 제네릭 클래스)public class ChildProduct<T, M, C> extends Product<T, M> {
private C company;
public C getCompany() { return this.company; }
public void setCompany(C company) { this.company = company; }
}
제네릭 인터페이스를 구현한 클래스도 제네릭 타입이 되는데, 다음과 같이 제네릭 인터페이스가 있다고 가정해보자.
Storage (제네릭 인터페이스)public interface Storage<T> {
public void add(T item, int index);
public T get(int index);
}
제네릭 인터페이스인 Storage<T> 타입을 구현한 StorageImpl 클래스도 제네릭 타입이어야 한다.
StorageImple (제네릭 구현 클래스)public class StorageImpl<T> implements Storage<T> {
private T[] array;
public StorageImpl(int capacity) {
this.array = (T[]) (new Object[capacity]); //타입 파라미터로 배열을 생성하려면 new T[n] 형태로 생성할 수 없고
//(T[n])(new Object[n])으로 생성해야 한다.
}
@Override
public void add(T item, int index) {
array[index] = item;
}
@Override
public T get(int index) {
return array[index];
}
}
다음 ChildProductAndStorageExample은 ChildProduct<T, M, C>와 StorageImpl<T> 클래스의 사용 방법을 보여준다.
public class ChildProductAndStorageExample {
public static void main(String[] args) {
ChildProduct<Tv, String, String> product = new ChildProduct<>();
product.setKind(new Tv());
product.setModel("SmartTV");
product.setCompany("Samsung");
Storage<Tv> storage = new Storage<tv>(100);
storage.add(new Tv(), 0);
Tv tv = storage.get(0);
}
}