Главная страница

6 Дженерики, коллекции. Что такое дженерики


Скачать 360 Kb.
НазваниеЧто такое дженерики
Дата12.10.2022
Размер360 Kb.
Формат файлаdocx
Имя файла6 Дженерики, коллекции.docx
ТипДокументы
#729663
страница1 из 3
  1   2   3

Что такое дженерики

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

До Java 5 проблема решалась типизацией данных как Object, но это несло ряд дополнительных трудностей (слабой типизацией), связанных с корректной обработкой таких данных и все это всплывало уже в рантайме, а с применением дженериков проверка типов осуществляется во время компиляции.

*Дженерики инвариантны – иерархия наследования не сохраняется для производных типов (полиморфизм не работает). Это устраняет проблему слабой типизации.

Смысл дженериков - реализация обобщенного программирования
Что было до дженериков

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

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

Можно типизировать только ссылочные типы.

Параметризация позволяет создавать классы, интерфейсы и методы, в которых тип

обрабатываемых данных задается как параметр

Первое ограничение- Можно типизировать только ссылочные типы.

Второе серьёзное ограничение - Внутри параметризованного класса или метода нельзя создавать экземпляр или массив T, так же не работает проверка instanceof

Также невозможно выполнить явный вызов конструктора generic-типа:

class Optional {T value = new T();}

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

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

могут иметь generic-параметры или обращаться к generic-полям

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

Классы, не поддерживающие параметризацию, наследников Throwable (из-за Type Erasure в рантайме трай-кэтч не будет знать с каким типом ему работать), анонимные классы enums.
Что могу параметризовать?

Метод, класс, интерфейс, конструктор.
Что такое стирание и сырые типы (raw type - необработанный тип)

При преобразовании в байт-код информация о типах параметрах стирается (расширяется до типа Object) и в рантайме информации об этих типах уже нет. Это сделано из соображений поддержки обратной совместимости.
Что такое raw type? К чему приводит использование raw type?

Raw type — это класс-дженерик, из которого удалили его тип (List list = new ArrayList();). Многие ранние версии классов стандартной библиотеки могут реализовываться как «сырые» типы, это связано с обратной совместимостью. Использовать такие типы в новом коде будет неразумно. При присваивании ссылки параметризованного типа Raw типу компилятор выдаст warning «Unchecked assigment», а при вызове параметризованного метода у Raw типа выдаст «Unchecked call».
Почему в языке вообще позволено использовать raw types?

Создатели Java оставили в языке поддержку raw types чтобы не создавать проблем с совместимостью. К моменту выхода Java 5.0 (в этой версии впервые появились дженерики) было написано уже очень много кода с использованием raw types. Поэтому такая возможность сохраняется и сейчас.
Что такое даймонд оператор

Оператор diamond – введенный в Java 1.7 – добавляет вывод типов в компилятор и уменьшает многословие в назначениях – при использовании дженериков: List cars = new ArrayList<>();

Даймон оператор - это <> без объявления типа, данный оператор связан с процессом type inference – выводом типа из контекста (из типа переменной, ссылающийся на данный объект). *Работает только с new
В чем разница между сырым типом и даймонд-оператором?

<> - даймонд оператор - синтаксический сахар; когда компилятор сам выводит тип правой части исходя из контекста, но контекстом не всегда является левая часть выражения (может быть возвращаемое значение метода, т.е. то, что должно вернуться из левой части). Дает компилятору 100% гарантию, что будет тот же тип.

Сырой тип (raw type). Возвращает предупреждения.
В чем разница между и ?

и являются синонимами

super Number> - ограничен классом Number. можем в реализации записать как , но не сможем как
Какой механизм обеспечивает обратную совместимость сырых типов и дженериков

Стирание типов. См выше
Если поле типизировано дженериком как в байт коде будет представлен этот тип Object
Параметр vs Аргумент. (в дженериках)

Параметр – это тип данных, которыми оперируют классы, интерфейсы указанные в дженериках. Только ссылки.

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

Можно ли объявить так: class Animal {} нет – ошибка компиляции
Что мы можем подставить в метод с параметром , если класс параметризован как Class ?
Чему эквивалентно
Параметризация статических методов

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

* Ещё один случай, когда может понадобиться явно параметризовать метод - это нестатический метод, параметр типа которого должен отличаться от параметра типа класса
Зачем статическому методу нужна дополнительная параметризация, почему он не может взять параметр от класса, как обычный метод?

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

public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2).
Wildcard

- это символ подстановки , (верхняя граница), (нижняя граница).

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

List arrayLists = new ArrayList();

ArrayList<List> arrayList = new ArrayList<ArrayList>();

ошибка Incompatible types – коллекции и дженерики инвариантны
Принцип PECS

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

Если мы объявили wildcard с extends, то это producer. Он только «продюсирует», предоставляет элемент из контейнера, а сам ничего не принимает.

Если же мы объявили wildcard с super — то это consumer. Он только принимает, а предоставить ничего не может.
Если метод имеет аргументы с параметризованным типом (например, Collection или Predicate), то в случае, если аргумент — производитель (producer), нужно использовать ? extends T, а если аргумент — потребитель (consumer), нужно использовать ? super T.
если метод читает данные из аргумента, то этот аргумент — производитель, а если метод передаёт данные в аргумент, то аргумент является потребителем. Важно заметить, что определяя производителя или потребителя, мы рассматриваем только данные типа T.

Иерархия коллекций

