Заметки о generics


Обобщённое программирование — это такой подход к описанию данных и алгоритмов, который позволяет их использовать с различными типами данных без изменения их описания. В Java, начиная с версии J2SE 5.0, добавлены средства обобщённого программирования, синтаксически основанные на C++. Ниже будут рассматриваться generics (дженерики) или <<контейнеры типа T>> — подмножество обобщённого программирования.

Существенное преимущество при использовании дженериков состоит в том, что некоторые ошибки, связанные с приведением типов могут быть отловлены на момент компиляции. Ошибка компиляции «лучше» ошибки времени выполнения, т.к. чисто теоретически скомпилированный код с ошибкой может попасть туда, куда ему лучше бы и не попадать. Это очевидное достоинство дженериков.

После имени класса в угловых скобках "<" и ">" указано имя типа "Т", которое может использоваться внутри класса. Фактически Т – это тип, который должен быть определён позже (при создании объекта класса).

При создании дженерик-классов мы не ограничены одним лишь типом (Т) – их может быть несколько.

Алмазный синтаксис (Diamond syntax)

Немного лениво каждый раз заполнять типы и при этом можно ошибиться. Чтобы упростить жизнь программистам в Java 7 был введён алмазный синтаксис (diamond syntax), в котором можно опустить параметры типа. Т.е. можно предоставить компилятору определение типов при создании объекта. Вид упрощённого объявления:

Pair pair = new Pair<>(6, " Apr");

Универсальные методы (Generic methods)

По аналогии с универсальными классами (дженерик-классами), можно создавать универсальные методы (дженерик-методы), то есть методы, которые принимают общие типы параметров. Универсальные методы не надо путать с методами в дженерик-классе. Универсальные методы удобны, когда одна и та же функциональность должна применяться к различным типам. (Например, есть многочисленные общие методы в классе java.util.Collections.)

import java.util.ArrayList; 
import java.util.List; 
class Utilities { 
    public static <T> void fill(List<T> list, T val) { 
        for (int i = 0; i < list.size(); i++) 
            list.set(i, val); 
    } 
}
class Test { 
    public static void main(String[] args) { 
        List<Integer> intList = new ArrayList<Integer>(); 
        intList.add(1); 
        intList.add(2);
    } 
}

Wildcards (Маски)

Сейчас будут рассмотрены Wildcard Parameters (wildcards). Этот термин в разных источниках переводится по-разному: метасимвольные аргументы, подстановочные символы, групповые символы, шаблоны, маски и т.д.

Вот такая строка кода не скомпилируется: List intList = new ArrayList();

Но есть возможность похожей реализации: List<?> intList = new ArrayList();

Под маской мы будем понимать вот эту штуку – " <?> ".

Пример кода использующего маску и пригодного к компиляции:

class Test { 
    static void printList(List<?> list) { 
        for (Object l : list) 
            System.out.println("{" + l + "}"); 
    } 
    public static void main(String[] args) { 
        List<Integer> list = new ArrayList<>(); 
        list.add(10); 
        list.add(100); 
        printList(list); 
        List<String> strList = new ArrayList<>(); 
        strList.add("10"); 
        strList.add("100"); 
        printList(strList); 
    } 
}

Однако вот это не скомпилируется:

List<?> intList = new ArrayList<Integer>(); 
intList.add(new Integer(10)); 
/* intList.add(new Float(10.0f)); даже с закомментированной последней строкой не скомпилируется */

Почему не компилируется? При использовании маски мы сообщаем компилятору, чтобы он игнорировал информацию о типе, т.е. <?> - неизвестный тип. При каждой попытке передачи аргументов дженерик-типа компилятор Java пытается определить тип переданного аргумента. Однако теперь мы используем метод add () для вставки элемента в список. При использовании маски мы не знаем, какого типа аргумент может быть передан. Тут вновь видна возможность ошибки, т.к. если бы добавление было возможно, то мы могли бы попытаться вставить в наш список, предназначенный для чисел, строковое значение. Во избежание этой проблемы, компилятор не позволяет вызывать методы, которые могут добавить невалидный тип - например, добавить значение типа Float, с которым мы потом попробуем работать как с Integer (или String - по маске не определишь точно). Тем не менее есть возможность получить доступ к информации, хранящейся в объекте, с использованием маски, как это было показано выше.

Маске может быть задано ограничение:

List<? extends Number> numList = new ArrayList<Integer>(); 
numList = new ArrayList<Double>();

То, что было описано выше называется ограниченными масками (Bounded wildcards). Применение таких конструкций может быть весьма красивым и полезным. Допустим нам необходимо посчитать сумму чисел различного типа, которые хранятся в одном списке:

public static Double sum(List<? extends Number> numList) { 
    Double result = 0.0; 
    for (Number num : numList) { 
        result += num.doubleValue(); 
    } 
    return result; 
}

В завершение этой темы добавлю, что аналогично ключевому слову extends в подобного рода выражениях может использоваться ключевое слово super - "<? super Integer> ". Выражение <? super X> означает, что вы можете использовать любой базовый тип (класс или интерфейс) типа Х, а также и сам тип Х. Пара строк, которые нормально скомпилируются:

List<? super Integer> intList = new ArrayList<Integer>(); 
System.out.println("The intList is: " + intList);

results matching ""

    No results matching ""