какой механизм обеспечивает обратную совместимость сырых типов и дженериков java

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

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

какой механизм обеспечивает обратную совместимость сырых типов и дженериков java

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

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

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

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

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

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

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

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

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

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

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

Wildcards

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

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

какой механизм обеспечивает обратную совместимость сырых типов и дженериков 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

Разница между ними в том, что слева происходит 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 подходит к концу. Вот другие источники, которые помогут вам в освоении темы:

Источник

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

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

какой механизм обеспечивает обратную совместимость сырых типов и дженериков java

какой механизм обеспечивает обратную совместимость сырых типов и дженериков java

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

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

Содержание

какой механизм обеспечивает обратную совместимость сырых типов и дженериков java

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

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

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

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

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

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

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

какой механизм обеспечивает обратную совместимость сырых типов и дженериков 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

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

Мы не успели разобраться с более сложными вещами — например, с заменами аргументов типов в классах-наследниках, с переопределением дженерик-методов, не узнали об особенностях коллекций с 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

Что общего

Когда дело доходит до дженериков, каждый не будет незнакомым, в нашем коде есть много таких утверждений:

ArrayList является универсальным классом. Устанавливая различные типы, мы можем хранить различные типы типов данных в коллекции (и можем хранить только установленные типы данных, что является одним из преимуществ универсальных типов). «Универсальный» просто означает универсальный тип (параметризованный тип). Представьте себе такой сценарий: если мы должны были написать контейнерный класс (поддерживающий добавление, удаление и запрос данных), мы бы написали тип, который поддерживает тип String, а позже нам нужно написать тип, который поддерживает тип Integer. Тогда что? Doubel, Float, различные пользовательские типы? Это дублирование кода слишком много, и алгоритмы этих контейнеров согласованы. Мы можем ссылаться на тип T вместо всех необходимых нам ранее типов и передавать нужный нам тип в качестве параметра контейнеру, так что нашему алгоритму нужно только написать набор для адаптации ко всем типам. Наиболее типичным примером является ArrayList. Этот набор работает хорошо независимо от того, какой тип данных мы передаем.
Прочитав приведенное выше описание, умные одноклассники написали кусок кода:

Этот код очень гибкий, и все типы могут быть преобразованы в класс Object, поэтому мы можем хранить в нем различные типы данных. Действительно, Java сделала это до появления дженериков. Но есть проблема с этим: если в коллекции много данных, возникает ошибка при определенном преобразовании данных, и ее нельзя найти во время компиляции. Но java.lang.ClassCastException происходит во время выполнения. Например:

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

Введение в Java Generics

Давайте познакомимся с соответствующим контентом Java-дженериков. Будут представлены следующие аспекты:

Универсальные классы Java

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

Универсальные методы Java

Ранее мы ввели дженерики для всего класса, теперь мы ввели дженерик-методы. Универсальные методы могут существовать как в общих, так и в обычных классах. Если использование универсального метода может решить проблему, вы должны попытаться использовать универсальный метод, когда это возможно. Давайте посмотрим на использование универсальных методов на примерах:

Давайте посмотрим на результаты:

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

Этот универсальный метод все еще может передавать данные типов Double, Float и т. Д. Параметр типа T в универсальном методе и параметр типа в универсальном классе являются разными типами. Из приведенного выше метода вызова мы также видим, что универсальный метод printInfo не подчиняется параметру универсального типа в нашем DataHolder, являющемся String. воздействие. Давайте суммируем некоторые основные характеристики универсальных методов:

Универсальный интерфейс Java

Определение универсального интерфейса Java в основном совпадает с универсальным классом Java. Вот пример:

Здесь следует отметить две вещи:

Из этого примера мы видим, что все T мест в классе реализации должны быть реализованы как String.

Универсальное стирание Java и связанный контент

Давайте посмотрим на пример:

Мы смотрим на вывод и находим, что class1 и class2 на самом деле имеют одинаковый тип ArrayList, а переменные типа String и Integer, которые мы передали во время выполнения, отбрасываются. Обобщения языка Java разработаны для совместимости со старым кодом.В механизме обобщения языка Java используется механизм «стирания». Давайте посмотрим на более тщательный пример:

В приведенном выше коде мы хотим получить параметры типа класса во время выполнения, но мы видим, что возвращаются все «формальные параметры». Во время выполнения мы не можем получить информацию о типе, которая была объявлена.
Примечание.
Хотя компилятор удаляет информацию о типах параметров во время компиляции, он обеспечивает согласованность типов параметров внутри класса или метода.
Общий параметр будет удален до его первой границы (может быть несколько границ, можно повторно использовать ключевое слово extends, и он может добавить границу к типу параметра). Компилятор фактически заменяет параметр типа на тип его первой границы. Если граница не указана, параметр типа будет удален в Object. В следующем примере универсальный параметр T может использоваться как тип HasF.