На вершине в иерархии Java Collection Framework располагаются два интерфейса Collection и Map. Эти интерфейсы разделяют все коллекции, входящие во фреймворк на две части по типу хранения данных: простые последовательные наборы элементов и наборы пар «ключ-значения» (словари). Классы и интерфейсы Collection Framework находятся в пакете java.util




Отличие коллекции от массива

  • В коллекциях реализовано автоматическое динамическое расширение размера, а массивы имеют фиксированный размер, заданный при создании.

  • Массивы ковариантны. Number[] arr = new Integer[10] можно, но если положить в массив какой-нибудь Double - в рантайме получим ArrayStoreException.

  • Массивы не имеют защиты на изменение, если массив доступен на чтение – он также доступен для записи (final запрещает только менять ссылку на массив). Коллекции могут разрешать только чтение (? extends … или через обертку – коллекцию полученную через Collections.unmodifiableSet).

  • В коллекциях реализован широкий набор инструментов для работы с хранящимися данными (equals, toString, методы объединения вычитания и пересечения данных и тд)

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

Что такое коллекция.

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

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

Collection — этот интерфейс находится в составе JDK c версии 1.2 и определяет основные методы работы с простыми наборами элементов, которые будут общими для всех его реализаций (например size(), isEmpty(), add(E e) и др.). Интерфейс был слегка доработан с приходом дженериков в Java 1.5.
Внутреннее устройство коллекций
Может ли коллекция хранить примитивы?

Примитивы хранить нельзя, для этого есть специальные реализации коллекций, например Trove library или PCJ(Primitive Collections for Java)
Какие структуры данных вы знаете

Структура данных – это контейнер, который хранит информацию в определенном виде.

Массив (Array)

Стек (Stack)

Очередь (Queue)

Связный список (Linked List)

Дерево (Tree)

Граф (Graph)

Префиксное дерево (Trie)

Хэш-Таблица (Hash Table)
Как добавить новый элемент в массив, если массив заполнен?

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

С помощью классов оберток, т.к. примитивы имеют свои классы-обертки, чтобы можно было работать с ними как с объектами.
Можно ли с помощью цикла for each изменить поле объектов, которые находятся в массиве? И можно ли изменить сам объект?

Поле изменить можно если оно не final. Объект изменить нельзя, тк отсутствует доступ к индексу объекта

Элемент из коллекции в цикле for-each удалить нельзя, т.к. нельзя проводить одновременно итерацию (перебор) коллекции и изменение ее элементов. Итератор этого учесть не может.

Значения элементов массива менять можно!

Интерфейс Set



Представляет собой неупорядоченную коллекцию, которая не может содержать дублирующиеся данные (хранит только уникальные элементы). Является программной моделью математического понятия «множество».
3 реализации Set

HashSet — реализация интерфейса Set, базирующаяся на HashMap. Внутри использует объект HashMap для хранения данных. В качестве ключа используется добавляемый элемент, а в качестве значения — объект-пустышка (new Object()). Из-за особенностей реализации порядок элементов не гарантируется при добавлении.
LinkedHashSet — расширяет класс HashSet, отличается от HashSet только тем, что в основе лежит LinkedHashMap вместо HashMap. Благодаря этому отличию порядок элементов при обходе коллекции является идентичным порядку добавления элементов. Это позволяет организовать упорядоченную итерацию вставки в набор.
TreeSet — аналогично другим классам-реализациям интерфейса Set содержит в себе объект NavigableMap (древо для хранения), что и обуславливает его поведение. Предоставляет возможность управлять порядком элементов в коллекции при помощи объекта Comparator, либо сохраняет элементы с использованием "natural ordering" (лексикографический порядок или по возрастанию значений)
Интерфейс SortedSet предназначен для создания коллекций, который хранят элементы в отсортированном виде (сортировка по возрастанию). SortedSet расширяет интерфейс Set, поэтому такая коллекция опять же хранит только уникальные значения.

Интерфейс NavigableSet расширяет интерфейс SortedSet и позволяет извлекать элементы на основании их значений
Обобщенный класс TreeSet представляет структуру данных в виде дерева, в котором все объекты хранятся в отсортированном виде по возрастанию. TreeSet является наследником класса AbstractSet и реализует интерфейс NavigableSet, а следовательно, и интерфейс SortedSet.
Как работает HashSet.

базируется на HashMap. Внутри использует объект HashMap для хранения данных. В качестве ключа используется добавляемый элемент, а в качестве значения — объект-пустышка (new Object()), методы делегируются методам HashMap.

  • Т.к. класс реализует интерфейс Set, он может хранить только уникальные значения;

  • Может хранить NULL – значения;

  • Порядок добавления элементов вычисляется с помощью хэш-кода (Класс Object имеет метод hashCode(), который используется классом HashSet для эффективного размещения объектов, заносимых в коллекцию);

  • HashSet также реализует интерфейсы Serializable и Cloneable.


Что кладется на место значения в HashSet — объект-пустышка (new Object())

Почему в HashSet вместо value не null, а (new Object())

HashSet хранит (new Object()), потому что в контракте указывается метод remove(), который возвращает true, если указанный объект существовал и был удален. Для этого он использует обертку, HashMap#remove() который возвращает удаленное значение. Если бы вы сохранили null вместо объекта, то вызов HashMap#remove() вернул бы бы null, что было бы неотличимо от результата попытки удалить несуществующий объект, и контракт HashSet.remove() не мог бы быть выполнен.
  1   2   3


написать администратору сайта