Главная страница
Навигация по странице:

  • Collection c; T[] ta; new T[10];

  • List myList1 = new ArrayList ();

  • Box b = new Box (); String x = b.getDefault(); преобразуется вBox b = new Box(); String x = (String) b.getDefault();

  • Class classInt = Integer.class; Class classInt2 = Integer.class;

  • List list = new ArrayList();

  • List arrayLists = new ArrayList (); ArrayList arrayList = new ArrayList ();

  • Wildcard ( )

  • List ints = nums;

  • Принцип PECS . Принцип PECS - Producer Extends Consumer Super

  • Map getAll(Collection keys);

  • String str = new String("Test!"); Object obj = str;

  • Generics и коллекции. 6 блок. Generics. Collections


    Скачать 0.76 Mb.
    Название6 блок. Generics. Collections
    АнкорGenerics и коллекции
    Дата15.07.2022
    Размер0.76 Mb.
    Формат файлаdocx
    Имя файлаJava 6 module.docx
    ТипДокументы
    #631266
    страница1 из 3
      1   2   3

    6 БЛОК. Generics. Collections.

    ДЖЕНЕРИКИ

    1. Что было до дженериков? (Object) (Raw Types)

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

    Другой пример - коллекция принимает список объектов, этот список хранит не только String, но и Integer.

    Компилятор не увидит ничего плохого. Мы получим ошибку уже ВО ВРЕМЯ ВЫПОЛНЕНИЯ (в Runtime).

    Так же если не делать проверку (instanceof) на принадлежность к классу, то иногда можно получить ClassCastException –аварийное завершение программы.

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

    2. Что такое дженерики? Какую проблему они решают?

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

    Дженерики — это типы с параметром.

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

    При создании дженерика ты указываешь не только его тип, но и тип данных, с которыми он должен работать. – List<String> myList1 = new ArrayList<>();

    Особенность такого списка заключается в том, что в него нельзя будет «запихивать» все подряд: он работает исключительно с объектами String.

    Что можно параметризировать Дженериками?

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

    - Классы, интерфейсы или методы, имеющие дело с параметризованными типами, называются параметризованными или обобщениями, параметризованными (обобщёнными) классами или параметризованными (обобщёнными) методами.

    Подробнее можно посмотреть тут!

    Ограничения Generic?

    Дженерики работают только с объектами! Нельзя использовать примитивные типы.

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

    Нельзя создавать обобщенные статические переменные и методы. Но объявить статические обобщенные методы со своими параметрами типа всё же можно:
    Невозможно создать массив параметра типа

    Collection c;

    T[] ta;

    new T[10]; // Ошибка !!
    Невозможно создать массив Generic-классов

    new ArrayList>();

    List[] la = new List[10]; // Ошибка !!

    GenericSub[] gens = new GenericSub[10]; // Ошибка !!

    3. Какие типы дженериков бывают? (Generic Types и Raw Types)

    В дженериках есть две категории: типизированные типы (Generic Types) и "сырые" типы (Raw Types).

    При использовании классов-дженериков ни в коем случае не превращай generic type в raw type.
    Generic Types - Типизированные типы — с указанием "уточнения":

    List myList1 = new ArrayList<>(); Generic Types — это класс-дженерик, с указанием типа.

    Обеспечивает безопасность. В этот список нельзя будет «запихивать» все подряд: он работает исключительно с объектами String.
    Raw Types - Сырые типы — это типы без указания "уточнения" в фигурных скобках (angle brackets):

    List myList1 = new ArrayList<>(); Raw type — это класс-дженерик, из которого удалили его тип.

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

    Как следствие, мы теряем безопасность типов, если используем необработанный тип, такой как List , но не если вы используете параметризованный тип, такой как List .
    У тебя мог возникнуть вопрос: а почему в языке вообще позволено использовать Raw types?

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

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

    И если бы в процессе перевода мы помещали в байт-код информацию о типах-параметрах, это сломало бы весь ранее написанный код, ведь до Java 5 никаких типов-параметров не существовало!

    При работе с дженериками есть одна очень важная особенность, о которой необходимо помнить. Она называется “стирание типов” (type erasure). Подробнее в 8 пункте.

    4. Хоть где-то можно и нужно использовать Raw Types в своём коде без риска получить ошибку?

    К сожалению, дженерики Java не являются овеществленными, есть два исключения, когда необработанные типы (Raw Types) должны использоваться в новом коде:

    - Литералы класса, например List.class, а не List.class.

    - Операнд instanceof , например o instanceof Set, а не o instanceof Set.

    5. Чем необработанный тип (Raw Types) отличается от использования в качестве параметра типа?

    List , List и т. д. — Это все List,поэтому может возникнуть соблазн просто написать, что они List. Однако есть существенное различие: поскольку List определяет только add(String), вы не можете добавить к List просто любой произвольный объект. С другой стороны, поскольку необработанный тип List не имеет безопасности типа, вы можете add() практически всё, что угодно, в List.
    Рассмотрим следующий вариант с использованием метода:

    static void appendNewObject(List list) {

    list.add(new Object()); // compilation error!

    }

    List names = new ArrayList();

    appendNewObject(names); // this part is fine!

    Компилятор проделал замечательную работу по защите вас от потенциального нарушения инвариантности типа List ! Если бы вы объявили в методе параметр как необработанный тип List list, то код компилировался бы, и вы нарушили бы инвариант типа List names.

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

    Стирание типов в runtime. Дженерики позволяют проверять типы во время компиляции. Во время исполнения программы информации о типах не доступна.

    7. Понятие терминов Non-Reifiable Types и Reifiable Types?

    - Reifiable-type — это тип, информация о котором полностью доступна во время выполнения.

    В языке Java к ним относятся примитивы, raw-types, а также типы, не являющиеся дженериками.

    - Non-ReifiableTypes — это типы, информация о которых стирается и становится недоступной во время выполнения. Это как раз дженерики — List, List и т.д.

    (Отсюда и проблема со стиранием типа).

    Так же создание массивов из Non-Reifiable Type в Java запрещено.

    Подробнее можно прочитать тут!

    8. Что такое стирание типа в дженериках? (Всё очень запутанно)

    Cтирание типов относится к процессу загрузки, с помощью которого явные аннотации типов удаляются из программы перед её выполнением во время выполнения. Соответственно стирание типа происходит во время компиляции.
    Весь написанный тобой Java-код превратится в байт-код, в нём не будет информации о типах-параметрах.

    Внутри байт-кода твой список List cats не будет отличаться от List strings. В байт-коде ничто не будет говорить о том, что cats — это список объектов Cat. Информация об этом сотрется во время компиляции, и в байт код попадет только информация о том, что у тебя в программе есть некий список List cats. Такая петруха.
    Дженерики реализуются в Java с помощью обмана компилятора. Скомпилированный универсальный код на самом деле просто использует java.lang.Object везде, где вы говорите о T (или каком - то другом параметре типа) - и есть некоторые метаданные, которые говорят компилятору, что это действительно универсальный тип.

    Когда вы компилируете код универсального типа или метода, компилятор вычисляет, что вы на самом деле имеете в виду (т. е. что такое аргумент типа для T) и проверяет во время компиляции , что вы делаете правильно, но испускаемый код снова просто говорит в терминах java.lang.Object - компилятор генерирует дополнительные приведения там, где это необходимо. Во время выполнения List и List абсолютно одинаковы; дополнительная информация о типе была стёрта компилятором. Например, Listбудет преобразован в не универсальный тип List, который обычно содержит произвольные объекты.
    Из-за стирания типа параметры типа не могут быть определены во время выполнения. Например, когда ArrayList рассматривается во время выполнения, не существует общий способ определить, является ли, (до того стирания типа), ArrayListили ArrayList. Многие недовольны этим ограничением. Есть частичные подходы. Например, можно исследовать отдельные элементы, чтобы определить, к какому типу они принадлежат; например, если ArrayList содержит Integer, то этот ArrayList мог быть параметризирован Integer(однако он мог быть параметризирован любым родительским элементом Integer, например Number или Object).

    Подробнее можно прочитать тут!

    5. Если поле типизировано дженериком как в байт коде будет представлен этот тип?

    Box b = new Box();

    String x = b.getDefault();

    преобразуется в

    Box b = new Box();

    String x = (String) b.getDefault();
    Стирание типов означает выбрасывание тегов типов, созданных во время разработки, или выведенных тегов типов во время компиляции таким образом, чтобы скомпилированная программа в двоичном коде не содержала никаких тегов типов. И это относится ко всем языкам программирования, компилируемым в двоичный код, за исключением некоторых случаев, когда вам нужны теги времени выполнения. Эти исключения включают, например, все экзистенциальные типы (Java ссылочные типы, которые являются подтипами, любой тип во многих языках, типы объединения). Причина стирания типов заключается в том, что программы преобразуются в язык, который в некотором роде является однотипным (двоичный язык допускает только биты), поскольку типы являются только абстракциями и утверждают структуру для своих значений и соответствующую семантику для их обработки.

    Как можно обойти стирание типов?

    В Java есть класс Class. Используя его, мы можем получить класс любого нашего объекта:

    Class classInt = Integer.class;

    Class classInt2 = Integer.class; //ошибка компиляции!

    для объекта Integer.class является не просто Class, а Class. Типом объекта string.class является не просто Class, Class, и т.д.
    Мы просто передаём нужный класс-параметр в конструктор нашего класса-дженерика:

    TestClass testString = new TestClass<>(MySecretClass.class);

    Благодаря этому мы сохранили информацию о типе-параметре и уберегли её от стирания. В итоге мы смогли создать объект T! :)

    9. Что такое даймонд оператор? Примеры использования?  (<>)

    Diamond <> применчяется только вместе с new

    List myList1 = new ArrayList<>();

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

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

    List list = new ArrayList();

    Однако это будет смешиванием нового стиля с дженериками и старого стиля без них. И это крайне нежелательно. При компиляции кода выше мы получим сообщение: Note: HelloWorld.java uses unchecked or unsafe operations.
    У ArrayList есть конструктор, который принимает на вход коллекцию.

    И вот тут-то и кроется коварство. Без diamond <> синтаксиса компилятор не понимает, что его обманывают, а вот с diamond — понимает.

    Поэтому, правило #1: всегда использовать diamond синтаксис, если мы используем типизированные типы. В противном случае мы рискуем пропустить, где у нас используется raw type.

    3. Почему последняя строчка не скомпилируется?

    List arrayLists = new ArrayList();

    ArrayList arrayList = new ArrayList();

    Потому что параметры типов у этих дженериков не совпадают!

    Wildcard ()

    1. Что такое wildcard?

    Для решения проблемы совместимости используется Wildcard («?»). Он не имеет ограничения в использовании (то есть имеет соответствие с любым типом) и в этом его плюсы.

    Благодаря Wildcard можно сделать универсальный метод (или переменную), работающий с разными типами данных.
    List ints = nums;

    Запись вида или — называется wildcard или символом подстановки, с верхней границей (extends) или с нижней границей (super).

    Или просто . Принцип PECS.

    Принцип PECS - Producer Extends Consumer Super

    1. Принцип PECS, что это? (нужен для проектировки удобного API)

    Если метод имеет аргументы с параметризованным типом,то в случае, если аргумент - производитель (producer), нужно использовать ? extends T, а если аргумент - потребитель (consumer), нужно использовать ? super T.

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

    Пример:Map getAll(Collection keys);
    В случае, если аргумент является и потребителем, и производителем одновременно - например, если метод одновременно и читает из коллекции, и пишет в неё (плохой стиль, но всякое бывает) - тогда его нужно оставить как есть .

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

    Производителями могут быть не только коллекции. Самый очевидный пример производителя — это фабрика:

    interface Factory {

    T create(Object... args); // Создаёт новый экземпляр объекта заданного типа и возвращает его.

    }

    Хорошим примером аргумента, являющегося и производителем, и потребителем, будет аргумент вот такого типа:

    interface Cloner {

    T clone(T obj); // Клонирует исходный obj и возвращает копию

    }

    Между объектом и коллекцией объектов есть важное различие, какое?

    String str = new String("Test!");

    Object obj = str; // никаких проблем
    List strings = new ArrayList();

    List objects = strings; // ошибка компиляции!

    Если класс B является наследником класса А, то Collection при этом — не наследник Collection.

    Именно по этой причине мы не смогли привести наш List к List. String является наследником Object, но List не является наследником List.

    Если бы этого не было, то мы лишились гарантии того, что в нашей коллекции находятся только указанные в дженерике объекты String. То есть, мы потеряли главное преимущество дженериков — типобезопасность.
      1   2   3


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