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);

Комментарии (336)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Georgy Dmitriev Уровень 19
20 июля 2025
Серьезно???? дженерикам 1 лекция посвящена..... мда
Anonymous #3525688 Уровень 23
22 сентября 2025
Там же вроде ясно сказали (раза 3 за лекцию), что Generics мы будем проходить в квесте Java Collections :/
Anonymous #3585174 Уровень 33
1 июля 2025
Like
Алексей Уровень 7
14 июня 2025
Хмелов - самый крутой лектор
DragonIT Уровень 38
27 июня 2025
тут прям не поспоришь, мне повезло видимо, что к нему попал на Универе :)
Danya Уровень 17
13 апреля 2025
нормально нормально
Савелий Уровень 66
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 при работе с дженериками
Кирилл Уровень 1
8 января 2025
ArrayList<Integer> list; Создание переменных list = new ArrayList<Integer> (); Создание объектов
Кирилл Уровень 1
8 января 2025
в чем разница между объектом и переменной? это все что меня заинтересовало пока простым языком пожалуйста
Кирилл Уровень 1
8 января 2025
String a = "Hello" И вот так: String a = new String("Hello") в чем разница
Диана Белова Уровень 32
8 января 2025
Разница между объектами и переменными в программировании заключается в их назначении и функциональности: Переменные: - Это контейнеры для хранения данных. Они имеют имя и тип (например, int, String, ArrayList и т.п.). - Переменная только содержит ссылку или значение, но сама по себе не является объектом. Например, в int x = 5; переменная x хранит значение 5. Объекты: - Это экземпляры классов, которые могут иметь состояние (сохраняемые данные) и поведение (методы, которые могут выполняться). - Объекты создаются на основе классов и могут представлять более сложные структуры, такие как ArrayList, которые управляют коллекцией данных. - Например, в случае ArrayList<Integer> list = new ArrayList<Integer>();, сами ArrayList и его элементы являются объектами. В общем, переменные содержат ссылки на объекты или примитивные значения, а объекты представляют собой конкретные реализации классов с состоянием и поведением. Это ответ от нейросети, зарегайся на какой-нибудь, очень удобно у нее что то спрашивать если надо разжевать какой то материал.
Диана Белова Уровень 32
8 января 2025
В Java создание массива ArrayList делится на две части: объявление переменной и создание объекта. Вот как это выглядит: Объявление переменной: ArrayList<Integer> list; Здесь вы объявляете переменную list типа ArrayList<Integer>, но пока она не инициализирована (не ссылается на объект). Создание объекта: list = new ArrayList<Integer>(); Здесь создаётся новый объект ArrayList и переменной list присваивается ссылка на этот объект. Теперь list указывает на реально существующий ArrayList, и вы можете использовать его для хранения целых чисел. Таким образом, в первом шаге вы создаёте лишь ссылку на возможный объект, а во втором — создаёте сам объект.
Floridova Alina Уровень 32 Expert
9 января 2025
у тебя в ящиках секретера хранятся сладости. Ящики подписаны (шоколадка, зефир, конфета и т.д.) переменная - это ящичек, в котором хранится объект (сладость). Тип переменной - табличка (какая сладость в ней хранится). Тип объекта - это сам тип сладости. Ты не можешь положить в ящичек с надписью "Зефир" печеньку, потому что это другой тип. Но если у тебя на ящике написано просто "Вкусняшка" (это аналогия с "Object") - то ты можешь туда положить и конфету, и мершмеллоу (потому что они все вкусняшки). Ты можешь вызвать метод "Съесть вкусняшку" (он есть у всех вкусняшек),но не можешь у этого ящичка вызвать метод "Приготовить на огне". Этот метод есть только у Мершмеллоу и вызвать его можно только у ящичков, которые подписаны "Мершмеллоу" (в них точно не будет других типов сладостей, кроме мершмеллоу и его подвидов).
19 апреля 2025
переменная = контейнер / коробка которая что то хранит. переменная имеет тип хранимых данных это может быть String, int, Rabbit и т.д. объект = непосредственно данные. "Кролик в шляпе" - шляпа = переменная, кролик = объект.
Alina Gabidulina Уровень 13
17 мая 2025
а как ты первого лвла и тут находишься?сбросил лвл акка?тогда почему возникают такие вопросы?
Yasin Akhmadov Уровень 22
2 января 2025
Если говорить обобщённо, то Дженерики решили недостатки старых коллекций, которые использовали объект массив класса Object, с целью хранения объектов разных классов. Решили тем, что мы можем не беспокоиться о том, что в коллекцию какого-то типа не будут попадать объекты другого типа. Также не приходится вручную приводить типы, за нас это делает компилятор.
Yasin Akhmadov Уровень 22
2 января 2025
Круто, конечно, что ArrayList, хранящий массив типа Object, способен хранить ссылки на объекты разных классов. Но для их изъятия с целью присвоить другой переменной нужно сразу использовать приведение типа, ведь класс Object методов как у других классов много не имеет