Жёлтым я пометил новые фрагменты.
Добавлена дерево запретов.
Исправлено больше ошибок.
Дженерики. Часть 3.
Дзюба Влад. ИП-42
Болше о шаблонах аргументов Наследование в дженериках
Замечание: Любой код, который будет показан лучше всего вручную написать и проверить правильность утверждений (ведь это круто – найти ошибку в лекции ). Также это помогает забить в пальцы и глаза синтаксис языка.
Рассмотрим такой код:
Этот код вызывает функцию someMethod два раза: первый раз с Integer, второй с Double, хотя при объявлении задано, что функция принимает агрумент типа Number. Это возможно благодаря тому, что Integer и Double являются подклассами Number.
Теперь перейдём к нашей теме дженериков:
Во-первых, какие елементы будут в списках после циклов?
Ну а теперь, обратим внимание на то, что компилятор выдаст ошибку при вызове someMethod в обоих случаях. На первый взгляд кажется, что, как и в предыдущем примере передавать вместо ArrayList<Number> можно передавать ArrayList<Integer> и ArrayList<Double>, но это не так!
Хотя Integer иDouble наследуются от Number, ArrayList<Integer>, ArrayList<Double> и ArrayList<Number> наследуются от Object.
Поэтому нужно использовать «?» для написания функции:
Посмотрите: мы изменили только тип аргумента функции и всё заработало. Теперь можно использовать любой подкласс Number для этой функции. В этом и состоит сила дженериков.
Для тех, кто подумал о том, что генерируется в списках: сразу рассказывать ответ неинтересно.
Также для создания дженериков я использовал алмазный синтаксис (diamond syntax), который позволяет не указывать параметризуемый тип второй раз
ArrayList<Integer> = new ArrayList<>();
Получается, что Integer и Double являются подклассами ? extends Number. Но также они являются подклассами ? extends Integer и ? extends Double соответственно. А ? extends Number является подклассом ? extends Object. Всё это можно представить в такой диагарамме.
Такую же диаграмму можно сделать и для классов, ограниченных словом super ? super Type используется для надклассов Type (Integer, Number и Object подходят под шаблон ? super Integer).
Это всё описывает наследование в пределах одного класса, но с разнымим параметризоваными типами.
Но допустим перед нами стоит задача: реализовать функцию, которая в любой коллекции чисел (Collection<Number>) ищет число 42. Почему 42? Потому что для этого и создают компьютеры . (http://lurkmore.to/42).
Сразу встаёт вопрос: как работать с любой коллекцией? Ведь коллекцией может быть, как и ArrayList, так и HashSet, так и LinkedList.
Это позволит сделать наследенование дженериков:
Наследование дженериков с одним параметризованым типом есть только, если этот тип одинаковый. То есть ниList<Integer>, ни List<String> не могут наследовать List<Number>.
Также будем использовать Iterator – это класс, позволяющий перебрать все элементы коллекции.
Вот пример функции, которая возвращает есть ли в коллекции 42.
(Для того, чтоб код скомпилировался нужно подключить java.util.Iterator, java.util.Collection)
Iterator itr = col.iterator();
Создаёт новый итератор и присвает ему итератор коллекции, передаваемой в функцию.
while (itr.hasNext())
Запускает цикл, который выполняется, пока в коллекции есть ещё необработаные элементы.
Number elem = (Number) itr.next();
Получает следующий элемент в коллекции. Так как тип коллекции –Collection<? Extends Number>, то мы по-любому имеем дело с Number и никаких ошибок, связаных с кастом (приведением типов) не будет.
Ну и находим числа, дробное представление которых равно 42.0. Что было бы, если бы внутри if было бы такое выражение: (elem.intValue() == 42)?
Функция готова, но теперь нужно её протестировать. Для того, чтобы видеть, что в данный момент находится в коллекции напишем функцию print, которая будет выводить все элементы любой коллекции.
(Для того, чтобы код скомпилился нужно вначале подключить java.util.Iteartor, java.util.Collection)
Во первых, функция принимает любую коллекцию: как Collection<Number>, так и LinkedHashSet<String>, так как тип аргумента Collection<?>
Интересно выражение:
col.getClass().getSimpleName()
Так мы не знаем заранее класс, то нельзя выводить заранее заданую строку. Это выражение получает название класса аргумента.
Также для удобства создадим функцию, которая будет заносить какие-то элементы в коллекцию.
(Для того, чтобы код скомпилился нужно вначале подключить java.util.Collection)
Аргументом может быть коллекция любого надкласса Integer, потому что числа, которые мы заносим Integer и могут содержаться в любой такой коллекции.
Теперь работа с нашими функциями:
(Для того, чтобы код скомпилился нужно вначале подключить java.util.ArrayList, java.util.HashSet)
Для примера используем ArrayList<Integer> и HashSet<Number>. Для каждого добавляем элементы, выводим и анализируем есть ли 42 внутри. Каким должно быть возвращаемое из isWorldSolutionHere() значение, чтобы выводилось “not “?
В итоге в этом примере используется наследование дженерикиов, шаблоны «?» и методы Collection(iterator(), add()) и Object(toString()) для того, чтобы организовать методы, которые будут работать с любыми подходящими коллекциями. Внимание: это очень круто!
Также одни дженерики могут иметь подклассом дженерики с большим количеством пааметризованых типов, если подкласс расширяет последовательность типов.