Что такое обобщение java

MnogoBlog

как создать сайт на wordpress, настроить и оптимизировать wordpress

Что такое обобщение java. Смотреть фото Что такое обобщение java. Смотреть картинку Что такое обобщение java. Картинка про Что такое обобщение java. Фото Что такое обобщение java

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

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

Метод average() класса Stats пытается получить версию типа double каждого числа в массиве nums, вызывая метод doubleValue(). Поскольку все числовые классы, такие как Integer и Double, являются подклассами Number, а класс Number определяет метод doubleValue(), этот метод доступен всем числовым классам-оболочкам.

Проблема в том, что компилятор не имеет возможности узнать, что вы намерены создавать объекты класса Stats, используя только числовые типы.
То есть, когда вы компилируете класс Stats, выдается сообщение об ошибке, свидетельствующее о том, что метод doubleValue() не известен. Чтобы решить эту проблему, вам нужен какой-то способ сообщить компилятору, что вы собираетесь передавать в параметре Т только числовые типы.
Более того, необходим еще некоторый способ гарантии того, что будут передаваться только числовые типы.

Чтобы справиться с этой ситуацией, язык Java предлагает ограниченные типы. Когда указывается параметр типа, вы можете создать ограничение сверху, которое объявляет суперкласс, от которого должны быть унаследованы все аргументы типов. Для этого используется ключевое слово extends при указании параметра типа, как показано ниже:

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

Результат работы этой программы выглядит следующим образом:
Среднее значение iob равно 3.0
Среднее значение dob равно 3.3

Обратите внимание на то, что класс Stats теперь объявлен так:

Поскольку тип Т теперь ограничен классом Number, компилятор Java знает, что все объекты типа Т могут вызывать метод doubleValue(), так как это метод класса Number. Это уже серьезное преимущество.

Однако в качестве дополнительного бонуса ограничение параметра Т также предотвращает создание нечисловых объектов класса Stats.

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

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

Здесь параметр T ограничен классом по имени MyClass и интерфейсом
Mylnterface. То есть любой тип, переданный параметру Т, должен быть подклассом класса MyClass и иметь реализацию интерфейса Mylnterface.

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

Например, класс Stats, рассмотренный в предыдущем разделе, предполагает, что вы хотите добавить метод по имени sameAvg(), который определяет, содержат ли два объекта класса Stats массивы, порождающие одинаковое среднее значение, независимо от того, какого типа числовые
значения в них содержатся.

Например, если один объект содержит значения типа double 1.0, 2.0 и 3.0, а другой — целочисленные значения 2, 1 и 3, то среднее
значение у них будет одинаково. Один из способов реализации метода sameAvg() — передать ему аргумент класса Stats, а затем сравнивать его среднее значение со средним значением вызывающего объекта, возвращая значение true, если они равны.

Например, необходимо иметь возможность вызывать метод sameAvg(), как показано ниже:

Вначале написание метода sameAvg() кажется простой задачей. Поскольку
класс Stats является обобщенным и его метод average() может работать с объектами класса Stats любого типа, кажется, что написание метода sameAvg() не представляет сложности. К сожалению, проблема появляется сразу, как только вы попытаетесь объявить параметр типа Stats. Поскольку Stats — параметризованный тип, какой тип параметра вы укажете для Stats, когда создадите параметр типа Stats?

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

С приведенным выше кодом связана проблема, которая заключается в том, что такой код будет работать только с другим объектом класса Stats, тип которого совпадает с вызывающим объектом.

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

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

Это демонстрируется в следующей программе:

Результат работы этой программы:
Среднее для iob равно 3.0
Среднее для dob равно 3.3
Среднее для fob равно is 3.0
Средние iob и dob отличаются.
Средние iob и fob равны.

И еще один, последний, момент: важно понимать, что шаблон не влияет на то, какого конкретного типа создается объект класса Stats. Этим управляет слово extends в объявлении класса Stats. Шаблон просто соответствует корректному объекту класса Stats.

Ограниченные шаблоны.

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

На вершине иерархии находится класс TwoD, который инкапсулирует двухмерные координаты XY. Его наследник — класс ThreeD — добавляет третье измерение, описывая координаты XYZ. От класса ThreeD наследуется класс FourD, который добавляет четвертое измерение (время), порождая четырехмерные координаты.

