что нельзя параметризовать java
BestProg
Java. Обобщения (шаблоны). Параметризованные типы. Обобщенные классы
Содержание
Поиск на других ресурсах:
1. Что такое «обобщение» (шаблон) в языке Java? Что такое параметризованный тип? Особенности применения обобщений
Обобщение – это механизм построения программного кода для некоторого типа с произвольным именем с целью его дальнейшего конвертирования (преобразования) в другой конкретный ссылочный тип. Реализацию конвертирования из обобщенного типа в другой (конкретный) осуществляет компилятор.
2. Преимущества применения обобщений
Использование обобщений в языке Java дает следующие преимущества:
3. Общая форма объявления обобщенного класса и объявление ссылки на обобщенный класс
Чаще всего обобщенный класс оперирует с одним типом. В этом случае общая форма класса имеет вид:
Общая форма объявления ссылки на обобщенный класс следующая
Например, если в программе объявить класс, который получает параметром тип T
то в этом классе можно реализовывать переменные и методы, которые имеют тип T
После объявления, использование вышеприведенного класса для типа Integer будет следующим
В вышеприведенном объявлении тип Integer есть аргументом типа.
Создание экземпляра класса для типа Double следующее
Подобным образом обобщенный класс SomeClass может использоваться для любых других типов.
4. Какие типы запрещается использовать в обобщенных классах в качестве параметризованных типов?
То есть, если задан класс
то объявить экземпляр типа int или другого базового типа не удастся
5. Пример обобщенного класса, который реализует метод поиска элемента в двумерной матрице
Метод SearchKey() получает следующие параметры:
6. Пример реализации метода, который осуществляет циклический сдвиг в массиве обобщенного типа Type
Результат работы программы
7. Пример класса, который получает два параметризованных типа
Core Java
Курс лекций. Лекция 6
Иван Пономарёв, КУРС/МФТИ
До появления дженериков
После появления дженериков
Определяем собственный параметризованный класс
Определение и использование
Generic methods
Другой пример
Промежуточные выводы
Использование параметризованных классов — простое (просто укажите параметры, List )
Использование параметризованных методов — ещё проще: type inference: Manager m = getRandomItem(…);
Написание собственных параметризованных классов и методов — задачка более сложная.
Bounded types
Intersection types
Реализация дженериков
Появились в 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
Стирание типов до Object → невозможность использовать примитивные типы в качестве параметров
Примитивы и дженерики
День сегодняшний: нужна производительность? — пишем специальные реализации.
В стандартной библиотеке:
В специализированных библиотеках вроде fastutil:
HashMap → Int2ObjectMap (ВНИМАНИЕ: реальная потребность в таких библиотеках возникает редко!!)
День завтрашний: Project Valhalla, specialized generics. Решит проблему раз и навсегда.
Нельзя инстанцировать типы-параметры
Решается с помощью метакласса и рефлексии (о которой речь впереди)
Тем более нельзя инстанцировать массив типа-параметра
Решается передачей параметра, например, в ArrayList:
Массивы и дженерики — лютые враги
Забьём значения кувалдой и устроим heap pollution
Varargs — всё равно массив…
Тот же heap pollution, что и в прошлом примере:
Компилятор что-то предчувствует…
Чтобы успокоить компилятор, надо поставить аннотацию @SafeVarargs :
…и компилятор успокоится.
Зачем?!
Всё потому, что иметь varargs с параметризованными типами удобно.
Collections.addAll(Collection c, T… elements)
EnumSet.of(E first, E… rest)
Если вести себя хорошо, можно ставить @SafeVarargs, и всё будет хорошо:
Не записывать ничего в элементы массива,
Не раздавать ссылку на массив параметров наружу.
Нельзя параметризовать
ловля исключений — это проверка их типов,
дальше сырых типов мы в рантайме проверить не можем.
Инстанцируется по месту, не может быть несколько классов, параметризованных по-разному.
Параметры типов нельзя использовать в статическом контексте
Нельзя реализовывать разные параметризации одного и того же интерфейса
Source code
Compiled
Ковариантность массивов vs инвариантность дженериков
Реальная картина
Как быть, если хочется такого?
Так не получится…
Wildcard Types
В общем, addAllTo реализовать не получится…
В обратную сторону (контравариантные типы)
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 1.5 и призваны обезопасить код от неправильной типизации. Параметризируя класс, интерфейс или метод, можно добиться гибкости в переиспользовании алгоритмов, строгой проверки типов и упростить написание кода.
Без использования дженериков в код может пробраться ошибка типов:
Здесь мы случайно добавили в список число 3, а затем берём из списка строки. Код скомпилируется, но вот при запуске будет выдан ClassCastException на последней строке.
Перепишем с использованием дженериков:
Готовим дженерики
Для наглядности опишем гараж в терминах ООП. У нас будет некоторое транспортное средство:
Небольшая невнимательность, и вот вместо мотоцикла мы вывозим кучу металлолома марки «ClassCastException».
Параметризация класса
Для параметризации класса или интерфейса, необходимо добавить после имени класса. Вместо T можно использовать что-то другое, например V, VEHICLE, но обычно используют T, как сокрашение от Type. Это T можно будет подставлять в своём классе как тип объекта.
Перепишем класс Garage.
Какого чёрта в нашем гараже делает Юпитер? И почему это компилируется и работает? Ответ прост: мы не указали верхнюю границу типов.
Верхняя граница типов
Когда мы переписывали класс Garage с обычной версии на версию с дженериками, мы забыли о том, что изначально в гараже были объекты типа Vehicle. Нужно это исправить и указать, что T должно быть не любым объектом, а подклассом Vehicle.
Для это служит такая конструкция:
Пример с множественной параметризацией
Для примера сделаем трёхместный гараж.
Во втором случае, хоть мы и указали, что на первом месте должен быть Car, поставить Truck мы всё-таки можем, ведь он наследуется от Car. Но вот при получении объекта, Truck мы явно уже не получим, разве что явным приведением типа: Truck truck = (Truck) garage2.get1();
Дженерики и массивы
Теперь сделаем гараж, в который можно ставить множество транспортных средств одного типа. На ум, конечно же, приходит массив.
Дженерики и списки
Вместо массива можно использовать список. Поскольку интерфейс List параметризован, мы можем указать ему тип T и использовать в своей реализации гаража с переменным размером.
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
Давайте добавим ещё два метода. Первый будет заменять сразу несколько транспортных средств в гараже, а второй возвращать список транспортных средств, удовлетворяющих некоему критерию.
Параметризованные классы 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, чтобы этих трудностей у вас было поменьше. Пригодится, если вы не гуру в дженериках, и поможет избежать много трудностей при погружении в тему.
Работа с коллекциями
Предположим, банку нужно подсчитать сумму сбережений на счетах клиентов. До появления «дженериков» метод вычисления суммы выглядел так:
С появлением Generics необходимость в проверке и приведении типа отпала:
Во второй строчке проверки необходимость тоже отпадала. Если потребуется, приведение типов ( casting ) будет сделано на этапе компиляции.
Принцип подстановки
| Тип | Подтип |
| Number | Integer |
| List | ArrayList |
| Collection | List |
| Iterable | Collection |
Примеры отношения тип/подтип
Вот пример использования принципа подстановки в Java:
Ковариантность, контравариантность и инвариантность
Но если мы попытаемся изменить содержимое массива через переменную arr и запишем туда число 42, то получим ArrayStoreException на этапе выполнения программы, поскольку 42 является не строкой, а числом. В этом недостаток ковариантности массивов Java: мы не можем выполнить проверки на этапе компиляции, и что-то может сломаться уже в рантайме.
«Дженерики» инвариантны. Приведем пример:
Wildcards
Всегда ли Generics инварианты? Нет. Приведу примеры:
Это ковариантность. List — подтип List














