Скидка 20% на курс GRASP & GOF Design Patterns
Узнать больше
10.12.2024
5 минут чтения

Generic в Java: что это и как их использовать

Generics (обобщенные типы) — это одна из важнейших особенностей языка программирования Java, которая была внедрена с версии Java 5. Основная идея generics заключается в том, чтобы обеспечить возможность создания обобщенных классов, интерфейсов и методов, которые могут работать с различными типами данных, сохраняя при этом контроль типов на этапе компиляции.

В этой статье от FoxmindEd мы рассмотрим, что такое generics в Java, как они работают, какие преимущества они предоставляют, а также выясним некоторые их ограничения.

Хотите освоить профессию Java Developer? Присоединяйтесь к программе «От 0 до Strong Java Junior за 12 месяцев». Воспользуйтесь выгодным предложением от FoxmindEd!
Зарегистрироваться

История возникновения

Generics были внедрены в Java с выпуском версии 5, вышедшей в 2004 году. До этого момента Java не имела механизмов для создания обобщенных классов и методов, что приводило к определенным неудобствам и потенциальным ошибкам при выполнении программ. Основной проблемой было то, что при работе с коллекциями, такими как List, Set или Map, все объекты сохранялись как тип Object. Это требовало от разработчиков явного приведения типов при получении элементов из коллекций, что могло привести к классическим ошибкам на этапе выполнения, когда тип данных не соответствовал ожидаемому.

Рассмотрим на примере как было до появления generics. Представим, что у нас есть сумка. И в этой сумке мы можем хранить книги, письма, кошелек… Что угодно, но что-то одно. Как бы мы создали этот класс? Правильно, с использованием Object.

@Data

@AllArgsConstructor

public class Bag {

   private Object item;

}

В чем могут быть проблемы использования такого подхода?

public class Main {

   public static void main(String[] args) {

       String letter = "Letter to Olga";

       Book book = new Book("Harry Potter");

       Bag bagLetter = new Bag(letter);

       Bag bagBook = new Bag(book);

       String item = (String) bagLetter.getItem();

   }

}

Чтобы получить объект нам надо делать кастомизацию. А что, если в Bag не тип String? Если там Book или Integer? Это сейчас код состоит из 5 строчек и все в одном классе. Но в реальных проектах объекты могут сетится в одних классах, доставаться в других. Или кто-то изменит логику и вместо String в bagLetter положит тип Book, а вы будете доставать String. Поэтому допустить ошибку с кастомизацией объектов довольно просто. Конечно, можно сделать проверку:

if (bagLetter.getItem() instanceof String) {

   String item = (String) bagLetter.getItem();     

}

Однако это все усложняет код и добавляет кучу проверок. И в случае, если в Bag лежит не String то сделать ничего с тем нельзя и наша логика в блоке if просто будет игнорироваться. Даже узнать, что внутри Bag (без дженериков) не получится.

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

Понятие generics и его использование

Generic типы позволяют объявить класс или метод таким образом, чтобы он работал с параметризованными типами. Это означает, что при использовании generics вы можете указать, с какими типами данных будет работать класс или метод, что обеспечивает валидацию типов при компиляции и избежание проблем, связанных с неправильным типом данных.

Дженерики чаще всего используются при работе с коллекциями, такими как List, Set, Map и другими. Это позволяет хранить объекты определенного типа в коллекции и избегать необходимости приведения типов при получении данных. Например:

public class Main {

   public static void main(String[] args) {

       List<String> names = new ArrayList<>();

       names.add("Irina");

       names.add("Oleg");

       String firstName = names.get(0); // приведение типа не требуется

   }

}

<> — это специальные скобки, в которых указывается параметризованный тип, с которым будет работать класс или метод. То есть наш массив names принимает только String. С другими типами он не будет работать. Это обеспечивает проверку типов во время компиляции и помогают избежать ошибок, связанных с неправильным приведением типов.

Давайте перепишем наш класс Bag с использованием дженериков:

@Data

@AllArgsConstructor

public class Bag<T> {

   private T item;

}

