Освой программирование играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Обобщения - это параметризованные типы. С их помощью можно объявлять классы, интерфейсы и методы, где тип данных указан в виде параметра. Обобщения добавили в язык безопасность типов, немного на тему говорится в статье про ArrayList.
Рассмотрим пример с обобщением.
class Gen<T> {
T ob; // объявление объекта типа T
// Передать конструктору ссылку на объект типа T
Gen(T o) {
ob = o;
}
// Вернуть ob
T getob() {
return ob;
}
// Показать тип T
void showType() {
System.out.println("Тип T: " + ob.getClass().getName());
}
}
// Код для кнопки
// Работаем с обобщённым классом
// Создаём Gen-ссылку для Integer
Gen<Integer> iOb;
// Создаём объект Gen<Integer>
iOb = new Gen<Integer>(77);
// Показать тип данных, используемый iOb
iOb.showType();
// Получить значение iOb
int value = iOb.getob();
System.out.println("Значение " + value);
// Создадим объект Gen для String
Gen<String> strOb = new Gen<String>("Обобщённый текст");
// Показать тип данных, используемый strOb
strOb.showType();
// Получить значение strOb
String str = strOb.getob();
System.out.println("Значение: " + str);
В результате мы получим:
Типом T является java.lang.Integer Значение: 77 Типом T является java.lang.String Значение: Обобщённый текст
Изучим код. Мы объявили класс в следующей форме:
class Gen<T> {
}
В угловых скобках используется T - имя параметра типа. Это имя используется в качестве заполнителя, куда будет подставлено имя реального типа, переданного классу Gen при создании реальных типов. То есть параметр типа T применяется в классе всякий раз, когда требуется параметр типа. Угловые скобки указывают, что параметр может быть обобщён. Сам класс при этом называется обобщённым классом или параметризованным типом.
Далее тип T используется для объявления объекта по имени ob:
T ob; // объявляет объект типа T
Вместо T подставится реальный тип, который будет указан при создании объекта класса Gen. Объект ob будет объектом типа, переданного в параметре типа T. Если в параметре T передать тип String, то экземпляр ob будет иметь тип String.
Рассмотрим конструктор Gen().
Get(T o) {
ob = o;
}
Параметр o имеет тип T. Это значит, что реальный тип параметра o определяется типом, переданным параметром типа T при создании объекта класса Gen.
Параметр типа T также может быть использован для указания типа возвращаемого значения метода:
T getob() {
return ob;
}
В именах переменных типа принято использовать заглавные буквы. Обычно для коллекций используется буква E, буквами K и V - типы ключей и значение (Key/Value), а буквой T (и при необходимости буквы S и U) - любой тип.
Как использовать обобщённый класс. Можно создать версию класса Gen для целых чисел:
Gen<Integer> iOb;
В угловых скобках указан тип Integer, т.е. это аргумент типа, который передаётся в параметре типа T класса Gen. Фактически мы создаём версию класса Gen, в которой все ссылки на тип T становятся ссылками на тип Integer.
Когда мы присваиваем ссылку на экземпляр, то угловые скобки также требуется указывать.
iOb = new Gen<Integer>(77);
Полная версия записи может быть такой:
iOb = new Gen<Integer>(new Integer(88));
Но такая запись избыточна, так как можно использовать автоматическую упаковку значения 77 в нужный формат.
Аналогично, можно было бы использовать вариант без автоупаковки для получения значения:
int value = iOb.getob().intValue(); // избыточный код
Обобщения работают только с объектами. Поэтому нельзя использовать в качестве параметра элементарные типы вроде int или char:
Gen<int> intOb = new Gen<int>(44); // нельзя!
Хотя объекты iOb и strOb имеют тип Gen<T>, они являются ссылками на разные типы и их сравнивать нельзя.
iOB = strOb; // нельзя!
Использование обобщений автоматически гарантирует безопасность типов во всех операциях, где они задействованы. Это очень мощный механизм, широко используемый в Java.
Можно указать два и более параметров типа через запятую.
class TwoGen<T, V> {
T ob1;
V ob2;
// Передать конструктору ссылки на объекты двух типов
TwoGen(T o1, V o2) {
ob1 = o1;
ob2 = o2;
}
void showTypes() {
System.out.println("Тип T: " + ob1.getClass().getName());
System.out.println("Тип V: " + ob2.getClass().getName());
}
T getob1() {
return ob1;
}
V getob2() {
return ob2;
}
// Используем созданный класс
TwoGen<Integer, String> twogenObj = new TwoGen<Integer, String>(77, "Обобщённый текст");
// Узнаем типы
twogenObj.showTypes();
// Узнаем значения
int value = twogenObj.getob1();
System.out.println("Значение: " + value);
String str = twogenObj.getob2();
System.out.println("Значение: " + str);
}
Никто не запрещает создавать и методы с параметрами и возвращаемыми значениями в виде обобщений.
public static <T> T getSomething(T... a){
return a;
}
Шаблон аргументов указывается символом ? и представляет собой неизвестный тип.
boolean sameAvg(Stats<?> ob) ...
По сути, вопрос заменяет Object и мы можем использовать любой класс, который в любом случае будет происходить от Object.
Мы можем ограничить диапазон объектов, указав суперкласс.
public void addItems(ArrayList<? extends Animal> list)
В этом случае можно использовать классы, которые могут быть наследниками Animal - Dog, Cat. А String или Integer вы уже не сможете использовать.
Пример обобщённого метода
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
Переменная типа вводится после модификаторов и перед возвращаемым типом.
Отдельно стоит упомянуть новинку JDK 7, позволяющую сократить код.
MyClass<Integer, String> mcObj = new MyClass<Integer, String>(33, "Meow"); // старый способ в JDK 6
MyClass<Integer, String> mcObj = new MyClass<>(33, "Meow"); // новый способ в JDK 7
Во второй строке используются только угловые скобки, без указания типов.
Помните, что нельзя создать экземпляр типа параметра.
class Gen {
T ob;
Gen() {
ob = new T(); // Недопустимо
}
}
Под Android вам часто придётся использовать списочный массив, который использует обобщещние.
ArrayList<String> catNames = new ArrayList<String>();
В Java 7, которую можно использовать в проектах для KitKat и выше, существует укороченная запись, когда справа не указывается тип (см. выше). Компилятор сам догадается по левой части выражения.
ArrayList<String> catNames = new ArrayList<>();