с какими типами данных не могут работать обобщения java

С какими типами данных не могут работать обобщения java

metanit java

Обобщения или generics (обобщенные типы и методы) позволяют нам уйти от жесткого определения используемых типов. Рассмотрим проблему, в которой они нам могут понадобиться.

Допустим, мы определяем класс для представления банковского счета. К примеру, он мог бы выглядеть следующим образом:

И на первый взгляд мы можем решить данную проблему следующим образом: задать id как поле типа Object, который является универсальным и базовым суперклассом для всех остальных типов:

Проблема может показаться искуственной, так как в данном случае мы видим, что в конструктор передается строка, поэтому мы вряд ли будем пытаться преобразовывать ее к типу int. Однако в процессе разработки мы можем не знать, какой именно тип представляет значение в id, и при попытке получить число в данном случае мы столкнемся с исключением java.lang.ClassCastException.

Писать для каждого отдельного типа свою версию класса Account тоже не является хорошим решением, так как в этом случае мы вынуждены повторяться.

Эти проблемы были призваны устранить обобщения или generics. Обобщения позволяют не указывать конкретный тип, который будет использоваться. Поэтому определим класс Account как обобщенный:

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

Используем данный класс:

Например, первый объект будет использовать тип String, то есть вместо T будет подставляться String:

В этом случае в качестве первого параметра в конструктор передается строка.

А второй объект использует тип int (Integer):

Обобщенные интерфейсы

Интерфейсы, как и классы, также могут быть обобщенными. Создадим обобщенный интерфейс Accountable и используем его в программе:

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

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

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

Кроме обобщенных типов можно также создавать обобщенные методы, которые точно также будут использовать универсальные параметры. Например:

Особенностью обобщенного метода является использование универсального параметра в объявлении метода после всех модификаторов и перед типом возвращаемого значения.

Затем внутри метода все значения типа T будут представлять данный универсальный параметр.

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

Использование нескольких универсальных параметров

Мы можем также задать сразу несколько универсальных параметров:

Обобщенные конструкторы

Конструкторы как и методы также могут быть обобщенными. В этом случае перед конструктором также указываются в угловых скобках универсальные параметры:

В данном случае конструктор принимает параметр id, который представляет тип T. В конструкторе его значение превращается в строку и сохраняется в локальную переменную.

Источник

BestProg

Java. Обобщения (шаблоны). Параметризованные типы. Обобщенные классы

Содержание

Поиск на других ресурсах:

1. Что такое «обобщение» (шаблон) в языке Java? Что такое параметризованный тип? Особенности применения обобщений

Обобщение – это механизм построения программного кода для некоторого типа с произвольным именем с целью его дальнейшего конвертирования (преобразования) в другой конкретный ссылочный тип. Реализацию конвертирования из обобщенного типа в другой (конкретный) осуществляет компилятор.

2. Преимущества применения обобщений

Использование обобщений в языке Java дает следующие преимущества:

3. Общая форма объявления обобщенного класса и объявление ссылки на обобщенный класс

Чаще всего обобщенный класс оперирует с одним типом. В этом случае общая форма класса имеет вид:

Общая форма объявления ссылки на обобщенный класс следующая

Например, если в программе объявить класс, который получает параметром тип T

то в этом классе можно реализовывать переменные и методы, которые имеют тип T

После объявления, использование вышеприведенного класса для типа Integer будет следующим

В вышеприведенном объявлении тип Integer есть аргументом типа.

Создание экземпляра класса для типа Double следующее

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

4. Какие типы запрещается использовать в обобщенных классах в качестве параметризованных типов?

То есть, если задан класс

то объявить экземпляр типа int или другого базового типа не удастся

5. Пример обобщенного класса, который реализует метод поиска элемента в двумерной матрице

Метод SearchKey() получает следующие параметры:

6. Пример реализации метода, который осуществляет циклический сдвиг в массиве обобщенного типа Type

Результат работы программы

7. Пример класса, который получает два параметризованных типа

Источник

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

Ограничения типов, метасимвольные аргументы, обобщенные методы и конструкторы

Продолжаем тему Generics (обобщения) и начнем с вопроса ограничений обобщенных типов. О чем здесь речь? Представьте, что нам нужно реализовать метод с некоторым типом данных T, который бы возвращал максимальное из двух значений:

Здесь я воспользовался классом Point, который мы создали на предыдущем занятии и добавил метод getMax(), возвращающий максимальную координату. Но если попытаться скомпилировать эту программу, то возникнет ошибка, так как не все типы данных поддерживают метод doubleValue(). Этот метод существует у типов, наследуемых от класса Number. Поэтому, если бы мы указали компилятору, что в качестве T будем использовать только числовые типы: Integer, Short, Double, Float, то проблем с вызовом doubleValue() не было бы, т.к. все эти типы реализуют этот метод. Так вот, чтобы сделать такой трюк и ограничить T числовыми типами, следует использовать такую запись:

Здесь мы говорим, что в качестве T можно передавать любой тип данных, у которого базовый класс является Number. А это, как раз, все числовые типы. Теперь, при компиляции программы никаких ошибок не появляется благодаря введенному ограничению на типы данных.

Создадим в методе main объект класса Point и вызовем метод getMax():

В консоли увидим значение 2.0. Обратите внимание, несмотря на то, что указан тип Integer, метод doubleValue() для него возвратит вещественное значение, т.е. целое число будет приведено к типу double и возвращено этим методом. Это в данном случае удобно, т.к. double можно использовать как универсальный тип представления разных действительных чисел.

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

В этом случае возникнет ошибка в момент компиляции программы, т.к. тип String не наследуется от класса Number и не подходит под наши ограничения.

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

В этом случае в качестве типов можно использовать любые типы данных, унаследованных от класса Number и с указанным в нем типом Integer.

Можно делать и еще более сильные ограничения, когда помимо класса указываются интерфейсы, которые он должен реализовывать. В качестве отвлеченного примера, объявим два пустых интерфейса:

И, затем, у типа класса Point укажем их после имени базового класса:

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

Также можно указывать только интерфейсы без базового класса:

В этом случае можно использовать любые тип, реализующие эти два интерфейса. Думаю, принцип реализации ограничений понятен.

Метасимвольные аргументы

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

И, далее в методе main() создаем два объекта и сравниваем их с помощью нашего метода equalsPoint():

Но при компиляции возникнет ошибка в строчке

то проблема будет решена. Теперь, метод equalsPoint() принимает любой тип данных класса Point.

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

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

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

Иногда требуется объявлять не обобщенный класс, а один или несколько обобщенных методов внутри обычного класса. Например, мы пишем класс Math и хотим определить метод, который бы определял нахождение определенного значения в массиве. Для этой цели хорошо подходит следующий статический обобщенный метод:

Смотрите, мы здесь перед типом метода указываем обобщенный тип T, и далее используем его при определении аргументов. Затем, в методе main() можно вызвать этот метод следующим образом:

Или, с явным указанием обобщенного типа:

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

Обобщенные конструкторы

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

Обратите внимание, сам класс Digit не является обобщенным, только его конструктор. Далее, в методе main() можно его использовать с любыми типами числовых данных: Integer, Float, Short и т.д.

Вот так в языке Java можно накладывать ограничения на используемые типы данных, использовать метасимвольные аргументы и обобщенные конструкторы и методы.

Видео по теме

default

#11 Концепция объектно-ориентированного программирования (ООП)

default

#12 Классы и создание объектов классов

default

#13 Конструкторы, ключевое слово this, инициализаторы

default

#14 Методы класса, сеттеры и геттеры, public, private, protected

default

#15 Пакеты, модификаторы конструкторов и классов

default

#16 Ключевые слова static и final

default

#17 Внутренние и вложенные классы

default

#18 Как делается наследование классов

default

#19 Ключевое слово super, оператор instanceof

default

#20 Модификаторы private и protected, переопределение методов, полиморфизм

default

#21 Абстрактные классы и методы

default

default

default

#24 Анонимные внутренние классы

default

#25 Перечисления (enum)

default

#26 Обобщения классов (Generics)

default

#27 Ограничения типов, метасимвольные аргументы, обобщенные методы и конструкторы

default

#28 Обобщенные интерфейсы, наследование обобщенных классов

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

Источник

Дженерики (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), в котором можно опустить параметры типа. Т.е. можно предоставить компилятору определение типов при создании объекта. Вид упрощённого объявления:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Wildcards (Маски)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Голосов: 175 Голосовать loading

Источник

Оцените статью
Самые лучшие ответы на вопрос "Какой"
Adblock
detector