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
171 changes: 171 additions & 0 deletions lessons/java-core/029/Generics. Parameterized method and wildcard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Generics. Часть II

## Параметризация методов

В прошлом уроке мы писали параметризованные методы в рамках параметризованного класса. Но что делать, если необходимо
создать параметризованный метод вне _generic_-класса?

В таком случае нам нужно явно указать методу, что он параметризован. Сделать это можно так:

```java
private <T> void doSth() {
//sth logic
}
```

Как видим, достаточно указать обобщенный тип перед возвращаемым типом метода. Метод `doSth()` является
параметризованным. Но в таком виде мы не можем полноценно работать с типом, подставляемым в `T` – у нас нет ни
возвращаемого значения типа `T`, ни параметров этого же типа. Но данный пример хорошо демонстрирует саму параметризацию
метода в общем виде. К тому же, даже в таком виде код не содержит ошибок, программа с таким методом скомпилируется
корректно.

Однако рассмотрим более «живой» пример. Для дополнительной демонстрации возможностей параметризуем метод двумя типами:

```java
private <T, R> R doSth(T param) {
//sth logic returning R-type object
}
```

В данном случае, мы указываем, что метод параметризован двумя типами: `T` и `R`. Метод возвращает объект типа `R`, а
также принимает параметр типа `T`. `R` является любым типом, отличным от `T`, который неизвестен на этапе компиляции. На
практике нам проблематично реализовать тело подобного метода, потому что мы не знакомы с лямбда-выражениями, при
использовании которых такая форма записи имела бы смысл. Поэтому сейчас просто отложим в голове, что так делать можно и
у подобной параметризации есть своя сфера применения.

Главное, что стоит вынести из этого подраздела – мы можем параметризовать методы вне параметризованного класса (или
дополнительно параметризовать прямо в классе-дженерике, тогда нам будет доступна и параметризованный тип уровня класса,
и параметризованный тип уровня метода). Можем определенным образом работать с параметризованными параметрами и
возвращать значение параметризованного типа.

В качестве закрепления рассмотрим несколько примеров, которые мы могли бы использовать уже сейчас:

```java
// Пример работы с параметризованным параметром метода, при наличии других параметров
private <T> String getStringValue(T param, String prefix) {
return prefix + param.toString();
}

// Пример использования ограничения типа при параметризации метода
private <T extends Number> String getDoubleStringValue(T param) {
return String.valueOf(param.doubleValue());
}

// Возвращение параметризованного типа из метода
private <T> T doSth(T param) {
// какая-то логика обработки параметра
return param; // или другой объект того же типа
}
```

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

## Приведение параметризованных типов

Разбирая в прошлом уроке синтаксис обобщенных типов, мы опустили одну небольшую, но важную деталь. Для параметризованных
типов не работает приведение в том виде, в котором мы привыкли:

```java
Generic1<String> generic1String = new Generic1<>();
Generic1<Object> generic1Object = generic1String; // Ошибка компиляции
```

Связано это с тем, что `Generic1<String>` не является наследником `Generic1<Object>`. Что делать, если подобное
приведение нам необходимо, мы рассмотрим ниже.

## Wildcards

В ряде случаев нет необходимости полноценно параметризовать метод или указывать тип при создании переменной
параметризованного класса. В таком случае нам может прийти на помощь **подстановочный символ «?»**:

```java
Generic1<?> generic1 = new Generic1<String>();
```

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

```java
generic1 = new Generic1<Integer>();
```

В данной форме записи `«?»` эквивалентен `Object`. Условно говоря, если все классы в Java наследуются от `Object`, то
все параметризованные типы наследуются от класса, параметризованного `«?»`. Т.е. `Generic1<String>` –
наследник `Generic1<?>`. Это не соответствует действительности с точки зрения JVM, но такая аналогия позволяет понять
нюансы приведения обобщенных типов.

Другое использование подстановочного символа заключается в передаче в метод параметра обобщенного типа или возврат
объекта такого типа из метода. Например, вне класса `Generic1`, не имея доступ к `T`, мы можем описать следующие методы:

```java
private Generic1<?> getGeneric(String param) {
return new Generic1<>(param);
}

private Object getValue(Generic1<?> value) {
return value;
}
```

В англоязычной литературе и, соответственно, в профессиональном сленге подстановочный символ называется **wildcard**
(дословно – подстановочный знак или подстановочный тип).

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

Плюс wildcard’а в том, что мы можем использовать его внутри метода, не параметризуя сам метод. Его минус – это не
самостоятельный тип, в отличии от условного `T`. Т.е. мы можем использовать вайлдакрд внутри `<>`, однако объявить
переменную или передать параметр типа `?` у нас не получится:

```java
private void doSth(? param) {} // ошибка компиляции
...
? obj = new Object(); // ошибка компиляции
```

Как и при классической параметризации, wildcard можно ограничивать:

- `Generic<? extends SthClass>`: под такую форму записи подойдут объекты `Generic`, параметризованные типом `SthClass`
или его наследниками;
- `Generic<? super SthClass>`: под такую форму записи подойдут объекты `Generic`, параметризованные типом `SthClass` или
каким-либо из его предков. Ограничение с помощью `super` доступно только для wildcard. Если кому-то интересно, зачем
такое ограничение существует – рекомендую ознакомиться с правилом **PECS (Producer Extends Consumer Super)**.

Wildcard, у которого описано ограничение типа называют **Bounded (ограниченный) wildcard**. Соответственно, wildcard без
ограничения типа – **Unbounded (неограниченный) wildcards**.

К сожалению, актуальность вайлдкардов достаточно тяжело понять до знакомства с коллекциями. Поэтому данный урок больше
направлен на знакомство с синтаксисом, нежели на практики использования. Однако мы обязательно вернемся к этой теме при
знакомстве с коллекциями и в других темах (например, **Stream API**), сравнив использование параметризованных методов и
вайлдкардов.

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

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

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

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

## Задача 1:

Реализуйте обобщенный тип, хранящий параметризованное поле. Также в классе `Main` реализуйте параметризованый метод,
принимает первым параметром объект вашего дженерика, вторым — объект типа, которым параметризован объект первого
параметра. Метод должен возвращать значение поля дженерика, если оно `!= null`, в противном случае — возвращать второй
параметр.

## Задача 2:

Используя Задачу 1 из урока [Generics. Часть I](https://telegra.ph/Generics-Chast-I-12-12), реализуйте в `Main` метод,
принимающий аргументом объект подходящего для дженерика типа и возвращающий объект дженерика. Допустима параметризация
только с использованием wildcard.

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