Sdscompany.ru

Компьютерный журнал
3 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Параметризованные методы java

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

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

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

В результате мы получим:

Изучим код. Мы объявили класс в следующей форме:

В угловых скобках используется T — имя параметра типа. Это имя используется в качестве заполнителя, куда будет подставлено имя реального типа, переданного классу Gen при создании реальных типов. То есть параметр типа T применяется в классе всякий раз, когда требуется параметр типа. Угловые скобки указывают, что параметр может быть обобщён. Сам класс при этом называется обобщённым классом или параметризованным типом.

Далее тип T используется для объявления объекта по имени ob:

Вместо T подставится реальный тип, который будет указан при создании объекта класса Gen. Объект ob будет объектом типа, переданного в параметре типа T. Если в параметре T передать тип String, то экземпляр ob будет иметь тип String.

Рассмотрим конструктор Gen().

Параметр o имеет тип T. Это значит, что реальный тип параметра o определяется типом, переданным параметром типа T при создании объекта класса Gen.

Параметр типа T также может быть использован для указания типа возвращаемого значения метода:

В именах переменных типа принято использовать заглавные буквы. Обычно для коллекций используется буква E, буквами K и V — типы ключей и значение (Key/Value), а буквой T (и при необходимости буквы S и U) — любой тип.

Как использовать обобщённый класс. Можно создать версию класса Gen для целых чисел:

В угловых скобках указан тип Integer, т.е. это аргумент типа, который передаётся в параметре типа T класса Gen. Фактически мы создаём версию класса Gen, в которой все ссылки на тип T становятся ссылками на тип Integer.

Когда мы присваиваем ссылку на экземпляр, то угловые скобки также требуется указывать.

Полная версия записи может быть такой:

Но такая запись избыточна, так как можно использовать автоматическую упаковку значения 77 в нужный формат.

Аналогично, можно было бы использовать вариант без автоупаковки для получения значения:

Обобщения работают только с объектами. Поэтому нельзя использовать в качестве параметра элементарные типы вроде int или char:

Хотя объекты iOb и strOb имеют тип Gen , они являются ссылками на разные типы и их сравнивать нельзя.

Использование обобщений автоматически гарантирует безопасность типов во всех операциях, где они задействованы. Это очень мощный механизм, широко используемый в Java.

Обобщённый класс с двумя параметрами

Можно указать два и более параметров типа через запятую.

Обобщённые методы

Никто не запрещает создавать и методы с параметрами и возвращаемыми значениями в виде обобщений.

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

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

По сути, вопрос заменяет Object и мы можем использовать любой класс, который в любом случае будет происходить от Object.

Мы можем ограничить диапазон объектов, указав суперкласс.

В этом случае можно использовать классы, которые могут быть наследниками AnimalDog, Cat. А String или Integer вы уже не сможете использовать.

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

Переменная типа вводится после модификаторов и перед возвращаемым типом.

Отдельно стоит упомянуть новинку JDK 7, позволяющую сократить код.

Во второй строке используются только угловые скобки, без указания типов.

Помните, что нельзя создать экземпляр типа параметра.

Под Android вам часто придётся использовать списочный массив, который использует обобщещние.

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

Обобщение типа данных, generic

Начиная с Java 5 появились новые возможности для программирования, к которым следует отнести поддержку обобщенного программирования, названная в Java generic. Эта возможность позволяет создавать более статически типизированный код. Соответственно, программы становятся более надежными и проще в отладке.

generic являются аналогией с конструкцией «Шаблонов»(template) в С++. Ожидалось, что дженерики Java будут похожи на шаблоны C++. На деле оказалось, что различия между generic’ами Java и шаблонами С++ довольно велики. В основном generic в Java получился проще, чем их C++-аналог, однако он не является упрощенной версией шаблонов C++ и имеют ряд значительных отличий. Так, в языке появилось несколько новых концепций, касающихся generic’ов – это маски и ограничения.

Рассмотрим 2 примера без использования и с использованием generic. Пример без использования generic с приведением типа (java casting):

