Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions lessons/java-core/028/Generics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Generics. Часть I

Если кто-то обратил внимание, в статье про полиморфизм было указано, что в Java он представлен в трех видах. Об одном из
них – параметрическом полиморфизме, а точнее, о синтаксисе, который Java для него предоставляет, мы будем говорить
сегодня и завтра. Урок разбит на две части.

Итак, **generic (дженерик, обобщенный тип)** – средство языка, позволяющее обрабатывать данные разных типов. Именно на
базе generic’ов работают **коллекции** в Java, с которыми уже знакомы некоторые из вас. Также на обобщенных типах
построено **функциональное программирование** в Java, с которым мы познакомимся в дальнейшем.

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

Класс, работающий с generic’ом, называется **параметризованным**.

В качестве базиса урока по обобщенным типам (включая завтрашний) можно использовать
[статью](https://metanit.com/java/tutorial/3.11.php)

Но советую обратиться к ней уже после прочтения текущей.

## Синтаксис обобщенных типов

Рассмотрим синтаксис обобщенного класса и пример создания его экземпляра:

```java
//Параметризуем класс generic T. T может быть любым ссылочным типом
public class Generic1<T> {
//Объявляем поле типа T. В конечном итоге для каждого экземпляра T будет соответствовать конкретному типу данных
private T field;
//Метод, возвращающий объект, соответствующий типу T для данного экземпляра Generic1
public T getField() {
return field;
}
//Метод, принимающий параметр, соответствующий типу T для данного экземпляра Generic1
public void setField(T field) {
this.field = field;
}
}
...
// Создаем объект Generic1, T в этом экземпляре равносильно String
Generic1<String> generic1String = new Generic1<>();
generic1String.setField("1");

// Создаем объект Generic1, T в этом экземпляре равносильно Integer
Generic1<Integer> generic1Integer = new Generic1<>();
generic1Integer.setField(1);

// Создаем объект Generic1 без указания параметризации, T в этом экземпляре равносильно Object
Generic1 generic1Object = new Generic1();
// Поскольку тип не указан явно, мы можем передавать все, что может быть приведено к ссылочному типу
generic1Object.setField("1");
//Здесь работает принцип автоупаковки. Будет передан параметр типа Integer, а не int
generic1Object.setField(1);
```

В данном примере параметризованный тип указан как `T`. Это распространенная практика (`T` – сокращение от _Type_), но в
целом, `T` можно заменить на любое другое название. Например, во внутренних классах Java часто встречается `R` –
_Result_.

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

```java
public class Generic1<ORIGINAL_NAME> { ... }
public class Generic1<OriginalName> { ... }
```

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

При обращении к параметризованным полям или параметрам будут доступны только методы `Object`.

Также класс может содержать несколько параметризованных типов. Для демонстрации передачи двух параметризованных
параметров используется конструктор, для методов принцип тот же:

```java
//Параметризуем класс типомами T1 и T2. Они могут быть любыми ссылочными типами
public class Generic2<T1, T2> {
private T1 field1;
private T2 field2;

public Generic2(T1 field1, T2 field2) {
this.field1 = field1;
this.field2 = field2;
}

public T1 getField1() {
return field1;
}

public void setField1(T1 field1) {
this.field1 = field1;
}
...
}
...
Generic2<String, Integer> generic2StrInt = new Generic2<>("1", 1);
Generic2<String, String> generic2StrStr = new Generic2<>("", "");
```

Как видите, в коде часто используется оператор `<>`, содержащий (или не содержащий) внутри себя тип данных. Именно он
однозначно указывает на то, что мы имеем дело с обобщенным типом.

## Ограничения обобщенных типов

В ряде случаев нам может понадобиться создать обобщенный тип, который будет работать только для какой-то части классов,
а не для всех. Для этого мы можем использовать ключевое слово `extends` при объявлении классов, внутри `<>`:

```java
public class Generic1<T extends Number> { ... }
```

Для такого класса можно создать объекты, параметризуя его только `Number` или его наследниками – `Integer`, `Double` и
др. После `extends` можно использовать как классы, так и интерфейсы.

При обращении к параметризованному полю или методу в таком случае будут также доступны поля и методы его предка.
Например, для `T extends Number` будут доступны методы абстрактного класса `Number`.

## Способы создания объектов обобщенных типов и немного об обратной совместимости в Java

В рамках примеров выше мы создавали объекты обобщенных типов двумя разными способами:

1. `Generic1<Integer> generic1Integer = new Generic1<>()`. `<>` - **Diamond (алмазный оператор)**. Синтаксис,
позволяющий не указывать тип дженерика повторно. Появился в Java 7. Ранее этот код выглядел бы
как `Generic1<Integer> generic1Integer = new Generic1<Integer>()`. Алмазным оператором называется именно
использование `<>` без какого-либо значения внутри;
2. `Generic1 generic1Object = new Generic1()`. Такая форма записи называется **Raw type (сырой тип)**. В абсолютном
большинстве случаев так делать не стоит, потому что мы лишаем себя проверки типов со стороны Java.

В целом, второй пункт объясняет, зачем вообще нужны обобщенные типа и почему бы вместо них не использовать поля и
параметры типа `Object`. Это тоже рабочий подход (и именно так и были написаны первый коллекции вроде `Vector`, когда
обобщенных типов в Java еще не было). Но он заставляет при каждом вызове метода проверять тип объекта, который
передается как параметр. Кроме того, вместо использования `T` (или другого обозначения типа) в каждом методе пришлось бы
указывать `Object`. Это не слишком удобно даже если класс параметризован одним типом. А если параметризован
несколькими – работа с данным классом превращается в ад.

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

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

Также стоит учитывать, что при компиляции кода параметризация стирается, в итоге
условный `Generic1<Integer> generic1Integer` превращается для Java в `Generic1 generic1Integer`. Это поведение получило
название **«стирание типов»** и оно также является следствием обратной совместимости. Данная информация имеет не так
много практического применения, но про это вполне могут спросить на собеседовании.

## Проверки типов

Мы знакомы с проверками типов с помощью `instanceof` и через объект класса `Class`. Пару нюансов об их использовании для
обобщенных типов:

- При вызове `getClass()` у двух элементов, одного типа, но параметризованных разными типами, будет возвращен один и тот
же объект `Class`;
- `Generic1<String>.class` - ошибка компиляции. Обращение к литералу класса всегда происходит без указания
параметризации: `Generic1.class`;
- `instanceof` также стоит использовать без указания параметризованного типа. Если его указать, то условие с
таким `instanceof` будет всегда `true` (если у объекта и проверяемого класса параметризованный тип совпадает), либо
будет ошибка компиляции (если параметризованный тип неизвестен или точно не совпадает с таковым у проверяемого
объекта). Оба варианта бесполезны. Можете убедиться в описанном поведении на практике на практике, написав проверки
для обобщенных классов через `instanceof`.

#### С теорией на сегодня все!

В следующем уроке мы разберемся с другой функциональностью, которая доступна при параметризации – **Wild card** – и
узнаем, для чего в Java нужен оператор `«?»`.

![img.png](../../../commonmedia/defaultFooter.jpg)

Переходим к практике:

## Задача 1:

Создать обобщенный тип, принимающий в себя любого из наследников `Number`. Создать метод, возводящий значение
параметризованного типа в степень, переданную параметром в метод.

## Задача 2:

Создать класс-обертку над объектом любого типа. Предусмотреть _boolean_-метод, проверяющий значение объекта на `null`.

## Задача 3:

Реализовать класс для работы с массивом. Разработать метод, производящий поиск значения в массиве. Если значение не
найдено — выбрасывать исключение. Если найдено — возвращать его.

## Задача 4 (*):

Реализовать параметризованный класс, хранящий и обрабатывающий **стек**. Стек — структура данных, в котором каждый
элемент хранит ссылку на следующий. Работает по принципу **LIFO** (последний вошел — первый вышел).

Реализовать следующие методы:

1. Добавление элемента в стек;
2. Удаление элемента из стека. При удалении несуществующего элемента – исключение;
3. Получение глубины (количества элементов) стека;
4. Поиск по стеку, при отсутствии искомого значения – исключение;
5. Получение строкового эквивалента элементов стека, представленных в виде
массива (_[строковое представление элемента1, ..., строковое представление элементаN]_).

> Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)
>
> Канал: https://t.me/ViamSupervadetVadens
>
> Мой тг: https://t.me/ironicMotherfucker
>
> **Дорогу осилит идущий!**