Сайт разработчика Александр Климова

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Обобщения (Generic)

Обобщения - это параметризованные типы. С их помощью можно объявлять классы, интерфейсы и методы, где тип данных указан в виде параметра. Обобщения добавили в язык безопасность типов.

Рассмотрим пример с обобщением.


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);
}

Шаблоны аргументов

Шаблон аргументов указывается символом ? и представляет собой неизвестный тип.


boolean sameAvg(Stats<?> ob) ...

Также бывают обобщённые методы, конструкторы, интерфейсы. Тема обобщений очень обширна и требует отдельного изучения.

Пример обобщённого метода


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> catnamesList = new ArrayList<String>();

В Java 7, которую можно использовать в проектах для KitKat и выше, существует укороченная запись, когда справа не указывается тип (см. выше). Компилятор сам догадается по левой части выражения.


ArrayList<String> catnamesList = new ArrayList<>();
Реклама