JavaRush /Курсы /Java Syntax Pro /Параметризированные типы в Java – Generics

Параметризированные типы в Java – Generics

Java Syntax Pro
12 уровень , 6 лекция
Открыта

1. Все классы унаследованы от Object

Все классы в Java неявно (скрытно) унаследованы от класса Object.

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

Переменной типа Object можно присвоить объект любого класса. Пример:

Код Примечание
Object o = new Scanner(System.in);
В переменную o сохранена ссылка на объект типа Scanner
Object o = new String();
В переменную o сохранена ссылка на объект типа String
Object o = new Integer(15);
В переменную o сохранена ссылка на объект типа Integer
Object o = "Привет";
В переменную o сохранена ссылка на объект типа String

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

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

Код Примечание
Object o = new Scanner(System.in);
int x = o.nextInt();
Программа не скомпилируется. У класса Object нет метода nextInt().
Object o = new Scanner(System.in);

Scanner console = (Scanner) o;

int x = console.nextInt();
Так будет работать.

Тут мы сохраняем ссылку на объект типа Scanner в переменную типа Scanner с помощью оператора приведения типа.

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

Тип имя1 = (Тип) имя2;

Где имя1 – это имя переменной типа Тип, а имя2 – это имя переменной типа Object, которая хранит ссылку на объект типа Тип.

Приведение типа

Если типы переменной и объекта не совпадают, возникнет ошибка ClassCastException. Пример:

Код Примечание
Object o = new Integer(5);
String s = (String) o;
Во время выполнения возникнет ошибка:
тут кинется исключение ClassCastException

В Java есть способ обойти эту ошибку: существует способ проверить, какой на самом деле тип находится внутри переменной:

имя instanceof Тип

Оператор instanceof проверяет, является ли переменная имя объектом типа Тип.

Пример — нахождение строки среди массива данных:

Код Примечание
Object[] objects = {10, "Привет", 3.14};

for (int i = 0; i < objects.length; i++)
{
   if (objects[i] instanceof String)
   {
      String s = (String) objects[i];
      System.out.println(s);
   }
}
Autoboxing превратит эти значения в Integer, String и Double.

Цикл по массиву объектов

Если объект имеет тип String

Сохраняем его в переменную типа String
Выводим переменную на экран.


2. Причина возникновения шаблонов (коллекции)

Возвращаемся к коллекциям.

Когда Java-разработчики только создавали класс ArrayList, они хотели сделать его универсальным, чтобы в нем можно было хранить объекты любого типа. Поэтому для хранения элементов они воспользовались массивом типа Object.

Сильная сторона такого подхода в том, что в коллекцию можно добавить объект любого типа.

Ну а слабых сразу несколько.

Недостаток 1.

Всегда приходилось писать оператор преобразования типа, когда доставали элементы из коллекции:

Код Примечание
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 10);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
Создаем объект-коллекцию для хранения ссылок на объекты типа Object

Заполняем коллекцию цифрами 0, 10, ..., 90;



Суммируем элементы коллекции


Нужно использовать приведение типа

Недостаток 2.

Не было гарантии, что в коллекции хранятся элементы определенного типа

Код Примечание
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 2.5);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
Создаем объект-коллекцию для хранения ссылок на объекты типа Object

Заполняем коллекцию числами типа Double:
0.0, 2.5, 5.0, ...


Суммируем элементы коллекции


Будет ошибка: тип Double нельзя привести к типу Integer

Данные в коллекцию могут заполняться где угодно:

  • в другом методе
  • в другой программе
  • загружаться из файла
  • получаться по сети

Недостаток 3.

Данные коллекции можно случайно поменять по незнанию.

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

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


3. Дженерики

Дженерики в Java

Все эти проблемы устраняет такая классная вещь в Java как дженерики (Generics).

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

ОсновнойТип<ТипПараметр>

Все вместе — это именно тип. И он может использоваться там, где обычно можно использовать типы.

Код Описание
ArrayList<Integer> list;
Создание переменных
list = new ArrayList<Integer> ();
Создание объектов
ArrayList<Integer>[] array;
Создание массивов

В такую коллекцию можно сохранить только переменные типа Integer:

Код Описание
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(new Integer(1));
list.add(2);
list.add("Привет");
Коллекция типа ArrayList с элементами типа Integer
Так можно
И так можно: сработает
autoboxing

А так нельзя: ошибка компиляции

Как создавать свои классы с типами-параметрами, вы изучите в квесте Java Collections. Сейчас же мы разберем, как этим пользоваться и как это работает.


4. Как работают Generics

На самом деле Generics работают до ужаса примитивно.

Компилятор просто заменяет тип с параметром на него же, только без параметра. А при взаимодействии с его методами добавляет операцию приведения типа к типу-параметру:

Код Что сделает компилятор
ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList list = new ArrayList();
list.add(1);
list.add( (Integer) 1 );
int x = list.get(0);
int x = (Integer) list.get(0);
list.set(0, 10);
list.set(0, (Integer) 10);

Допустим, у нас был код метода, который суммирует числа в коллекции целых чисел:

Код Что сделает компилятор
public int sum(ArrayList<Integer> numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + numbers.get(i);

   return result;
}
public int sum(ArrayList numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + (Integer) numbers.get(i);

   return result;
}

Т.е. по сути дженерики — это такая разновидность синтаксического сахара, как и autoboxing, только побольше. При autoboxing компилятор за нас добавляет методы для преобразования типа int к Integer и обратно, а для generics добавляет операторы приведения типа.

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

Иногда программистам, которые пишут свои классы с типами-параметрами, очень не хватает информации о типах, которые туда передаются в качестве параметров. Как с этим борются и что из этого выходит, вы узнаете в квесте Java Collections.



5. Несколько фактов о дженериках

Еще несколько интересных фактов о дженериках.

У классов может быть не один тип параметр, а несколько. Выглядит это примерно так:

ОсновнойТип<ТипПараметр1, ТипПараметр2, ТипПараметр3>

Собственно говоря, в этом нет ничего удивительного. Там, где компилятор может добавить оператор приведения к одному типу, он может добавить и несколько таких.

Примеры:

Код Примечание
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(7, "Привет");
map.put(-15, "Привет");
первый параметр метода put имеет тип Integer, второй — тип String

Также сложные типы тоже можно использовать в качестве параметров. Выглядит это примерно так:

ОсновнойТип<ТипПараметр<ТипПараметрПараметра>>

Допустим, мы хотим создать список, который будет хранить списки строк. В таком случае мы получим примерно такой код:

// список приветствий
ArrayList<String> listHello = new ArrayList<String>();
listHello.add("Привет");
listHello.add("Hi");

// список прощаний
ArrayList<String> listBye = new ArrayList<String>();
listBye.add("Пока");
listBye.add("Good Bye");

// список списков
ArrayList<ArrayList<String>> lists = new ArrayList<ArrayList<String>>();
lists.add(listHello);
lists.add(listBye);

Комментарии (341)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
KarinaShineofKornflower Уровень 23
30 июня 2026
KAAAAAAAATE
Grigoryvvv Уровень 17 Expert
17 января 2026
17.01.2026 / 13 уровень
C0N5P1RACY Уровень 1
18 декабря 2025
Так, внесем немного ясности в логику и лор комикса... Клон брата сестры президента Айтинии - тоесть, у того шефа Айтинии была сестра и был брат (брат сестры его брат тоже), у этого брата есть клон, и этот клон это президент Лулга. Брат клона сестры президента Айтинии - так же у сестры тоже есть клон, клон сестры, тоесть клон брата является братом клона сестры, клон - клону является братом/сестрой. Тоесть в их родственных связях есть - Два брата (один из них президент Айтинии), и одна сестра, а так же есть клон сестры и клон брата, и этот клон брата это президент Лулга. Но оригинал (не клон) не является президентом (хотя дальше может оказаться иначе..) Далее, президент Лулга говорит о президенте Айтинии как "брат сестры клона" Это значит, то либо президент Айтинии так же является клоном, либо они считают что клоны это такие же родственники... Я твой брат, брат, а кто не брат мне, тот не брат, брат Раз брат, два брат, ты мне брат, а ты не брат Брат, брат, брат, брат, брат два, вот так
Georgy Уровень 16
20 июля 2025
Серьезно???? дженерикам 1 лекция посвящена..... мда
Anonymous #3525688 Уровень 24
22 сентября 2025
Там же вроде ясно сказали (раза 3 за лекцию), что Generics мы будем проходить в квесте Java Collections :/
Anonymous #3585174 Уровень 33
1 июля 2025
Like
hidden #3543105 Уровень 64
14 июня 2025
Хмелов - самый крутой лектор
AshByte Уровень 68
27 июня 2025
тут прям не поспоришь, мне повезло видимо, что к нему попал на Универе :)
Александр Уровень 19
13 февраля 2026
Хмелов говорит и общается с людьми, у которых уже есть опыт, и, к сожалению, говорит об этом открыто. Мазиков в данном контексте для целей платформы - гораздо лучше как преподаватель.
Danya Уровень 17
13 апреля 2025
нормально нормально
Савелий Уровень 3
18 февраля 2025
когда мы передаём обычный параметр в метод, то мы передаём в него именно значение параметра, а когда используем дженерик, то передаём тип-параметр, который сможем использовать в методе. пример без дженерика:

class IntegerBox {
    private int value;
    public void setValue(int value) { this.value = value; }
    public int getValue() { return value; }
}

class StringBox {
    private String value;
    public void setValue(String value) { this.value = value; }
    public String getValue() { return value; }
}

пример с дженериком:

class Box<T> {  // <T> — это тип-параметр
    private T value;
    
    public void setValue(T value) { this.value = value; }
    public T getValue() { return value; }
}

Agent_Pandora Уровень 26
20 января 2025
Матросы! Впереди шторм, но мы идем дальше⚔☠Тортугаа
Cryptosin Уровень 24
9 января 2025
Почему не работает запись из лекции

ArrayList<Integer>[] array;

Ответ тут: Использование varargs при работе с дженериками