Информация о типе, которая следует за ключевым словом extension, определяет, какую информацию может сохранить общий параметр. Стирание типа Java будет стирать только для типов HasF.

Принцип универсального стирания Java

Давайте рассмотрим это на примере. Сначала рассмотрим неуниверсальную версию:

Ниже мы даем общую версию с точки зрения байт-кода:

Во время компиляции доступна информация о переменных типа. Следовательно, метод set может выполнять проверку типов в компиляторе, и недопустимые типы не могут быть скомпилированы. Но для метода get, благодаря механизму стирания, реальным ссылочным типом во время выполнения является тип объекта. Чтобы «вернуть» тип возвращаемого результата, компилятор добавил преобразование типа после get. Следовательно, в файле GenericHolder.class строка 18 основного метода имеет логику преобразования типов. Он автоматически добавляется компилятором для нас.
Таким образом, мы имеем дело с местом чтения и записи универсального объекта класса и добавляем ограничения в код.

Дефекты и способы устранения универсального стирания Java

Универсальные типы нельзя явно использовать в операциях с типами среды выполнения, такими как: cast, instanceof и new. Потому что во время выполнения информация о типе всех параметров теряется. Код, подобный следующему, не скомпилируется:

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

Проблема суждения типа

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

В основном методе мы можем назвать это так:

Мы записываем объект Class параметра type, а затем используем этот объект Class для оценки типа.

Создать экземпляр типа

Существует две причины, по которым new T () нельзя использовать в универсальном коде: одна из них заключается в том, что тип не может быть определен из-за стирания, невозможно определить, содержит ли T конструктор без параметров.
Чтобы избежать этих двух проблем, мы используем явный шаблон фабрики:

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

Создать универсальный массив

Обычно не рекомендуется создавать универсальные массивы. Попробуйте использовать ArrayList вместо универсальных массивов. Но вот способ создать универсальный массив.

Здесь мы все еще используем тип параметра, используя метод newInstance типа для создания экземпляра.

Универсальный шаблон Java

Подстановочный знак верхней границы

Давайте сначала посмотрим на пример:

Теперь мы определим класс пластины:

Ниже мы определяем тарелку с фруктами, теоретически в тарелке с фруктами могут быть яблоки.

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

В иерархии классов выше, Plate покрывает синюю часть ниже:

какой механизм обеспечивает обратную совместимость сырых типов и дженериков java

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

Вы обнаружите, что вы не можете установить данные в нем. Естественно, мы установили универсальный тип в? Extend Fruit. Разумеется, мы должны добавить к нему подклассы Fruit. Но компилятор Java не позволяет этого. делает недействительным метод set () для размещения вещей на тарелке. Но метод get () все еще действует
Причины:
Во время компиляции Java знает только, что Fruit и его производные классы хранятся в контейнере. Я не знаю, какой это тип. Может, это Fruit? Это может быть Apple? Это может быть банан, RedApple, GreenApple? После того, как компилятор увидит, что Plate назначен позже, пластина не помечается как «Apple». Он просто обозначает заполнитель «CAP # 1» для обозначения захвата Fruit или производного класса Fruit. Я не знаю, какой это тип. Весь вызывающий код не знает, может ли он соответствовать этому «CAP # 1», независимо от того, вставлен ли в контейнер компилятор Apple, Meat или Fruit, поэтому эти операции запрещены.
Но подстановочные знаки верхней границы разрешены для чтения. Пример кода:

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

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

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

какой механизм обеспечивает обратную совместимость сырых типов и дженериков javaПодстановочный знак нижней границы не влияет на хранилище, но считанные данные могут иметь только тип Object.
Причина:
Подстановочный знак нижней границы задает минимальную гранулярность элемента. Это должен быть T и его базовый класс, чтобы я мог хранить в нем T и его производные классы, поскольку он может быть неявно преобразован. Т тип. Но читать его нелегко, так как он хранит T и его базовые классы. Он не может быть преобразован ни в какой тип. Может быть установлен только базовый класс Object.

Принципы PECS

Наконец, мы кратко представляем принципы PECS, представленные в эффективной Java.

Бесконечный шаблон

Распространить новости

Tencent Cloud Год скидка, зарегистрируйтесь 500 баллов и получите 350 купонов на скидку, облачный сервер минимальная скидка 2%, 1 ядро ​​1G памяти 50G жесткий диск 1 год минимум 325 юаней!Ткните сюда для подробностей!

Источник

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

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