<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
와 그 조상 클래스 타입을 구현한 객체들만 올 수 있다.
Course
Course
는 과정 클래스로 과정 이름과 수강생을 저장할 수 있는 배열을 가지고 있다. 타입 파라미터 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);
}
}