Как видите, после названия класса мы добавили <T>. Это означает, что мы параметризовали класс. И при его создании мы сразу определяем, какой именно тип наша сумка будет принимать как item, ведь item тоже имеет тип Т. Замечу, что вместо T может быть любая буква или даже слово. Однако самыми распространенными параметрами типов являются:

  • T — Type (общий тип).
  • E — Element (элемент, часто используется в коллекциях).
  • K — Key (ключ, обычно используется в ассоциативных массивах).
  • V — Value (значение, используется вместе с K).
  • N — Number (числовой тип).

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

public class Main {

   public static void main(String[] args) {

       Bag<Book> bookBag = new Bag<>(new Book("Harry Potter"));

       Bag<String> letterBag = new Bag<>("Letter to Olga");

       Bag<Book> bookBag2 = new Bag<>("Letter to Olga"); // будет ошибка помпиляции

Book item = bookBag.getItem(); // кастомизацию делать не надо

   }

}

В результате, для одного и того же класса можно использовать разные типы данных, такие как Book или String, не меняя сам класс. Теперь нам не надо делать кастомизацию, добавлять проверки ведь мы защищены от того, что в сумку вместо Book положат String. Дженерики обеспечивают проверку типов на этапе компиляции. Это означает, что ошибки, связанные с неправильными типами данных, будут обнаружены еще до запуска программы, что значительно снижает количество runtime ошибок.

Подпишитесь на наш Ютуб-канал! Полезные видео для программистов уже ждут вас! YouTube
Выберите свой курс! Путь к карьере программиста начинается здесь! Посмотреть

А что будет, если не параметризировать Bag при его создании?

Bag bookBag = new Bag(new Book("Harry Potter"));

В таком случае T будет восприниматься как Object и мы будем иметь все те же проблемы, о которых была речь выше. Такая запись, когда generics используется без указания конкретного типа, называется Raw Type. Raw Type — это вариант использования класса generics, где тип опускается, что снижает безопасность и гибкость кода, ведь компилятор не сможет контролировать правильность типов.

Заключение

Generics в Java являются одним из самых мощных инструментов для обеспечения гибкости и типобезопасности кода. Они позволяют создавать классы, методы и интерфейсы, которые могут работать с различными типами, сохраняя при этом контроль над правильностью типов во время компиляции.

Generics значительно улучшают качество и гибкость программного обеспечения, особенно при работе с java и коллекциями, где они обеспечивают типобезопасность без потери производительности. Изучив основные принципы и подводные камни generics, можно писать более устойчивый и понятный код, который легко поддерживать и расширять. Если хочешь более углубленных знаний в разработке на Java, регистрируйся на соответствующий курс по программированию от FoxmindEd.

FAQ
Что такое generics в Java?

Generics - это механизм в Java, который позволяет создавать классы, методы и интерфейсы, работающие с параметризованными типами, обеспечивая типобезопасность на этапе компиляции.

Почему generics появились в Java?

Generics внедрили в Java 5 для избежания ошибок, связанных с неправильным приведением типов, которые возникали при работе с коллекциями без параметризации.

Как используются generics с коллекциями?

Generics позволяют задавать типы данных, которые хранятся в коллекциях, например, List хранит только строки, что устраняет необходимость ручного приведения типов.

Что такое Raw Type в generics?

Raw Type - это использование generics без указания конкретного типа, что приводит к возврату типа Object и теряет преимущества проверки типов при компиляции.

Можно ли указать несколько типов в generics?

Да, можно. Например, Map использует два типа: для ключей (K) и значений (V), что позволяет создавать более гибкие структуры данных.

Какие преимущества дженериков?

Generics обеспечивают: 1. типобезопасность во время компиляции. 2. Меньше ошибок во время выполнения. 3. Улучшенную читаемость и поддерживаемость кода.

🤔 Остались вопросы о generics в Java? - смело задавайте ниже! 💬

Добавить комментарий

Ваш имейл не будет опубликован. Обязательные поля отмечены *

Сохранить моё имя, имейл и адрес сайта в этом браузере для будущих комментариев