Ниже показан обобщенный класс, называемый Coords, который хранит массив координат:

Обратите внимание на то, что класс Coords задает тип параметра, ограниченный классом TwoD. Это значит, что любой массив, сохраненный в объекте класса Coords, будет содержать объект типа TwoD или любой из его подклассов.
Теперь предположим, что вы хотите написать метод, который отображает координаты X и Y для каждого элемента в массиве coords объекта класса Coords.
Поскольку все типы объектов класса Coords имеют, как минимум, пару координат (X и Y), это легко сделать с помощью шаблона:

Поскольку класс Coords — ограниченный обобщенный тип, который задает
класс TwoD как верхнюю границу, все объекты, которые можно использовать для создания объекта класса Coords, будут массивами типа TwoD или классов, наследуемых от него. Таким образом, метод showXY() может отображать содержимое любого объекта класса Coords.

Но что, если вы хотите создать метод, отображающий координаты X, Y и Z объекта классов ThreeD или FourD?

Ограниченный шаблон задает верхнюю или нижнюю границу типа аргумента.
Это позволяет ограничить типы объектов, которыми будет оперировать метод.
Наиболее популярен шаблон, ограничивающий сверху, который создается с применением оператора extends, почти так же как при описании ограниченного типа.
Применяя ограниченные шаблоны, легко создать метод, отображающий координаты X, Y и Z для объекта класса Coords, если этот объект действительно имеет эти три координаты. Например, следующий метод showXYZ() показывает координаты элементов, сохраненных в объекте класса Coords, если эти элементы имеют тип ThreeD (или унаследованы от класса ThreeD):

Ниже приведена полная программа, которая демонстрирует действие ограниченного шаблона аргумента:

Обратите внимание на следующие закомментированные строки:

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

В этом случае допустимыми аргументами могут быть только классы, которые являются суперклассами для подклассa. Это исключающая конструкция, поcкольку она не включает класс подклассa.

Источник

Дженерики в Java для самых маленьких: синтаксис, границы и дикие карты

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

Что такое обобщение java. Смотреть фото Что такое обобщение java. Смотреть картинку Что такое обобщение java. Картинка про Что такое обобщение java. Фото Что такое обобщение java

Что такое обобщение java. Смотреть фото Что такое обобщение java. Смотреть картинку Что такое обобщение java. Картинка про Что такое обобщение java. Фото Что такое обобщение java

Оля Ежак для Skillbox Media

У нас в парадной подъезде рядом с почтовыми ящиками стоит коробка. Предполагалось, что туда будут выбрасывать бумажный спам, который какие-то вредители упорно кладут в эти самые ящики. Но в коробке вместе с бумажками лежат пустые бутылки и банки, подозрительного вида пакеты, а в нынешних реалиях — ещё и использованные медицинские маски. Почему люди так делают? Потому что так можно.

Теперь представьте, что содержимое коробки вы отвозите на переработку, а перед этим каждый раз приходится отделять бумагу от прочего мусора. Не хотели бы вы заполучить такую коробку, которая не даст положить в себя что-то, кроме бумаги? Если ваш ответ «да» — вам понравятся дженерики (generics).

Содержание

Что такое обобщение java. Смотреть фото Что такое обобщение java. Смотреть картинку Что такое обобщение java. Картинка про Что такое обобщение java. Фото Что такое обобщение java

Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.

Знакомимся с дженериками

До появления дженериков программисты могли неявно предполагать, что какой-то класс, интерфейс или метод работает с элементами определённого типа.

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

Здесь предполагается, что метод printSomething работает со списком строк. Мы можем догадаться об этом, потому что в цикле все элементы приводятся (преобразуются) к классу String, а потом ещё и метод length этого класса вызывается.

Но смотрите, что сделали программисты Саша и Маша, — они поленились заглянуть внутрь метода и положили в список: один — число, а вторая — экземпляр StringBuilder.

Вот только тестировщик назначил баг не кому-то из них, а Паше, который написал метод printSomething, — потому что ошибка произошла именно во время его выполнения.

Что такое обобщение java. Смотреть фото Что такое обобщение java. Смотреть картинку Что такое обобщение java. Картинка про Что такое обобщение java. Фото Что такое обобщение java