В данном примере программист знает тип данных, размещамый в List’e. Тем не менее, необходимо обратить особое внимание на приведение типа («java casting»). Компилятор может лишь гарантировать, что метод next() вернёт Object, но чтобы обеспечить присвоение переменной типа Integer правильным и безопасным, требуется java casting. Приведение типа не исключает возможности появления ошибки «Runtime Error» из-за невнимательности разработчика.

Возникает вопрос: «Как с этим бороться? Каким образом зарезервировать List для определенного типа данных?». Данную проблему решают дженерики generic. В следующем примере используется generic без приведения типов.

Обратите внимание на объявления типа для переменной integerList, которое указывает на то, что это не просто произвольный List, а List . Кроме этого теперь java casting выполняется автоматически.

В примере вместо приведения к Integer, был определен тип списка List. В этом заключается существенное отличие, и компилятор может проверить данный тип на корректность во время компиляции во всем коде. Эффект от generic особенно проявляется в крупных проектах: он улучшает читаемость и надежность кода в целом.

Свойства Generics

  • Строгая типизация.
  • Единая реализация.
  • Отсутствие информации о типе.

Объявление generic-класса

Объявить generic-класс совсем несложно. Пример такого объявления :

Пример использования generic-класса GenericSample :

Проблемы реализации generic

1. Wildcard

Рассмотрим процедуру dump, которой в качестве параметров передается Collection для вывода значений в консоль.

При передаче списка данных с целочисленным типом возникает ошибка. В этом примере список List нельзя передавать в качестве параметра в dump, так как он не является подтипом List .

Проблема состоит в том что данная реализация кода не эффективна, так как Collection не является полностью родительской коллекцией всех остальных коллекций, грубо говоря Collection имеет ограничения. Для решения этой проблемы используется Wildcard («?»), который не имеет ограничения в использовании, то есть имеет соответствие с любым типом, и в этом его плюсы. И теперь, мы можем вызвать это с любым типом коллекции.

2. Bounded Wildcard

Рассмотрим процедуру draw, которая рисует фигуры, наследующие свойства родителя Shape. Допустим у Shape есть наследник Circle, и его необходимо «изобразить».

Возникла ошибка, связанная с несовместимостью типов. В предложенном решении необходимо определить тип и его подтипы. Это есть так называемое «ограничение сверху». Для этого нужно вместо определить .

Использование позволяет использовать тип Cycle и всех его предков вполоть до Object.

3. Generic метод

Определим процедуру addAll, которая в качестве параметров получает массив данных Object[] и переносит его в коллекцию Collection

Ошибки, возникающие в последних строках связаны с тем, что нельзя просто вставить Object в коллекции неизвестного типа. Способ решения этой проблемы является использование «generic метода«. Для этого перед методом нужно объявить и использовать его.

Читать еще:  Метод format java

Но все равно после выполнение останется ошибка в третьей строчке :

Допустим имеется функция, которая находит ближайший объект к точке Glyph из заданной коллекции. Glyph – это базовый тип, и может иметься неограниченное количество потомков этого типа. Также может иметься неограниченное количество коллекций, хранящих элементы, тип которых соответствует одному из этих потомков. Хотелось бы, чтобы функция могла работать со всеми подобными коллекциями, и возвращала элемент, тип которого совпадал бы с типом элемента коллекции, а не приводился к Glyph. Следующий пример не очень удачный:

Функция выглядит неплохо, но, тем не менее, не лишена недостатков. Получается так, что функции можно передать коллекцию любого типа. Это усложняет реализацию функции, порождая необходимость проверки типа элемента. Будет гораздо лучше написать так:

Теперь все встает на свои места, и в функцию можно передать только коллекцию, элементы которой реализуют интерфейс Glyph. generic сделал свое дело, код получился более типобезопасным.

4. Generic-классы

Наследование можно применять и для параметров generic-классов:

Как в методах, так и в классах можно задать более одного базового интерфейса, который должен реализовывать generic-параметр. Это делается при помощи следующего синтаксиса:

В данном примере generic-параметр должен реализовывать не только интерфейс Glyph, но и MoveableGlyph. Ограничений на количество интерфейсов, которые должен реализовывать переданный тип, нет. Но в класс можно передать только один, т.к. в Java нет множественного наследования. Типы в этом списке могут быть generic-типами, но ни один конкретный интерфейс не может появляться в списке более одного раза, даже с разными параметрами:

5. Bounded type argument

Метод копирования из одной коллекции в другую

Проблема в том, что две коллекции могут быть разных типов (несовместимость generic-типов). Для таких случаев был придуман Bounded type argument. Он нужен если метод, который мы разрабатываем, использовал бы определенный тип данных. Для этого нужно ввести (N принимает только значения M). Также можно корректно писать . (Принимает значения нескольких переменных).

6. Lower bounded wildcard

Метод нахождения максимума в коллекции

Ошибка возникает из-за того, что Test реализует интерфейс Comparable . Решение этой проблемы — Lower bounded wildcard («Ограничение снизу»). Суть заключается в том, что необходимо реализовывать метод не только для Т, но и для его супертипов (родительских типов). Например:

Теперь можно заполнить List , List или List .

6. Wildcard Capture

Реализация метода Swap в List

Ограничения generic

Невозможно создать массив generic’ов :

Невозможно создать массив generic-классов :

Преобразование типов

В Generics также можно манипулировать с информацией, хранящийся в переменных.

Наследование исключений в generic’ах

Возможность использовать параметр generic-класса или метода в throws позволяет при описании абстрактного метода не ограничивать разработчика, использующего класс или интерфейс, конкретным типом исключения. Но использовать тип, заданный в качестве параметра, в catch-выражениях нельзя.

Кроме того, можно сгенерировать исключение, тип которого задается generic-параметром, но экземпляр должен быть создан извне. Это ограничение порождается одним из ограничений Java generic’ов — нельзя создать объект, используя оператор new, тип которого является параметром generic’а.

Необходимо добавить, что тип, переданный в качестве параметра, должен обязательно быть наследником Throwable.

Таким образом, generic-и в Java получились проще и внесли несколько интересных концепций, таких как маски (wildcard) и ограничения, которые, добавили удобство при работе и помогли решить проблемы. Но, как и любое усложнение языка, эти нововведения затрудняют его понимание и изучение. Появление generic-ов сделало язык Java более выразительным и строгим; такие изменения только на пользу.

Дженерики (Java, обучающая статья)

Предисловие

За основу данной статьи была взята информация из 6-ой главы книги «Oracle Certified Professional Java SE 7 Programmers Exams 1Z0-804 and 1Z0-805». Она была немного изменена (кое-где обрезана, а кое-где дополнена с помощью Google и Википедии). Здесь показаны далеко не все нюансы дженериков — для более подробной информации следует обратиться к официальной документации. Приятного прочтения.

Введение

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

Допустим мы ничего не знаем о дженериках и нам необходимо реализовать специфический вывод на консоль информации об объектах различного типа (с использованием фигурных скобок).

Ниже пример реализации:

В вышеприведённом коде была допущена ошибка, из-за которой на консоли мы увидим следующее:

Теперь на время забудем об этом примере и попробуем реализовать тот же функционал с использованием дженериков (и повторим ту же ошибку):

Самое существенное отличие (для меня) в том, что при ошибке, аналогичной предыдущей, проблемный код не скомпилируется:

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

Посмотрим на декларацию BoxPrinter:

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

Внутри класса первое использование T в объявлении поля:

Здесь объявляется переменная дженерик-типа (generic type), т.о. её тип будет указан позже, при создании объекта класса BoxPrinter.

В main()-методе происходит следующее объявление:

Здесь указывается, что Т имеет тип Integer. Грубо говоря, для объекта value1 все поля Т-типа его класса BoxPrinter становятся полями типа Integer (private Integer val;).
Ещё одно место, где используется T:

Как и в декларации val с типом Т, вы говорите, что аргумент для конструктора BoxPrinter имеет тип T. Позже в main()-методе, когда будет вызван конструктор в new, указывается, что Т имеет тип Integer:

Теперь, внутри конструктора BoxPrinter, arg и val должны быть одного типа, так как оба имеют тип T. Например следующее изменение конструктора:

приведёт к ошибке компиляции.

Последнее место использования Т в классе – метод getValue():

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

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

Нет ограничений и на количество переменных с использующих такой тип:

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

Вернёмся немного назад к примеру со строкой кода:

Если типы не будут совпадать:

То мы получим ошибку при компиляции:

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

Читать еще:  Справочник javascript на русском

Следует обратить внимание, что возможны ошибки связанные с отсутствием «<>» при использовании алмазного синтаксиса

В случае с примером кода выше мы просто получим предупреждение от компилятора, Поскольку Pair является дженерик-типом и были забыты «<>» или явное задание параметров, компилятор рассматривает его в качестве простого типа (raw type) с Pair принимающим два параметра типа объекта. Хотя такое поведение не вызывает никаких проблем в данном сегменте кода, это может привести к ошибке. Здесь необходимо пояснение понятия простого типа.

Посмотрим на вот этот фрагмент кода:

Теперь посмотрим на вот этот:

По результатам выполнения оба фрагмента аналогичны, но у них разная идея. В первом случае мы имеем место с простым типом, во вторым – с дженериком. Теперь сломаем это дело – заменим в обоих случаях

Для простого типа получим ошибку времени выполнения (java.lang.ClassCastException), а для второго – ошибку компиляции. В общем, это очень похоже на 2 самых первых примера. Если в двух словах, то при использовании простых типов, вы теряете преимущество безопасности типов, предоставляемое дженериками.

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

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

Рассмотрим реализацию такого метода:

Нам в первую очередь интересно это:

» » размещено после ключевых слов «public» и «static», а затем следуют тип возвращаемого значения, имя метода и его параметры. Такое объявление отлично от объявления универсальных классов, где универсальный параметр указывается после имени класса. Тело метода вполне обычное – в цикле все элементы списка устанавливаются в одно значение (val). Ну и в main()-методе происходит вызов нашего универсального метода:

Стоит обратить внимание на то, что здесь не задан явно тип параметра. Для IntList – это Integer и 100 тоже упаковывается в Integer. Компилятор ставит в соответствие типу Т – Integer.

Возможны ошибки, связанные с импортом List из java.awt вместо java.util. Важно помнить, что список из java.util является универсальным типом а список из java.awt — нет.

А сейчас вопрос – какая (-ие) из нижеприведённых строк откомпилируется без проблем?

Перед ответом на этот вопрос следует учесть, что List – интерфейс, ArrayList наследуется от List; Number — абстрактный класс и Integer наследуется от Number.

Ответ с пояснением:
Первый вариант неправильный, т.к. нельзя создавать объект интерфейса.
Во втором случае мы создаем объект типа ArrayList и ссылку на него базового для ArrayList класса. И там, и там дженерик-тип одинаковый – всё правильно.
В третьем и четвёртом случае будет иметь ошибка компиляции, т.к. дженерик-типы должны быть одинаковыми (связи наследования здесь никак не учитываются).

Условие одинаковости дженерик-типов может показаться не совсем логичным. В частности хотелось бы использовать конструкцию под номером 3. Почему же это не допускается?

Будем думать от обратного – допустим 3-ий вариант возможен. Рассмотрим такой код:

Первая строка кода смотрится вполне логично, т.к. ArrayList наследуется от List , а Integer наследуется от Number. Однако допуская такую возможность мы получили бы ошибку в третьей строке этого кода, ведь динамический тип IntList — ArrayList , т.е. происходит нарушение типобезапасности (присвоение значение Float там, где ожидается Integer) и в итоге была бы получена ошибка компилятора. Дженерики созданы, чтобы избегать ошибок такого рода, поэтому существует данное ограничение. Но тем не менее это неудобное ограничение и Java поддерживает маски для его обхода.

Wildcards (Маски)

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

Как было написано выше вот такая строка кода не скомпилируется:

Но есть возможность похожей реализации:

Под маской мы будем понимать вот эту штуку – » «.

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

Метод printList принимает список, для которого в сигнатуре использована маска:

И этот метод работает для списков с различными типами данных (в примере Integer и String).

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

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

И ещё один маленький пример:

Тут не возникнет проблем компиляции. Однако нехорошо, что переменная numList хранит список со строками. Допустим нам нужно так объявить эту переменную, чтобы она хранила только списки чисел. Решение есть:

Данный код не скомпилируется, а всё из-за того, что с помощью маски мы задали ограничение. Переменная numList может хранить ссылку только на список, содержащий элементы унаследованные от Number, а всё из-за объявления: List numList. Тут мы видим, как маске задаётся ограничение – теперь numList предназначен для списка с ограниченным количеством типов. Double как и Integer наследуется от Number, поэтому код приведённый ниже скомпилируется.

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

