Одной из важнейших причин для появления параметризации стало создание классов контейнеров (см. главу 11). Контейнер предназначен для хранения объектов, используемых в программе. В принципе это описание подойдет и для массива, но контейнеры обычно обладают большей гибкостью и отличаются по своим характеристикам от простых массивов. Необходимость хранения групп объектов возникает едва ли не в каждой программе, поэтому контейнеры составляют одну из самых часто используемых библиотек классов.
Рассмотрим класс для хранения одного объекта. Конечно, в этом классе можно указать точный тип объекта:
//: generics/Holder1.java
class Automobile {}
public class Holder1 {
private Automobile a;
public Holder1(Automobile a) { this.a = a; }
Automobile get() { return a; }
}
Однако такой «контейнер» получается не слишком универсальным — он не может использоваться только для одного типа. Конечно, было бы неудобно создавать новый класс для каждого типа, который нам встретится в программе. До выхода Java SE5 можно было бы хранить в классе Object:
//: generics/Holder2.java
public class Holder2 {
private Object a;
public Holder2(Object a) { this.a = a; }
public void set(Object a) { this.a = a; }
public Object get() { return a; }
public static void main(String[] args) {
Holder2 h2 = new Holder2(new Automobile());
Automobile a = (Automobile)h2.get();
h2.set("Not an Automobile");
String s = (String)h2.get();
h2.set(1); // Автоматически упаковывается в Integer
Integer x = (Integer)h2.get();
}
}
Теперь класс Holder2 может хранить все, что угодно, — в приведенном примере один объект Holder2 используется для хранения трех разных типов данных. В некоторых случаях бывает нужно, чтобы контейнер мог хранить объекты разных типов, но чаще контейнер предназначается для одного типа объектов. Одна из главных причин для применения параметризованных типов заключается именно в этом: вы можете указать, какой тип должен храниться в контейнере, и заданный тип будет поддерживаться комплиятором. Итак, вместоObject в определении класса было бы удобнее использовать некий условный заменитель, чтобы отложить выбор до более позднего момента. Для этого после имени класса в угловых скобках указывается параметр типа, который при использовании заменяется фактическим типом. В нашем примере это будет выглядеть так (Т — параметр типа):
//: generics/Holder3.java
public class Holder3<T> {
private T a;
public Holder3(T a) { this.a = a; }
public void set(T a) { this.a = a; }
public T get() { return a; }
public static void main(String[] args) {
Holder3<Automobile> h3 =
new Holder3<Automobile>(new Automobile());
Automobile a = h3.get(); // Преобразование не требуется
// h3.set("Not an Automobile");// Ошибка
// h3.set(1); // Ошибка
}
}
При создании Holder3 необходимо указать тип объектов, хранящихся в контейнере, в угловых скобках, как в main(). В дальнейшем в контейнер можно будет помещать объекты только этого типа (или производного, так как принцип заменяемости работает и для параметризованных типов). А при извлечении вы автоматически получаете объект нужного типа. В этом заключается основная идея параметризованных типов Java: вы указываете, какой тип должен использоваться, а механизм параметризации берет на себя все подробности.