Паша быстро нашёл истинных виновников и попросил их исправить заполнение списка. Но на будущее решил подстраховаться от подобных ситуаций и переписал метод с использованием дженериков. Вот так:

Теперь, если кто-то захочет положить в массив нестроковый элемент, ошибка станет заметной сразу — ещё на этапе компиляции.

Что такое обобщение java. Смотреть фото Что такое обобщение java. Смотреть картинку Что такое обобщение java. Картинка про Что такое обобщение java. Фото Что такое обобщение java

Обратите внимание, что во второй версии Пашиного метода item не приводится насильно к типу String. Мы просто получаем в цикле очередной элемент списка, и компилятор соглашается, что это, очевидно, будет строка. Код стал менее громоздким, читать его стало проще.

Объявляем дженерик-классы и создаём их экземпляры

Давайте запрограммируем ту самую коробку, о которой шла речь в начале статьи: создадим класс Box, который умеет работать только с элементами определённого типа. Пусть для простоты в этой коробке пока будет только один элемент:

В классе два метода:

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

Теперь создадим коробку для бумаги. Пусть за бумагу отвечает класс Paper, а значит, экземпляр правильной коробки создаётся вот так:

Это полный вариант записи, но можно и короче:

Так как слева мы уже показали компилятору, что нужна коробка именно для бумаги, справа можно опустить повторное упоминание Paper — компилятор «догадается» о нём сам.

Это «угадывание» называется type inference — выведение типа, а оператор « <>» — это diamond operator. Его так назвали из-за внешнего сходства с бриллиантом.

E — element, для элементов параметризованных коллекций;

K — key, для ключей map-структур;

V — value, для значений map-структур;

N — number, для чисел;

T — type, для обозначения типа параметра в произвольных классах;

S, U, V и так далее — применяются, когда в дженерик-классе несколько параметров.

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

А можно пойти ещё дальше и создать дженерик-класс с двумя параметрами для коробки с двумя отсеками. Вот так:

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

Обратите внимание, что type inference и diamond operator позволяют нам опустить оба параметра в правой части.

Объявляем и реализуем дженерик-интерфейсы

Объявление дженерик- интерфейсов похоже на объявление дженерик-классов. Продолжим тему переработки и создадим интерфейс пункта переработки GarbageHandler сразу с двумя параметрами: тип мусора и способ переработки:

Реализовать (имплементить) этот интерфейс можно в обычном, не дженерик- классе:

Но можно пойти другим путём и сначала объявить дженерик-класс с двумя параметрами:

Или скомбинировать эти два способа и написать дженерик-класс только с одним параметром:

Дженерик-классы и дженерик-интерфейсы вместе называются дженерик-типами.

Можно создавать экземпляры дженерик-типов «без расшифровки», то есть никто не запретит вам объявить переменную типа Box — просто Box:

Для такого случая даже есть термин — raw type, то есть «сырой тип». Эту возможность оставили в языке для совместимости со старым кодом, который был написан до появления дженериков.

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

Пишем дженерик-методы

В примерах выше мы уже видели параметризованные методы в дженерик-классах и интерфейсах. Типизированными могут быть как параметры метода, так и возвращаемый тип.

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

У метода transfer есть свой личный параметр для типа, который не обязан совпадать ни с типом T, ни с типом S. При первом упоминании новый параметр, как и в случае с заголовком класса или интерфейса, пишется в угловых скобках.

Дженерик-методы можно объявлять и в обычных (не дженерик) классах и интерфейсах. Наш класс для переработки мог быть выглядеть так:

Здесь дженерики используются только в методе.

Обратите внимание на синтаксис: параметры типов объявляются после модификатора доступа ( public), но перед возвращаемым типом ( void). Они перечисляются через запятую в общих угловых скобках.

Ограничиваем дженерики сверху и снизу

Давайте немного расширим наше представление о мусоре и введём для него дополнительное свойство — массу «типичного представителя», то есть массу одной пластиковой бутылки или листка бумаги, например.

Теперь попробуем использовать эту массу в методе уже знакомого класса Box:

И получим ошибку при компиляции: мы не рассказали компилятору, что T — это какой-то вид мусора. Исправим это с помощью так называемого upper bounding — ограничения сверху:

Теперь метод getItemWeight успешно скомпилируется.

Здесь T extends Garbage означает, что в качестве T можно подставить Garbage или любой класс-наследник Garbage. Из уже известных нам классов это могут быть, например, Paper или Plastic. Так как и у Garbage, и у всех его наследников есть метод getWeight, его можно вызывать в новой версии дженерик-класса Box.

Для одного класса или интерфейса можно добавить сразу несколько ограничений. Вспомним про интерфейс для пункта приёма мусора и введём класс для метода переработки — HandleMethod. Тогда GarbageHandler можно переписать так:

Wildcards

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

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

Wildcards удобно использовать для объявления переменных и параметров методов совместно с классами из Java Collection Framework — здесь собраны инструменты Java для работы с коллекциями. Если вы не очень хорошо знакомы с ними, освежите знания, прочитав эту статью.

В примере ниже мы можем подставить вместо «?» любой тип, в том числе Paper, поэтому строка успешно скомпилируется:

Wildcards можно применять для ограничений типов:

Это уже знакомое нам ограничение сверху, upper bounding, — вместо «?» допуст им Garbage или любой его класс-наследник, то есть Paper подходит.

Но можно ограничить тип и снизу. Это называется lower bounding и выглядит так:

Что такое обобщение java. Смотреть фото Что такое обобщение java. Смотреть картинку Что такое обобщение java. Картинка про Что такое обобщение java. Фото Что такое обобщение java

Собираем понятия, связанные с дженериками

Мы не успели разобраться с более сложными вещами — например, с заменами аргументов типов в классах-наследниках, с переопределением дженерик-методов, не узнали об особенностях коллекций с wildcards.

Обо всём этом и не только — в следующих статьях. А пока соберём небольшой словарик из терминов, которые связаны с использованием дженериков, — они пригодятся при чтении специальной литературы:

ТерминРасшифровка
Дженерик-типы (generic types)Дженерик-класс или дженерик-интерфейс с одним или несколькими параметрами в заголовке
Параметризованный тип (parameterized types)Вызов дженерик-типа. Для дженерик-типа List параметризованным типом будет, например, List
Параметр типа (type parameter)Используются при объявлении дженерик-типов. Для Box T — это параметр типа
Аргумент типа (type argument)Тип объекта, который может использоваться вместо параметра типа. Например, для Box

Paper — это аргумент типа

WildcardОбозначается символом «?» — неизвестный тип
Ограниченный wildcard (bounded wildcard)Wildcard, который ограничен сверху — или снизу —
Сырой тип (raw type)Имя дженерик-типа без аргументов типа. Для List сырой тип — это List

Ещё больше о дженериках, коллекциях и других элементах языка Java узнайте на нашем курсе «Профессия Java-разработчик». Научим программировать на самом востребованном языке и поможем устроиться на работу.

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

Источник

Обобщения в Java: часть 1

ОГЛАВЛЕНИЕ

Аннотация

Java 5 (JDK 1.5) ввел принцип обобщений или параметризованных типов. Данная статья знакомит с принципами обобщений и показывает примеры их использования. В части II рассмотрено, как обобщения на самом деле реализованы в Java, и несколько проблем с применением обобщений.

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

где aBookReference – ссылка типа Book, не связанная с Dog, вы получите ошибку компиляции.

Однако, к сожалению, при появлении Java, это не осуществлялось полностью в библиотеке Коллекции. Так, например, можно написать:

Не контролируется то, какой тип объекта помещается в Vector. Рассмотрим следующий пример:

В программе выше создается ArrayList, заполняется некоторыми целыми значениями Integer, а затем значения суммируются путем извлечения Integer из ArrayList.

Что если изменить метод populateNumbers() следующим образом:

Ошибок компиляции не будет. Однако программа не выполнится правильно. Выдастся следующая ошибка при выполнении:

До Java 5 в коллекциях не было безопасности типов.

Что такое обобщения?