Double-тип был использован для переменной result т.к. он без проблем взаимодействует с другими числовыми типами (т.е. не будет проблем с приведением типов).

В завершение этой темы добавлю, что аналогично ключевому слову extends в подобного рода выражениях может использоваться ключевое слово super — » «. Выражение означает, что вы можете использовать любой базовый тип (класс или интерфейс) типа Х, а также и сам тип Х. Пара строк, которые нормально скомпилируются:

На этом все. Надеюсь, данная статья была полезной.

Если Вам понравилась статья, проголосуйте за нее

Голосов: 170 Голосовать

Когда использовать параметризованный метод vs параметризованный конструктор в Java

Я только начал изучать Java и пришел к этой теме параметризованного метода и параметризованного конструктора из Java девятое издание Herber Schildt[Глава 6] ,

Я понял, как их объявлять и как они работают, но я запутался в их использовании, когда использовать параметризованный метод и когда использовать параметризованный конструктор?

Приведите пример с простой аналогией.

3 ответа

Они имеют основное отличие, которое является возвращаемым типом. Конструктор ничего не возвращает , даже void. Конструктор запускается при создании экземпляра класса. С другой стороны, параметризованные методы используются для безопасности типа i.e разрешить различные классы без кастинга.

Читать еще:  Ошибка аутентификации вай фай что значит

Прежде всего, вы должны понять, что на самом деле означает конструктор.

Конструктор можно рассматривать как функцию, вызываемую при создании объекта для класса. Внутри класса вы можете иметь много переменных экземпляра, т. е. каждый объект этого класса будет иметь свою собственную копию своих переменных экземпляра. Таким образом, всякий раз, когда вы создаете новый объект с помощью new ClassName (args) оператора, вы можете инициализировать значения переменных экземпляра этого объекта в конструкторе, так как он будет вызываться первым при создании экземпляра объекта. Помните, конструктор вызывается только один раз при создании объекта.

Кроме того, есть 2 типа методов экземпляра и статических методов. Оба типа могут принимать параметры. Если вы используете статический параметризованный метод, то вы не можете вызвать метод на объект класса, так как они могут работать со статическими переменными класса.

Методы с параметризацией экземпляра могут использовать как экземпляры, так и статические члены класса, и они вызываются для объекта. Они представляют собой механизм для выполнения определенной операции над объектом, на который вы вызываете метод. Параметры, передаваемые внутри этих методов ( как статические, так и экземплярные), могут выступать в качестве входных данных для операции, которую вы хотите выполнить.
В отличие от конструктора, который вызывался только один раз при установке объекта, вы можете вызывать методы столько раз, сколько хотите.

Еще одним классическим отличием является то, что конструктор всегда возвращает ссылку на объект. Это неявное поведение по умолчанию, и нам не нужно явно указывать это в подписи.
Однако метод может возвратить что-нибыдь согласно вашему выбору.

Метод & конструктор совершенно различное понятие & служит различная цель.

Конструктор задается для инициализации объекта и любого параметра / набора данных при создании объекта.Конструктор нельзя вызывать снова и снова . Он будет вызываться один раз для каждого объекта(вы можете вызвать один конструктор из другого конструктора этого класса)

Метод функциональный блок, вы можете вызвать/повторно использовать их как много по мере того как вы хотите.

Обобщения в Java (Java Generics)


Дженерики появились в Java 1.5 и призваны обезопасить код от неправильной типизации. Параметризируя класс, интерфейс или метод, можно добиться гибкости в переиспользовании алгоритмов, строгой проверки типов и упростить написание кода.

Без использования дженериков в код может пробраться ошибка типов:

Здесь мы случайно добавили в список число 3, а затем берём из списка строки. Код скомпилируется, но вот при запуске будет выдан ClassCastException на последней строке.

Перепишем с использованием дженериков:

Теперь компилятор указывает на нашу ошибку в list.add(3) . К тому же код стал меньше за счёт отсутствия приведения типов.

Готовим дженерики
Для наглядности опишем гараж в терминах ООП. У нас будет некоторое транспортное средство:

Небольшая невнимательность, и вот вместо мотоцикла мы вывозим кучу металлолома марки «ClassCastException».

Параметризация класса
Для параметризации класса или интерфейса, необходимо добавить после имени класса. Вместо T можно использовать что-то другое, например V, VEHICLE, но обычно используют T, как сокрашение от Type. Это T можно будет подставлять в своём классе как тип объекта.

Перепишем класс Garage.

Какого чёрта в нашем гараже делает Юпитер? И почему это компилируется и работает? Ответ прост: мы не указали верхнюю границу типов.

Верхняя граница типов
Когда мы переписывали класс Garage с обычной версии на версию с дженериками, мы забыли о том, что изначально в гараже были объекты типа Vehicle. Нужно это исправить и указать, что T должно быть не любым объектом, а подклассом Vehicle.

Для это служит такая конструкция:

В методе get ожидается возвращение типа Car , а возвращается Vehicle . Конечно, можно написать return (T) vehicle; , но это небезопасно. В нормальной версии класса Garage T и так подразумевается как Vehicle, все его методы доступны, так что нет смысла заменять T на тип верхней границы.

Пример с множественной параметризацией
Для примера сделаем трёхместный гараж.

Во втором случае, хоть мы и указали, что на первом месте должен быть Car, поставить Truck мы всё-таки можем, ведь он наследуется от Car. Но вот при получении объекта, Truck мы явно уже не получим, разве что явным приведением типа: Truck truck = (Truck) garage2.get1();

Дженерики и массивы
Теперь сделаем гараж, в который можно ставить множество транспортных средств одного типа. На ум, конечно же, приходит массив.

Дженерики и списки
Вместо массива можно использовать список. Поскольку интерфейс List параметризован, мы можем указать ему тип T и использовать в своей реализации гаража с переменным размером.

Причиной этому сигнатура метода void addAll(List c) . Если Garage параметризован по Car, то отныне везде, где используется T, ожидается Car и только он. Нам же нужно сделать так, чтобы в гараж можно было добавлять все подклассы заданного типа Car .

Wildcards

Wildcards обозначаются знаком вопроса (ещё он зовётся джокером). Их можно ограничивать верхней и нижней границей, что существенно увеличивает мощь дженериков.

Верхняя граница подстановки
Мы уже рассматривали ограничение с верхней границей, но тогда оно использовалось для типов . Теперь используются wildcards, а выглядит это так: .

Чтобы исправить проблему с добавлением нескольких грузовиков в гараж с автомобилями, нужно использовать верхнюю границу подстановки.

Вместо
void addAll(List list)
метод должен быть объявлен как
void addAll(List list)

Нижняя граница подстановки
Ещё одна проблема кроется в методе forEach. Там мы не можем использовать Consumer или Consumer :

На этот раз нам нужно ограничение по нижней границе, чтобы Consumer мог использовать иерархию классов, начиная от Car, а именно Car, Vehicle, Object.

Нижняя граница подстановки задаётся так: .

Теперь, объявив метод forEach вместо
void forEach(Consumer consumer)
как
void forEach(Consumer consumer)
приведённый выше пример будет работать.

PECS
Давайте добавим ещё два метода. Первый будет заменять сразу несколько транспортных средств в гараже, а второй возвращать список транспортных средств, удовлетворяющих некоему критерию.

Как определить, какое ограничение использовать для дженериков?
Существует правило PECS — Producer Extends Consumer Super .
Если аргумент используется как поставщик, то есть нужно что-то взять из объекта, то используется .
Если аргумент используется как потребитель, то есть нужно что-то положить в объект, то используется .
Если аргумент используется как для чтения, так и для записи, то ограничения не накладывается и нужно использовать или просто .

В методе replaceWith(List list) мы используем list в качестве поставщика, получая от него элементы, которые потом будут добавлены в гараж. В таком случае нужно использовать replaceWith(List list) .

В методе filter(Predicate predicate) мы используем predicate в качестве потребителя, передавая ему элементы для того, чтобы убедиться, что транспортное средство удовлетворяет критерию. В этом случае используем filter(Predicate predicate) .

Ссылка на основную публикацию
Adblock
detector