что нельзя параметризовать java

BestProg

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

Содержание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

Core Java

Курс лекций. Лекция 6

Иван Пономарёв, КУРС/МФТИ

До появления дженериков

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

После появления дженериков

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

Определение и использование

Generic methods

Другой пример

Промежуточные выводы

Использование параметризованных классов — простое (просто укажите параметры, List )

Использование параметризованных методов — ещё проще: type inference: Manager m = getRandomItem(…​);

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

Bounded types

Intersection types

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

Реализация дженериков

Появились в Java 5

Стояла задача обратной совместимости

Generics — возможность языка, а не платформы

Type Erasure, будь он неладен!

Сырые типы

Generic Type (source)

Raw Type (compiled)

Ограниченные типы вместо Object

Generic Type (source)

Raw Type (compiled)

Вызовы методов

Source code

Compiled

Bridge methods для сохранения полиморфизма

Source code

Compiled

Итог: как это работает

Параметризованных классов в JVM нет, только обычные классы и методы.

Типовые параметры заменяются на Object или на свою границу.

Для сохранения полиморфизма добавляются связывающие методы (bridge methods).

Сведение типов добавляется по мере необходимости.

Никогда не употребляйте сырые типы

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

Java5 вышла в 2004 году.

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

Стирание типов → невозможность определить параметр типа в Runtime

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

Стирание типов до Object → невозможность использовать примитивные типы в качестве параметров

Примитивы и дженерики

День сегодняшний: нужна производительность? — пишем специальные реализации.

В стандартной библиотеке:

В специализированных библиотеках вроде fastutil:

HashMap → Int2ObjectMap (ВНИМАНИЕ: реальная потребность в таких библиотеках возникает редко!!)

День завтрашний: Project Valhalla, specialized generics. Решит проблему раз и навсегда.

Нельзя инстанцировать типы-параметры

Решается с помощью метакласса и рефлексии (о которой речь впереди)

Тем более нельзя инстанцировать массив типа-параметра

Решается передачей параметра, например, в ArrayList:

Массивы и дженерики — лютые враги

Забьём значения кувалдой и устроим heap pollution

Varargs — всё равно массив…​

Тот же heap pollution, что и в прошлом примере:

Компилятор что-то предчувствует…​

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

Чтобы успокоить компилятор, надо поставить аннотацию @SafeVarargs :

…​и компилятор успокоится.

Зачем?!

Всё потому, что иметь varargs с параметризованными типами удобно.

Collections.addAll(Collection c, T…​ elements)

EnumSet.of(E first, E…​ rest)

Если вести себя хорошо, можно ставить @SafeVarargs, и всё будет хорошо:

Не записывать ничего в элементы массива,

Не раздавать ссылку на массив параметров наружу.

Нельзя параметризовать

ловля исключений — это проверка их типов,

дальше сырых типов мы в рантайме проверить не можем.

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

Параметры типов нельзя использовать в статическом контексте

Нельзя реализовывать разные параметризации одного и того же интерфейса

Source code

Compiled

Ковариантность массивов vs инвариантность дженериков

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

Реальная картина

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

Как быть, если хочется такого?

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

Так не получится…​

Wildcard Types

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

В общем, addAllTo реализовать не получится…​

В обратную сторону (контравариантные типы)

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

Unbounded wildcard

Класть можем только null.

Мнемоническое правило

PECS

Producer Extends, Consumer Super

Правила использования wildcard-типов

Используются в аргументах методов и локальных переменных.

Невидимы пользователю API, не должны возвращаться.

Их цель — принимать те аргументы, которые надо принимать, и отвергать те аргументы, которые надо отвергать.

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

Wildcard Capture

Метод с type capture

Recursive Generics

Что почитать-посмотреть

J. Bloch, Effective Java, 3rd ed. Chapter 5 — Generics. Addison-Wesley, 2018

Источник

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

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

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

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

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

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

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

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

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

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

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

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

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

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

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

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

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

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

Wildcards
что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java
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
Давайте добавим ещё два метода. Первый будет заменять сразу несколько транспортных средств в гараже, а второй возвращать список транспортных средств, удовлетворяющих некоему критерию.

Источник

Параметризованные классы Java

К наиболее важным новшествам версии языка J2SE 5 можно отнести появление параметризованных (generic) классов и методов, позволяющих использовать более гибкую и в то же время достаточно строгую типизацию, что особенно важно при работе с коллекциями. Применение generic-классов для создания типизированных коллекций будет рассмотрено в главе «Коллекции». Параметризация позволяет создавать классы, интерфейсы и методы, в которых тип обрабатываемых данных задается как параметр.

Приведем пример generic-класса с двумя параметрами:

/*пример # 9 : объявление класса с двумя параметрами : Subject.java */

public class Subject <

public Subject(T2 ids, T1 names) <

Здесь T1, Т2 – фиктивные объектные типы, которые используются при объявлении членов класса и обрабатываемых данных. При создании объекта компилятор заменит все фиктивные типы на реальные и создаст соответствующий им объект. В качестве параметров классов запрещено применять базовые типы.