Наконец, в Java 5 было решено ввести обобщения. Хотя обобщения – возможность писать универсальный или обобщенный код, независимый от конкретного типа – в принципе похожи на шаблоны в C++, есть ряд отличий. Например, в отличие от C++, где генерируются разные классы для каждого параметризованного типа, в Java есть только один класс для каждого обобщенного типа, независимо от того, экземпляры скольких разных типов создаются посредством него. Конечно, в обобщениях Java есть определенные проблемы, но они будут рассмотрены в части II. В части I рассматриваются преимущества.

Развитие обобщений в Java началось с проекта под названием GJ1 (обобщенный Java), начатого как расширение языка. Затем эту идею принял Процесс сообщества Java (JCP) в качестве Запроса спецификации Java (JSR) 142.

Обобщенная безопасность типов

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

Используется ArrayList вместо ArrayList. Сейчас при компиляции кода выдается ошибка компиляции:

Параметризованный тип ArrayList обеспечивает безопасность типов.

Соглашения об именовании

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

• Использовать букву E для элементов коллекции, как в определении:

• Использовать буквы T, U, S и т.д. для универсальных типов.

Написание обобщенных классов

Синтаксис для написания обобщенного класса очень простой. Пример обобщенного класса:

Этот класс представляет собой пару значений некоторого обобщенного типа E. Рассмотрим несколько примеров использования данного класса:

Если попытаться создать объект с типами, которые не соответствуют, выдастся ошибка компиляции. Рассмотрим следующий пример:

Здесь предпринимается попытка отправить экземпляр Integer и экземпляр Double экземпляру Pair. Однако это дает ошибку компиляции.

Обобщения и заменяемость

Обобщения соблюдают принцип заменяемости Лискова. Поясним на примере. Допустим, есть корзина фруктов. В нее можно добавить апельсины, бананы, виноград и т.д. Теперь создадим корзину бананов. В нее должно быть разрешено добавлять только бананы. Она должна запрещать добавление других типов фруктов. Банан является фруктом, т.е. банан наследуется от фрукта. Должна ли корзина бананов наследоваться от корзины фруктов, как показано на рисунке ниже?

Что такое обобщение java. Смотреть фото Что такое обобщение java. Смотреть картинку Что такое обобщение java. Картинка про Что такое обобщение java. Фото Что такое обобщение java

Обобщения соблюдают этот принцип. Рассмотрим следующий пример:

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

Что если вы хотите обрабатывать другой тип Pair как один тип? Это будет рассмотрено позже в разделе «Подстановочный знак».

Прежде чем оставить данную тему, посмотрим на одно странное поведение. Тогда как:

запрещено, однако следующее – разрешено:

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

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

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

ArrayList lst1 заполняется тремя значениями, а затем его содержимое фильтруется (копируется) в другой ArrayList lst2. Размер lst2 после вызова метода filter() равен 2. Теперь посмотрим на немного другой вызов:

Здесь получаем ошибку компиляции:

Ошибка говорит, что невозможно отправить ArrayList разных типов этому методу. Хорошо, однако попробуем следующее:

Этот код компилируется без ошибок, и вызов lst3.size() возвращает 1. Почему он скомпилировался, и что здесь происходит? Компилятор лезет из кожи вон, чтобы обеспечить вызовы обобщенных методов, если это возможно. В данном случае, рассматривая lst3 как простой ArrayList, то есть без параметризованного типа (смотрите последний абзац в разделе “Обобщения и заменяемость” выше), он в состоянии вызвать метод filter.

Это может привести к ряду проблем. Добавим еще один оператор к примеру выше. При начале набора с клавиатуры интегрированная среда разработки (IDE) (используется IntelliJ IDEA) подсказывает код, как показано ниже:

Что такое обобщение java. Смотреть фото Что такое обобщение java. Смотреть картинку Что такое обобщение java. Картинка про Что такое обобщение java. Фото Что такое обобщение java

Она говорит, что вызов метода get() принимает индекс и возвращает Integer. Ниже приведен готовый код:

Как думаете, что произойдет при выполнении этого кода? Может, исключение времени выполнения? Сюрприз! Этот кусок кода даст следующий вывод:

Что произойдет, если добавить следующий код:

Здесь запрашивается Integer из коллекции. Этот код сгенерирует ClassCastException. В то время как предполагается, что обобщения делают код безопасным по отношению к типам, данный пример показывает, как можно легко, умышленно или случайно, обойти это и, в лучшем случае, получить исключение времени выполнения или, в худшем случае, получить код, потихоньку работающий неправильно. Хватит пока проблем. Некоторые из них будут детально рассмотрены во второй части II. В части I перейдем к тому, что пока работает хорошо.

Верхние пределы

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

Использовали бы его, как показано ниже:

Спрашивается, как доделать реализацию метода max()? Попытаемся сделать это:

Это не сработает. Оператор > не определен в ссылках. Как же тогда сравнить два объекта? Вспоминается интерфейс Comparable(сравнимый). Почему бы не использовать интерфейс comparable для выполнения этой задачи:

Несмотря на то, что этот код работает, есть две проблемы. Во-первых, он некрасив. Во-вторых, приходится учитывать случай, когда приведение к Comparable не удается. Поскольку так сильна зависимость от типа, реализующего этот интерфейс, почему бы не попросить компилятор навязать его? Именно это делают верхние пределы. Ниже приведен код:

Компилятор проверит, чтобы убедиться, что параметризованный тип, заданный при вызове этого метода, реализует интерфейс Comparable. Если попытаться вызвать max() с экземплярами некоторого типа, не реализующего интерфейс Comparable, выдастся строгая ошибка компиляции.

Подстановочный знак

Перейдем к более интересным принципам обобщений. Рассмотрим следующий пример:

Класс Animal(животное) имеет метод playWith(), принимающий коллекцию Animal. Dog(собака), расширяющая Animal, переопределяет этот метод. Попробуем использовать класс Dog в примере:

Здесь создается экземпляр Dog и отправляется коллекция Dog его методу playWith(). Выдается ошибка компиляции:

Причина состоит в том, что коллекцию Dog нельзя рассматривать как коллекцию Animal, которую ожидает метод playWith() (смотрите раздел “Обобщения и заменяемость” выше). Однако было бы логично иметь возможность отправить коллекцию Dog этому методу, не так ли? Как это сделать? Здесь вступает в дело подстановочный знак или неизвестный тип.

Оба метода playMethod()(в Animal и Dog) изменяются следующим образом:

Collection не имеет тип Animal. Вместо этого она имеет неизвестный тип (?). Неизвестный тип – не Object, он просто неизвестный или неопределенный.
Теперь код:

компилируется без ошибок. Однако есть проблема. Также можно написать:

Изменение, сделанное, чтобы позволить отправить коллекцию Dog методу playWith(), теперь позволяет отправить и коллекцию Integer. Если разрешить это, получится странная собака. Как сказать, что компилятор должен разрешать коллекции Animal или коллекции любого типа, расширяющего Animal, но не любую коллекцию других типов? Это позволяет осуществить применение верхних пределов, как показано ниже:

Нижние пределы

Рассмотрим последний пример. Допустим, надо скопировать элементы из одной коллекции в другую. Ниже приведен код первой попытки сделать это:

Попытаемся использовать данный метод:

В этом коде копируются Dog из одного Dog ArrayList в другой. Поскольку Dog является Animal, Dog может находиться в Dog ArrayList и в Animal ArrayList, не так ли? Следующий код копирует из Dog ArrayList в Animal ArrayList.

Однако при компиляции этого кода выдается ошибка:

Как заставить его работать? Здесь помогают нижние пределы. Второй аргумент Copy должен иметь тип T или любой тип, являющийся базовым типом T. Код выглядит так:

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

Заключение

На примерах была показана мощь обобщений в Java. Однако с использованием обобщений в Java есть проблемы, которые будут рассмотрены в части II данной статьи. В части II будут разобраны некоторые ограничения обобщений, реализация обобщений в Java, эффект стирания типа, изменения в библиотеке классов Java ради обеспечения обобщений, проблемы преобразования необобщенного кода в обобщенный код, и, наконец, некоторые из подводных камней или недостатков обобщений.

В части I были рассмотрены принципы обобщений в Java и их применение. Обобщения обеспечивают безопасность типов. Обобщения реализованы так, чтобы обеспечить обратную совместимость с необобщенным кодом. Они проще шаблонов в C++ и не вызывают раздувания кода при компиляции. В части II разбираются проблемы применения обобщений.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *