From 1e54cb930475ac5719634066d60f9296c6ac3b3f Mon Sep 17 00:00:00 2001 From: egorzhurov Date: Sat, 11 May 2024 13:55:28 +0300 Subject: [PATCH] Move 29 article to markdown format --- ...rics. Parameterized method and wildcard.md | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 lessons/java-core/029/Generics. Parameterized method and wildcard.md diff --git a/lessons/java-core/029/Generics. Parameterized method and wildcard.md b/lessons/java-core/029/Generics. Parameterized method and wildcard.md new file mode 100644 index 0000000..e4415dd --- /dev/null +++ b/lessons/java-core/029/Generics. Parameterized method and wildcard.md @@ -0,0 +1,171 @@ +# Generics. Часть II + +## Параметризация методов + +В прошлом уроке мы писали параметризованные методы в рамках параметризованного класса. Но что делать, если необходимо +создать параметризованный метод вне _generic_-класса? + +В таком случае нам нужно явно указать методу, что он параметризован. Сделать это можно так: + +```java + private void doSth() { + //sth logic + } +``` + +Как видим, достаточно указать обобщенный тип перед возвращаемым типом метода. Метод `doSth()` является +параметризованным. Но в таком виде мы не можем полноценно работать с типом, подставляемым в `T` – у нас нет ни +возвращаемого значения типа `T`, ни параметров этого же типа. Но данный пример хорошо демонстрирует саму параметризацию +метода в общем виде. К тому же, даже в таком виде код не содержит ошибок, программа с таким методом скомпилируется +корректно. + +Однако рассмотрим более «живой» пример. Для дополнительной демонстрации возможностей параметризуем метод двумя типами: + +```java + private R doSth(T param) { + //sth logic returning R-type object + } +``` + +В данном случае, мы указываем, что метод параметризован двумя типами: `T` и `R`. Метод возвращает объект типа `R`, а +также принимает параметр типа `T`. `R` является любым типом, отличным от `T`, который неизвестен на этапе компиляции. На +практике нам проблематично реализовать тело подобного метода, потому что мы не знакомы с лямбда-выражениями, при +использовании которых такая форма записи имела бы смысл. Поэтому сейчас просто отложим в голове, что так делать можно и +у подобной параметризации есть своя сфера применения. + +Главное, что стоит вынести из этого подраздела – мы можем параметризовать методы вне параметризованного класса (или +дополнительно параметризовать прямо в классе-дженерике, тогда нам будет доступна и параметризованный тип уровня класса, +и параметризованный тип уровня метода). Можем определенным образом работать с параметризованными параметрами и +возвращать значение параметризованного типа. + +В качестве закрепления рассмотрим несколько примеров, которые мы могли бы использовать уже сейчас: + +```java + // Пример работы с параметризованным параметром метода, при наличии других параметров + private String getStringValue(T param, String prefix) { + return prefix + param.toString(); + } + + // Пример использования ограничения типа при параметризации метода + private String getDoubleStringValue(T param) { + return String.valueOf(param.doubleValue()); + } + + // Возвращение параметризованного типа из метода + private T doSth(T param) { + // какая-то логика обработки параметра + return param; // или другой объект того же типа + } +``` + +Примеры, безусловно, исключительно демонстративные, логика внутри методов не несет полезной нагрузки. + +## Приведение параметризованных типов + +Разбирая в прошлом уроке синтаксис обобщенных типов, мы опустили одну небольшую, но важную деталь. Для параметризованных +типов не работает приведение в том виде, в котором мы привыкли: + +```java + Generic1 generic1String = new Generic1<>(); + Generic1 generic1Object = generic1String; // Ошибка компиляции +``` + +Связано это с тем, что `Generic1` не является наследником `Generic1`. Что делать, если подобное +приведение нам необходимо, мы рассмотрим ниже. + +## Wildcards + +В ряде случаев нет необходимости полноценно параметризовать метод или указывать тип при создании переменной +параметризованного класса. В таком случае нам может прийти на помощь **подстановочный символ «?»**: + +```java + Generic1 generic1 = new Generic1(); +``` + +В данном случае мы не указываем явно, каким типом данных параметризован экземпляр `generic1`. В таком виде мы можем +записать в эту переменную объект `Generic1`, параметризованный любым типом. Например, следующей же строчкой мы можем +присвоить другое значение этой же переменной: + +```java + generic1 = new Generic1(); +``` + +В данной форме записи `«?»` эквивалентен `Object`. Условно говоря, если все классы в Java наследуются от `Object`, то +все параметризованные типы наследуются от класса, параметризованного `«?»`. Т.е. `Generic1` – +наследник `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`: под такую форму записи подойдут объекты `Generic`, параметризованные типом `SthClass` + или его наследниками; +- `Generic`: под такую форму записи подойдут объекты `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 +> +> **Дорогу осилит идущий!** \ No newline at end of file