Объект класса Subject можно создать, например, следующим образом:

new Subject (ch, 71D );

В объявлении sub2 имеет место автоупаковка значения 71D в Double.

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

Ниже приведен пример параметризованного класса Optional с конструкторами и методами, также инициализация и исследование поведения объектов при задании различных параметров.

/*пример # 10 : создание и использование объектов параметризованного

public class Optional <

public Optional(T value) <

public void setValue(T val) <

public String toString() <

return value.getClass().getName() + » » + value;

public static void main(String[] args) <

int v1 = ob1.getValue();

String v2 = ob2.getValue();

//ob1 = ob2; //ошибка компиляции – параметризация не ковариантна

Optional ob3 = new Optional();

ob3.setValue(«Java SE 6»);

тип объекта, а не тип параметризации */

В результате выполнения этой программы будет выведено:

java.lang.String Java SE 6

В рассмотренном примере были созданы объекты типа Optional: ob1 на основе типа Integer и ob2 на основе типа String при помощи различных конструкторов. При компиляции вся информация о generic-типах стирается и заменяется для членов класса и методов заданными типами или типом Object, если параметр не задан, как для объекта ob3. Такая реализация необходима для обеспечения совместимости с кодом, созданным в предыдущих версиях языка.

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

public class OptionalExt <

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

Часто возникает необходимость в метод параметризованного класса одного допустимого типа передать объект этого же класса, но параметризованного другим типом. В этом случае при определении метода следует применить метасимвол ?. Метасимвол также может использоваться с ограничением extends для передаваемого типа.

/*пример # 11 : использование метасимвола в параметризованном классе: Mark.java, Runner.java */

public Mark(T value) <

/* вместо */ // public boolean sameAny(Mark ob) <

public boolean sameAny(Mark ob) <

return roundMark() == ob.roundMark();

public boolean same(Mark ob) <

return getMark() == ob.getMark();

public static void main(String[] args) <

Mark md = new Mark (71.4D);//71.5d

Mark mi = new Mark (71);

В результате будет выведено:

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

Для generic-типов существует целый ряд ограничений. Например, невозмож­но выполнить явный вызов конструктора generic-типа:

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

По аналогичным причинам generic-поля не могут быть статическими, статические методы не могут иметь generic-параметры или обращаться к generic-полям, например:

/*пример # 12 : неправильное объявление полей параметризованного класса: Failed.java */

Источник

Пришел, увидел, обобщил: погружаемся в Java Generics

Java Generics — это одно из самых значительных изменений за всю историю языка Java. «Дженерики», доступные с Java 5, сделали использование Java Collection Framework проще, удобнее и безопаснее. Ошибки, связанные с некорректным использованием типов, теперь обнаруживаются на этапе компиляции. Да и сам язык Java стал еще безопаснее. Несмотря на кажущуюся простоту обобщенных типов, многие разработчики сталкиваются с трудностями при их использовании. В этом посте я расскажу об особенностях работы с Java Generics, чтобы этих трудностей у вас было поменьше. Пригодится, если вы не гуру в дженериках, и поможет избежать много трудностей при погружении в тему.

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

Работа с коллекциями

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

С появлением Generics необходимость в проверке и приведении типа отпала:

Во второй строчке проверки необходимость тоже отпадала. Если потребуется, приведение типов ( casting ) будет сделано на этапе компиляции.

Принцип подстановки

ТипПодтип
NumberInteger
ListArrayList
CollectionList
IterableCollection

Примеры отношения тип/подтип

Вот пример использования принципа подстановки в Java:

Ковариантность, контравариантность и инвариантность

Но если мы попытаемся изменить содержимое массива через переменную arr и запишем туда число 42, то получим ArrayStoreException на этапе выполнения программы, поскольку 42 является не строкой, а числом. В этом недостаток ковариантности массивов Java: мы не можем выполнить проверки на этапе компиляции, и что-то может сломаться уже в рантайме.

«Дженерики» инвариантны. Приведем пример:

Wildcards

Всегда ли Generics инварианты? Нет. Приведу примеры:

Это ковариантность. List — подтип List

что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать javaextends B — символ подстановки с указанием верхней границы
super B — символ подстановки с указанием нижней границы
где B — представляет собой границу

2. Почему нельзя получить элемент из списка ниже?

The Get and Put Principle или PECS (Producer Extends Consumer Super)

Особенность wildcard с верхней и нижней границей дает дополнительные фичи, связанные с безопасным использованием типов. Из одного типа переменных можно только читать, в другой — только вписывать (исключением является возможность записать null для extends и прочитать Object для super ). Чтобы было легче запомнить, когда какой wildcard использовать, существует принцип PECS — Producer Extends Consumer Super.

и Raw типы

Если мы опустим указание типа, например, как здесь:

Если мы попытаемся вызвать параметризованный метода у Raw типа, то компилятор выдаст нам предупреждение «Unchecked call». Если мы попытаемся выполнить присваивание ссылки на параметризованный тип Raw типу, то компилятор выдаст предупреждение «Unchecked assignment». Игнорирование этих предупреждений, как мы увидим позже, может привести к ошибкам во время выполнения нашего приложения.

