0 added
0 removed
Original
2026-01-01
Modified
2026-02-21
1
<p><a>#База знаний</a></p>
1
<p><a>#База знаний</a></p>
2
<ul><li>15 июл 2021</li>
2
<ul><li>15 июл 2021</li>
3
<li>0</li>
3
<li>0</li>
4
</ul><p>Разбираемся, зачем нужны дженерики и как добавить их в свой код.</p>
4
</ul><p>Разбираемся, зачем нужны дженерики и как добавить их в свой код.</p>
5
<p>Оля Ежак для Skillbox Media</p>
5
<p>Оля Ежак для Skillbox Media</p>
6
<p>Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.</p>
6
<p>Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.</p>
7
<p>У нас в парадной подъезде рядом с почтовыми ящиками стоит коробка. Предполагалось, что туда будут выбрасывать бумажный спам, который какие-то вредители упорно кладут в эти самые ящики. Но в коробке вместе с бумажками лежат пустые бутылки и банки, подозрительного вида пакеты, а в нынешних реалиях - ещё и использованные медицинские маски. Почему люди так делают? Потому что так можно.</p>
7
<p>У нас в парадной подъезде рядом с почтовыми ящиками стоит коробка. Предполагалось, что туда будут выбрасывать бумажный спам, который какие-то вредители упорно кладут в эти самые ящики. Но в коробке вместе с бумажками лежат пустые бутылки и банки, подозрительного вида пакеты, а в нынешних реалиях - ещё и использованные медицинские маски. Почему люди так делают? Потому что так можно.</p>
8
<p>Теперь представьте, что содержимое коробки вы отвозите на переработку, а перед этим каждый раз приходится отделять бумагу от прочего мусора. Не хотели бы вы заполучить такую коробку, которая не даст положить в себя что-то, кроме бумаги? Если ваш ответ "да" - вам понравятся дженерики (generics).</p>
8
<p>Теперь представьте, что содержимое коробки вы отвозите на переработку, а перед этим каждый раз приходится отделять бумагу от прочего мусора. Не хотели бы вы заполучить такую коробку, которая не даст положить в себя что-то, кроме бумаги? Если ваш ответ "да" - вам понравятся дженерики (generics).</p>
9
<ul><li><a>Знакомимся с дженериками</a></li>
9
<ul><li><a>Знакомимся с дженериками</a></li>
10
<li><a>Объявляем дженерик-классы и создаём их экземпляры</a></li>
10
<li><a>Объявляем дженерик-классы и создаём их экземпляры</a></li>
11
<li><a>Объявляем и реализуем дженерик-интерфейсы</a></li>
11
<li><a>Объявляем и реализуем дженерик-интерфейсы</a></li>
12
<li><a>Пишем дженерик-методы</a></li>
12
<li><a>Пишем дженерик-методы</a></li>
13
</ul><ul><li><a>Ограничиваем дженерики сверху и снизу</a></li>
13
</ul><ul><li><a>Ограничиваем дженерики сверху и снизу</a></li>
14
<li><a>Wildcards</a></li>
14
<li><a>Wildcards</a></li>
15
<li><a>Собираем понятия, связанные с дженериками</a></li>
15
<li><a>Собираем понятия, связанные с дженериками</a></li>
16
</ul><p>До появления дженериков программисты могли<strong>неявно предполагать</strong>, что какой-то класс, интерфейс или метод работает с элементами определённого типа.</p>
16
</ul><p>До появления дженериков программисты могли<strong>неявно предполагать</strong>, что какой-то класс, интерфейс или метод работает с элементами определённого типа.</p>
17
<p>Посмотрите на этот фрагмент кода:</p>
17
<p>Посмотрите на этот фрагмент кода:</p>
18
public static void printSomething(List list){ for (int i = 0; i < list.size(); i++){ String item = (String)list.get(i); System.out.println(String.format("Длина строки \"%1$s\" = %2$d", item, item.length())); } }<p>Здесь предполагается, что метод printSomething работает со списком строк. Мы можем догадаться об этом, потому что в цикле все элементы приводятся (преобразуются) к классу String, а потом ещё и метод length этого класса вызывается.</p>
18
public static void printSomething(List list){ for (int i = 0; i < list.size(); i++){ String item = (String)list.get(i); System.out.println(String.format("Длина строки \"%1$s\" = %2$d", item, item.length())); } }<p>Здесь предполагается, что метод printSomething работает со списком строк. Мы можем догадаться об этом, потому что в цикле все элементы приводятся (преобразуются) к классу String, а потом ещё и метод length этого класса вызывается.</p>
19
<p>Но смотрите, что сделали программисты Саша и Маша, - они поленились заглянуть внутрь метода и положили в список: один - число, а вторая - экземпляр StringBuilder.</p>
19
<p>Но смотрите, что сделали программисты Саша и Маша, - они поленились заглянуть внутрь метода и положили в список: один - число, а вторая - экземпляр StringBuilder.</p>
20
<p>Вот только тестировщик назначил баг не кому-то из них, а Паше, который написал метод printSomething, - потому что ошибка произошла именно во время его выполнения.</p>
20
<p>Вот только тестировщик назначил баг не кому-то из них, а Паше, который написал метод printSomething, - потому что ошибка произошла именно во время его выполнения.</p>
21
Как всё работало без дженериков. Схема: Екатерина Степанова / Skillbox<p>Паша быстро нашёл истинных виновников и попросил их исправить заполнение списка. Но на будущее решил подстраховаться от подобных ситуаций и переписал метод с использованием дженериков. Вот так:</p>
21
Как всё работало без дженериков. Схема: Екатерина Степанова / Skillbox<p>Паша быстро нашёл истинных виновников и попросил их исправить заполнение списка. Но на будущее решил подстраховаться от подобных ситуаций и переписал метод с использованием дженериков. Вот так:</p>
22
public static void printSomething(List<String> list) { for (int i = 0; i < list.size(); i++) { String item = list.get(i); System.out.println(String.format("Длина строки \"%1$s\" = %2$d", item, item.length())); } }<p>Теперь, если кто-то захочет положить в массив нестроковый элемент, ошибка станет заметной сразу - ещё на этапе компиляции.</p>
22
public static void printSomething(List<String> list) { for (int i = 0; i < list.size(); i++) { String item = list.get(i); System.out.println(String.format("Длина строки \"%1$s\" = %2$d", item, item.length())); } }<p>Теперь, если кто-то захочет положить в массив нестроковый элемент, ошибка станет заметной сразу - ещё на этапе компиляции.</p>
23
Как всё работает с дженериками. Схема: Екатерина Степанова / Skillbox<p>Обратите внимание, что во второй версии Пашиного метода item не приводится насильно к типу String. Мы просто получаем в цикле очередной элемент списка, и компилятор соглашается, что это, очевидно, будет строка. Код стал менее громоздким, читать его стало проще.</p>
23
Как всё работает с дженериками. Схема: Екатерина Степанова / Skillbox<p>Обратите внимание, что во второй версии Пашиного метода item не приводится насильно к типу String. Мы просто получаем в цикле очередной элемент списка, и компилятор соглашается, что это, очевидно, будет строка. Код стал менее громоздким, читать его стало проще.</p>
24
<p>Давайте запрограммируем ту самую коробку, о которой шла речь в начале статьи: создадим класс Box, который умеет работать только с элементами определённого типа. Пусть для простоты в этой коробке пока будет только один элемент:</p>
24
<p>Давайте запрограммируем ту самую коробку, о которой шла речь в начале статьи: создадим класс Box, который умеет работать только с элементами определённого типа. Пусть для простоты в этой коробке пока будет только один элемент:</p>
25
class Box<T> { // обозначение типа - T // переменная с типом T private T item; public void putItem(T item) { //параметр метода типа T this.item = item; } public T getItem() { // возвращает объект типа T return item; } }<p>В классе два метода:</p>
25
class Box<T> { // обозначение типа - T // переменная с типом T private T item; public void putItem(T item) { //параметр метода типа T this.item = item; } public T getItem() { // возвращает объект типа T return item; } }<p>В классе два метода:</p>
26
<ul><li>первый добавляет элемент в коробку;</li>
26
<ul><li>первый добавляет элемент в коробку;</li>
27
<li>второй достаёт его обратно и возвращает пользователю.</li>
27
<li>второй достаёт его обратно и возвращает пользователю.</li>
28
</ul><p>Во всех случаях, кроме заголовка класса, символ T пишется без угловых скобок, он обозначает один и тот же<strong>параметр типа</strong>.</p>
28
</ul><p>Во всех случаях, кроме заголовка класса, символ T пишется без угловых скобок, он обозначает один и тот же<strong>параметр типа</strong>.</p>
29
<p><strong>Параметром типа</strong>для дженерика может быть только ссылочный тип, интерфейс или перечисление (enum). Примитивные типы и массивы с дженериками не используются, то есть нельзя создать Box<int> или Box<int[]>, но можно - Box<Integer> или Box<List<Integer>>.</p>
29
<p><strong>Параметром типа</strong>для дженерика может быть только ссылочный тип, интерфейс или перечисление (enum). Примитивные типы и массивы с дженериками не используются, то есть нельзя создать Box<int> или Box<int[]>, но можно - Box<Integer> или Box<List<Integer>>.</p>
30
<p>Теперь создадим коробку для бумаги. Пусть за бумагу отвечает класс Paper, а значит, экземпляр правильной коробки создаётся вот так:</p>
30
<p>Теперь создадим коробку для бумаги. Пусть за бумагу отвечает класс Paper, а значит, экземпляр правильной коробки создаётся вот так:</p>
31
class Paper {} Box<Paper> boxForPaper = new Box<Paper>();<p>Это полный вариант записи, но можно и короче:</p>
31
class Paper {} Box<Paper> boxForPaper = new Box<Paper>();<p>Это полный вариант записи, но можно и короче:</p>
32
Box<Paper> boxForPaper = new Box<>();<p>Так как слева мы уже показали компилятору, что нужна коробка именно для бумаги, справа можно опустить повторное упоминание Paper - компилятор "догадается" о нём сам.</p>
32
Box<Paper> boxForPaper = new Box<>();<p>Так как слева мы уже показали компилятору, что нужна коробка именно для бумаги, справа можно опустить повторное упоминание Paper - компилятор "догадается" о нём сам.</p>
33
<p>Это "угадывание" называется<strong>type inference</strong> - выведение типа, а оператор "<strong><></strong>" - это<strong>diamond operator</strong>. Его так назвали из-за внешнего сходства с бриллиантом.</p>
33
<p>Это "угадывание" называется<strong>type inference</strong> - выведение типа, а оператор "<strong><></strong>" - это<strong>diamond operator</strong>. Его так назвали из-за внешнего сходства с бриллиантом.</p>
34
<p>Для обозначения дженерик-типа в классе Box мы использовали латинскую букву<strong>T</strong>. Это необязательно, то есть можно было бы использовать любую другую букву или даже слово -<strong>Box<MyType></strong>. Тем не менее есть набор<a>рекомендаций</a>от Oracle о том, когда какие обозначения лучше использовать в дженериках. Вот они:</p>
34
<p>Для обозначения дженерик-типа в классе Box мы использовали латинскую букву<strong>T</strong>. Это необязательно, то есть можно было бы использовать любую другую букву или даже слово -<strong>Box<MyType></strong>. Тем не менее есть набор<a>рекомендаций</a>от Oracle о том, когда какие обозначения лучше использовать в дженериках. Вот они:</p>
35
<p><strong>E</strong> - element, для элементов параметризованных коллекций;</p>
35
<p><strong>E</strong> - element, для элементов параметризованных коллекций;</p>
36
<p><strong>K</strong> - key, для ключей map-структур;</p>
36
<p><strong>K</strong> - key, для ключей map-структур;</p>
37
<p><strong>V</strong> - value, для значений map-структур;</p>
37
<p><strong>V</strong> - value, для значений map-структур;</p>
38
<p><strong>N</strong> - number, для чисел;</p>
38
<p><strong>N</strong> - number, для чисел;</p>
39
<p><strong>T</strong> - type, для обозначения типа параметра в произвольных классах;</p>
39
<p><strong>T</strong> - type, для обозначения типа параметра в произвольных классах;</p>
40
<p><strong>S, U, V</strong>и так далее - применяются, когда в дженерик-классе несколько параметров.</p>
40
<p><strong>S, U, V</strong>и так далее - применяются, когда в дженерик-классе несколько параметров.</p>
41
<p>Дженерик-классы хороши своей универсальностью: с классом Box теперь можно создать не только коробку для бумаги, но и, например, коробки для сбора пластика или стекла:</p>
41
<p>Дженерик-классы хороши своей универсальностью: с классом Box теперь можно создать не только коробку для бумаги, но и, например, коробки для сбора пластика или стекла:</p>
42
class Plastic {} class Glass {} Box<Plastic> boxForPlastic = new Box<>(); Box<Glass> boxForGlass = new Box<>();<p>А можно пойти ещё дальше и создать дженерик-класс с двумя параметрами для коробки с двумя отсеками. Вот так:</p>
42
class Plastic {} class Glass {} Box<Plastic> boxForPlastic = new Box<>(); Box<Glass> boxForGlass = new Box<>();<p>А можно пойти ещё дальше и создать дженерик-класс с двумя параметрами для коробки с двумя отсеками. Вот так:</p>
43
class TwoCellsBox<T, S> { private T firstCellItem; private S secondCellItem; //... }<p>Теперь легко запрограммировать коробку, в одном отсеке которой будет собираться пластик, а во втором - стекло:</p>
43
class TwoCellsBox<T, S> { private T firstCellItem; private S secondCellItem; //... }<p>Теперь легко запрограммировать коробку, в одном отсеке которой будет собираться пластик, а во втором - стекло:</p>
44
TwoCellsBox<Plastic, Glass> plasticGlassBox = new TwoCellsBox<>();<p>Обратите внимание, что<strong>type inference</strong>и <strong>diamond operator</strong>позволяют нам опустить<strong>оба</strong>параметра в правой части.</p>
44
TwoCellsBox<Plastic, Glass> plasticGlassBox = new TwoCellsBox<>();<p>Обратите внимание, что<strong>type inference</strong>и <strong>diamond operator</strong>позволяют нам опустить<strong>оба</strong>параметра в правой части.</p>
45
<p>Объявление дженерик- интерфейсов похоже на объявление дженерик-классов. Продолжим тему переработки и создадим интерфейс пункта переработки GarbageHandler сразу с двумя параметрами: тип мусора и способ переработки:</p>
45
<p>Объявление дженерик- интерфейсов похоже на объявление дженерик-классов. Продолжим тему переработки и создадим интерфейс пункта переработки GarbageHandler сразу с двумя параметрами: тип мусора и способ переработки:</p>
46
interface GarbageHandler<T, S> { void handle(T what, S how); }<p>Реализовать (имплементить) этот интерфейс можно в обычном, не дженерик- классе:</p>
46
interface GarbageHandler<T, S> { void handle(T what, S how); }<p>Реализовать (имплементить) этот интерфейс можно в обычном, не дженерик- классе:</p>
47
class MyPaperHandleMethod { } class MyNonGenericPaperHandler implements GarbageHandler<Paper, MyPaperHandleMethod> { @Override public void handle(Paper what, MyPaperHandleMethod how) { // здесь что-то делается с бумажным мусором способом MyPaperHandleMethod } }<p>Но можно пойти другим путём и сначала объявить дженерик-класс с двумя параметрами:</p>
47
class MyPaperHandleMethod { } class MyNonGenericPaperHandler implements GarbageHandler<Paper, MyPaperHandleMethod> { @Override public void handle(Paper what, MyPaperHandleMethod how) { // здесь что-то делается с бумажным мусором способом MyPaperHandleMethod } }<p>Но можно пойти другим путём и сначала объявить дженерик-класс с двумя параметрами:</p>
48
class GarbageHandlerImpl<T, S> implements GarbageHandler<T, S> { @Override public void handle(T what, S how) { // здесь что-то делается с мусором типа T способом S } }<p>Или скомбинировать эти два способа и написать дженерик-класс только с одним параметром:</p>
48
class GarbageHandlerImpl<T, S> implements GarbageHandler<T, S> { @Override public void handle(T what, S how) { // здесь что-то делается с мусором типа T способом S } }<p>Или скомбинировать эти два способа и написать дженерик-класс только с одним параметром:</p>
49
class PaperHandler<T> implements GarbageHandler<Paper, T> { @Override public void handle(Paper what, T how) { // здесь что-то делается с бумагой способом T } }<p>Дженерик-классы и дженерик-интерфейсы вместе называются<strong>дженерик-типами</strong>.</p>
49
class PaperHandler<T> implements GarbageHandler<Paper, T> { @Override public void handle(Paper what, T how) { // здесь что-то делается с бумагой способом T } }<p>Дженерик-классы и дженерик-интерфейсы вместе называются<strong>дженерик-типами</strong>.</p>
50
<p>Можно создавать экземпляры дженерик-типов "без расшифровки", то есть никто не запретит вам объявить переменную типа Box - просто Box:</p>
50
<p>Можно создавать экземпляры дженерик-типов "без расшифровки", то есть никто не запретит вам объявить переменную типа Box - просто Box:</p>
51
<p>Для такого случая даже есть термин -<strong>raw type</strong>, то есть "сырой тип". Эту возможность оставили в языке для совместимости со старым кодом, который был написан до появления дженериков.</p>
51
<p>Для такого случая даже есть термин -<strong>raw type</strong>, то есть "сырой тип". Эту возможность оставили в языке для совместимости со старым кодом, который был написан до появления дженериков.</p>
52
<p>В новых программах так писать не рекомендуется. Да и зачем? Ведь при таком способе теряются все преимущества использования дженериков.</p>
52
<p>В новых программах так писать не рекомендуется. Да и зачем? Ведь при таком способе теряются все преимущества использования дженериков.</p>
53
<p>В примерах выше мы уже видели параметризованные методы в дженерик-классах и интерфейсах. Типизированными могут быть как параметры метода, так и возвращаемый тип.</p>
53
<p>В примерах выше мы уже видели параметризованные методы в дженерик-классах и интерфейсах. Типизированными могут быть как параметры метода, так и возвращаемый тип.</p>
54
<p>До этого мы использовали в методах только те обозначения типов, которые объявлены в заголовке дженерик-класса или интерфейса, но это не обязательно. Предположим, у нашего пункта переработки есть ещё опция: сбор опасных отходов, которые сотрудники вывозят на утилизацию в другое место. Напишем метод для этого:</p>
54
<p>До этого мы использовали в методах только те обозначения типов, которые объявлены в заголовке дженерик-класса или интерфейса, но это не обязательно. Предположим, у нашего пункта переработки есть ещё опция: сбор опасных отходов, которые сотрудники вывозят на утилизацию в другое место. Напишем метод для этого:</p>
55
interface GarbageHandler<T, S> { void handle(T what, S how); <E> void transfer(E dangerousWaste); }<p>У метода transfer есть свой личный параметр для типа, который не обязан совпадать ни с типом T, ни с типом S. При первом упоминании новый параметр, как и в случае с заголовком класса или интерфейса, пишется в угловых скобках.</p>
55
interface GarbageHandler<T, S> { void handle(T what, S how); <E> void transfer(E dangerousWaste); }<p>У метода transfer есть свой личный параметр для типа, который не обязан совпадать ни с типом T, ни с типом S. При первом упоминании новый параметр, как и в случае с заголовком класса или интерфейса, пишется в угловых скобках.</p>
56
<p>Дженерик-методы можно объявлять и в обычных (не дженерик) классах и интерфейсах. Наш класс для переработки мог быть выглядеть так:</p>
56
<p>Дженерик-методы можно объявлять и в обычных (не дженерик) классах и интерфейсах. Наш класс для переработки мог быть выглядеть так:</p>
57
class GarbageHandlerImpl { public <T, S> void handle(T what, S how) { // здесь что-то делается с мусором типа T способом S } }<p>Здесь дженерики используются только в методе.</p>
57
class GarbageHandlerImpl { public <T, S> void handle(T what, S how) { // здесь что-то делается с мусором типа T способом S } }<p>Здесь дженерики используются только в методе.</p>
58
<p>Обратите внимание на синтаксис: параметры типов объявляются после модификатора доступа (public), но <strong>перед</strong>возвращаемым типом (void). Они перечисляются через запятую в <strong>общих</strong>угловых скобках.</p>
58
<p>Обратите внимание на синтаксис: параметры типов объявляются после модификатора доступа (public), но <strong>перед</strong>возвращаемым типом (void). Они перечисляются через запятую в <strong>общих</strong>угловых скобках.</p>
59
<p>Давайте немного расширим наше представление о мусоре и введём для него дополнительное свойство - массу "типичного представителя", то есть массу одной пластиковой бутылки или листка бумаги, например.</p>
59
<p>Давайте немного расширим наше представление о мусоре и введём для него дополнительное свойство - массу "типичного представителя", то есть массу одной пластиковой бутылки или листка бумаги, например.</p>
60
abstract class Garbage{ public abstract double getWeight(); } class Paper extends Garbage{ @Override public double getWeight() { return 0.01; } } class Plastic extends Garbage{ @Override public double getWeight() { return 0.3; } }<p>Теперь попробуем использовать эту массу в методе уже знакомого класса Box:</p>
60
abstract class Garbage{ public abstract double getWeight(); } class Paper extends Garbage{ @Override public double getWeight() { return 0.01; } } class Plastic extends Garbage{ @Override public double getWeight() { return 0.3; } }<p>Теперь попробуем использовать эту массу в методе уже знакомого класса Box:</p>
61
class Box<T> { private T item; public double getItemWeight() { // не скомпилируется return item == null ? 0 : item.getWeight(); } //... остальные методы }<p>И получим ошибку при компиляции: мы не рассказали компилятору, что T - это какой-то вид мусора. Исправим это с помощью так называемого<strong>upper bounding</strong> - ограничения сверху:</p>
61
class Box<T> { private T item; public double getItemWeight() { // не скомпилируется return item == null ? 0 : item.getWeight(); } //... остальные методы }<p>И получим ошибку при компиляции: мы не рассказали компилятору, что T - это какой-то вид мусора. Исправим это с помощью так называемого<strong>upper bounding</strong> - ограничения сверху:</p>
62
class Box<T extends Garbage> { // методы класса }<p>Теперь метод getItemWeight успешно скомпилируется.</p>
62
class Box<T extends Garbage> { // методы класса }<p>Теперь метод getItemWeight успешно скомпилируется.</p>
63
<p>Здесь<strong>T extends Garbage</strong>означает, что в качестве T можно подставить Garbage или любой класс-наследник Garbage. Из уже известных нам классов это могут быть, например, Paper или Plastic. Так как и у Garbage, и у всех его наследников есть метод getWeight, его можно вызывать в новой версии дженерик-класса Box.</p>
63
<p>Здесь<strong>T extends Garbage</strong>означает, что в качестве T можно подставить Garbage или любой класс-наследник Garbage. Из уже известных нам классов это могут быть, например, Paper или Plastic. Так как и у Garbage, и у всех его наследников есть метод getWeight, его можно вызывать в новой версии дженерик-класса Box.</p>
64
<p>Для одного класса или интерфейса можно добавить сразу несколько ограничений. Вспомним про интерфейс для пункта приёма мусора и введём класс для метода переработки - HandleMethod. Тогда GarbageHandler можно переписать так:</p>
64
<p>Для одного класса или интерфейса можно добавить сразу несколько ограничений. Вспомним про интерфейс для пункта приёма мусора и введём класс для метода переработки - HandleMethod. Тогда GarbageHandler можно переписать так:</p>
65
class HandleMethod { } interface GarbageHandler<T extends Garbage, S extends HandleMethod> { void handle(T what, S how); }<p>В качестве границы может выступать класс, интерфейс или перечисление (enum), но не примитивный тип и не массив. При этом для интерфейсов тоже используется слово<strong>extends</strong>, а не implements:<strong><T extends SomeInterface></strong>вместо <T implements SomeInterface>.</p>
65
class HandleMethod { } interface GarbageHandler<T extends Garbage, S extends HandleMethod> { void handle(T what, S how); }<p>В качестве границы может выступать класс, интерфейс или перечисление (enum), но не примитивный тип и не массив. При этом для интерфейсов тоже используется слово<strong>extends</strong>, а не implements:<strong><T extends SomeInterface></strong>вместо <T implements SomeInterface>.</p>
66
<p>До этого мы использовали для параметров типов буквенные имена, но в Java есть и специальный символ для обозначения неизвестного типа - "<strong>?</strong>". Его принято называть<strong>wildcard</strong>, дословно - "дикая карта".</p>
66
<p>До этого мы использовали для параметров типов буквенные имена, но в Java есть и специальный символ для обозначения неизвестного типа - "<strong>?</strong>". Его принято называть<strong>wildcard</strong>, дословно - "дикая карта".</p>
67
<p>Термин<strong>wildcard</strong>пришёл в программирование из карточной игры. В покере, например, так называют карту, которая может сыграть вместо любой другой. Джокер - известный пример такой "дикой карты".</p>
67
<p>Термин<strong>wildcard</strong>пришёл в программирование из карточной игры. В покере, например, так называют карту, которая может сыграть вместо любой другой. Джокер - известный пример такой "дикой карты".</p>
68
<p>Wildcard нельзя подставлять везде, где до этого мы писали буквенные обозначения. Не получится, например, объявить класс Box<?> или дженерик-метод, который принимает такой тип:</p>
68
<p>Wildcard нельзя подставлять везде, где до этого мы писали буквенные обозначения. Не получится, например, объявить класс Box<?> или дженерик-метод, который принимает такой тип:</p>
69
class Box<?>{ // не скомпилируется ? variable; // не скомпилируется public <?> void someMethod(? param){ // не скомпилируется //... } }<p>Wildcards удобно использовать для объявления переменных и параметров методов совместно с классами из <strong>Java Collection Framework</strong> - здесь собраны инструменты Java для работы с коллекциями. Если вы не очень хорошо знакомы с ними, освежите знания, прочитав<a>эту</a>статью.</p>
69
class Box<?>{ // не скомпилируется ? variable; // не скомпилируется public <?> void someMethod(? param){ // не скомпилируется //... } }<p>Wildcards удобно использовать для объявления переменных и параметров методов совместно с классами из <strong>Java Collection Framework</strong> - здесь собраны инструменты Java для работы с коллекциями. Если вы не очень хорошо знакомы с ними, освежите знания, прочитав<a>эту</a>статью.</p>
70
<p>В примере ниже мы можем подставить вместо "?" любой тип, в том числе Paper, поэтому строка успешно скомпилируется:</p>
70
<p>В примере ниже мы можем подставить вместо "?" любой тип, в том числе Paper, поэтому строка успешно скомпилируется:</p>
71
List<?> example1 = new ArrayList<Paper>();<p>Wildcards можно применять для ограничений типов:</p>
71
List<?> example1 = new ArrayList<Paper>();<p>Wildcards можно применять для ограничений типов:</p>
72
List<? extends Garbage> example2 = new ArrayList<Paper>();<p>Это уже знакомое нам ограничение сверху,<strong>upper bounding</strong>, - вместо "?" допуст<strong>и</strong>м Garbage или любой его класс-наследник, то есть Paper подходит.</p>
72
List<? extends Garbage> example2 = new ArrayList<Paper>();<p>Это уже знакомое нам ограничение сверху,<strong>upper bounding</strong>, - вместо "?" допуст<strong>и</strong>м Garbage или любой его класс-наследник, то есть Paper подходит.</p>
73
<p>Но можно ограничить тип и снизу. Это называется<strong>lower bounding</strong>и выглядит так:</p>
73
<p>Но можно ограничить тип и снизу. Это называется<strong>lower bounding</strong>и выглядит так:</p>
74
List<? super Garbage> example3 = new ArrayList<Garbage>();<p>Здесь<strong><? super Garbage></strong>означает, что вместо "?" можно подставить Garbage или любой класс-предок Garbage. Все ссылочные классы неявно наследуют класс Object, так что в правой части ещё может быть ArrayList<Object>.</p>
74
List<? super Garbage> example3 = new ArrayList<Garbage>();<p>Здесь<strong><? super Garbage></strong>означает, что вместо "?" можно подставить Garbage или любой класс-предок Garbage. Все ссылочные классы неявно наследуют класс Object, так что в правой части ещё может быть ArrayList<Object>.</p>
75
Множества допустимых классов при использовании ограниченных wildcards. Схема: Екатерина Степанова / Skillbox<p>Мы не успели разобраться с более сложными вещами - например, с заменами аргументов типов в классах-наследниках, с переопределением дженерик-методов, не узнали об особенностях коллекций с wildcards.</p>
75
Множества допустимых классов при использовании ограниченных wildcards. Схема: Екатерина Степанова / Skillbox<p>Мы не успели разобраться с более сложными вещами - например, с заменами аргументов типов в классах-наследниках, с переопределением дженерик-методов, не узнали об особенностях коллекций с wildcards.</p>
76
<p>Обо всём этом и не только - в следующих статьях. А пока соберём небольшой словарик из терминов, которые связаны с использованием дженериков, - они пригодятся при чтении специальной литературы:</p>
76
<p>Обо всём этом и не только - в следующих статьях. А пока соберём небольшой словарик из терминов, которые связаны с использованием дженериков, - они пригодятся при чтении специальной литературы:</p>
77
<strong>Термин</strong><strong>Расшифровка</strong>Дженерик-типы (generic types)Дженерик-класс или дженерик-интерфейс с одним или несколькими параметрами в заголовкеПараметризованный тип (parameterized types)Вызов дженерик-типа. Для дженерик-типа List<E> параметризованным типом будет, например, List<Box>Параметр типа (type parameter)Используются при объявлении дженерик-типов. Для Box<T> T - это параметр типаАргумент типа (type argument)Тип объекта, который может использоваться вместо параметра типа. Например, для Box<Paper> Paper - это аргумент типаWildcardОбозначается символом "?" - неизвестный типОграниченный wildcard (bounded wildcard)Wildcard, который ограничен сверху - <? extends Garbage> или снизу - <? super Garbage>Сырой тип (raw type)Имя дженерик-типа без аргументов типа. Для List<E> сырой тип - это List<p>Ещё больше о дженериках, коллекциях и других элементах языка Java узнайте на нашем курсе "<a>Профессия Java-разработчик</a>". Научим программировать на самом востребованном языке и поможем устроиться на работу.</p>
77
<strong>Термин</strong><strong>Расшифровка</strong>Дженерик-типы (generic types)Дженерик-класс или дженерик-интерфейс с одним или несколькими параметрами в заголовкеПараметризованный тип (parameterized types)Вызов дженерик-типа. Для дженерик-типа List<E> параметризованным типом будет, например, List<Box>Параметр типа (type parameter)Используются при объявлении дженерик-типов. Для Box<T> T - это параметр типаАргумент типа (type argument)Тип объекта, который может использоваться вместо параметра типа. Например, для Box<Paper> Paper - это аргумент типаWildcardОбозначается символом "?" - неизвестный типОграниченный wildcard (bounded wildcard)Wildcard, который ограничен сверху - <? extends Garbage> или снизу - <? super Garbage>Сырой тип (raw type)Имя дженерик-типа без аргументов типа. Для List<E> сырой тип - это List<p>Ещё больше о дженериках, коллекциях и других элементах языка Java узнайте на нашем курсе "<a>Профессия Java-разработчик</a>". Научим программировать на самом востребованном языке и поможем устроиться на работу.</p>
78
<a>Научитесь: Профессия Java-разработчик + ИИ Узнать больше</a>
78
<a>Научитесь: Профессия Java-разработчик + ИИ Узнать больше</a>