Wildcard Capture

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

Более подробно о Wildcard Capture можно прочитать здесь и здесь.

Вывод

Переменные типа

Вот еще пример из класса Enum:

Multiple bounds (множественные ограничения)

Вывод

Переменная типа может быть ограничена только сверху одним или несколькими типами. В случае множественного ограничения левая граница (первое ограничение) используется в процессе затирания (Type Erasure).

Type Erasure

На скриншоте ниже два примера программы:
что нельзя параметризовать java. Смотреть фото что нельзя параметризовать java. Смотреть картинку что нельзя параметризовать java. Картинка про что нельзя параметризовать java. Фото что нельзя параметризовать java

Разница между ними в том, что слева происходит compile-time error, а справа все компилируется без ошибок. Почему?

Reifiable типы

Почему информация об одних типах доступна, а о других нет? Дело в том, что из-за процесса затирания типов компилятором информация о некоторых типах может быть потеряна. Если она потерялась, то такой тип будет уже не reifiable. То есть она во время выполнения недоступна. Если доступна – соответственно, reifiable.

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

И еще одна задачка. Почему в примере ниже нельзя создать параметризованный Exception?

Каждое catch выражение в try-catch проверяет тип полученного исключения во время выполнения программы (что равносильно instanceof), соответственно, тип должен быть Reifiable. Поэтому Throwable и его подтипы не могут быть параметризованы.

Unchecked Warnings

Компиляция нашего приложения может выдать так называемый Unchecked Warning — предупреждение о том, что компилятор не смог корректно определить уровень безопасности использования наших типов. Это не ошибка, а предупреждение, так что его можно пропустить. Но желательно все-так исправить, чтобы избежать проблем в будущем.

Heap Pollution

Как мы упомянули ранее, присваивание ссылки на Raw тип переменной параметризованного типа, приводит к предупреждению «Unchecked assignment». Если мы проигнорируем его, то возможна ситуация под названием » Heap Pollution » (загрязнение кучи). Вот пример:

В строке (1) компилятор предупреждает об «Unchecked assignment».

Рассмотрим еще один пример:

Java разрешает выполнить присваивание в строке (1). Это необходимо для обеспечения обратной совместимости. Но если мы попытаемся выполнить метод add в строке (2), то получим предупреждение Unchecked call — компилятор предупреждает нас о возможной ошибке. В самом деле, мы же пытаемся в список строк добавить целое число.

Reflection

Хотя при компиляции параметризованные типы подвергаются процедуре стирания (type erasure), кое-какую информацию мы можем получить с помощью Reflection.

С появлением Generics класс java.lang.Class стал параметризованным. Рассмотрим вот этот код:

Вывод

Если информация о типе доступна во время выполнения программы, то такой тип называется Reifiable. К Reifiable типам относятся: примитивные типы, непараметризованные типы, параметризованные типы с неограниченным символом подстановки, Raw типы и массивы, элементы которых являются reifiable.

Игнорирование Unchecked Warnings может привести к «загрязнению кучи» и ошибкам во время выполнения программы.

Reflection не позволяет получить информацию о типе объекта, если он не Reifiable. Но Reflection позволяет получить информацию о типе возвращаемого методом значения, о типе аргументов метода и о типе полей класса.

Type Inference

Термин можно перевести как «Вывод типа». Это возможность компилятора определять (выводить) тип из контекста. Вот пример кода:

С появлением даймонд-оператора в Java 7 мы можем не указывать тип у ArrayList :

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

Результат обобщенного метода List.nil() может быть выведен из правой части:

Механизм выбора типа компилятором показывает, что аргумент типа для вызова List.nil() действительно String — это работает в JDK 7, все хорошо.

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

В JDK 7 мы получили бы compile-time error. А в JDK 8 скомпилируется. Это и есть первая часть JEP-101, его первая цель — вывод типа в позиции аргумента. Единственная возможность осуществить это в версиях до JDK 8 — использовать явный аргумент типа при вызове обобщенного метода:

Вторая часть JEP-101 говорит о том, что неплохо бы выводить тип в цепочке вызовов обобщенных методов, например:

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

После выхода JEP 101 на StackOverflow появилось множество вопросов по теме. Программисты спрашивают, почему код, который выполнялся на 7-й версии, на 8-й выполняется иначе – или вообще не компилируется? Вот пример такого кода:

Посмотрим на байт-код после компиляции на JDK1.8:

А теперь байт-код после компиляции на JDK1.7 – то есть на Java 7:

Чтобы избежать таких проблем, Oracle выпустил руководство по переходу с JDK1.7 на JDK 1.8 в котором описаны проблемы, которые могут возникнуть при переходе на новую версию Java, и то, как эти проблемы можно решить.

Например если вы хотите, чтобы в коде выше после компиляции на Java 8 все работало так же, как и на Java 7, сделайте приведение типа вручную:

Заключение

На этом мой рассказ о Java Generics подходит к концу. Вот другие источники, которые помогут вам в освоении темы:

Источник

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

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