В чем смысл дженериков?
Одно из определений из Java docs:
Дженерики позволяют вам абстрагироваться от типов. Наиболее распространенными примерами являются типы контейнеров, например, в иерархии коллекций.
по-английски:
Абстрагироваться от типа означает группировать похожие и обобщать их.
Например: что нам делать, если нам нужно сохранить имя, фамилию и адрес электронной почты нескольких людей, создаем ли мы n количество переменных для хранения и извлечения их вручную операционная мы создаем над ним абстракцию, известную как Class .
Отлично! , Теперь мы знаем, что означает абстрактный над типом, но я все еще не понимаю, насколько это выгодно для нас???
Короче говоря: Generics предоставляет нам проверку типов во время компиляции и избавляет от необходимости ручного приведения типов.
Подробнее: Давайте выясним
Давайте рассмотрим пример того, как работала коллекция до внедрения дженериков.
List numList = new ArrayList(); // 1 numList.add(new Integer(1)); // 2 Integer x = (Integer) numList.iterator().next(); // 3
Строка 1 и строка 2 просты, мы создали ArrayList и добавили целое число.
В строке 3 у нас есть кое-что, что выглядит болезненно и, вероятно, вызовет у вас небольшое раздражение Ручное преобразование типов
Просто представьте, что вы делаете это каждый раз.
В приведенном выше случае, даже если разработчик знает, что данные являются целыми во время компиляции, нам все равно нужно вручную преобразовать их в целое число.
Это был лучший сценарий, когда, несмотря на то, что он выглядит плохо, он все равно работает что, если
List list = new ArrayList(); //1 list.add("abc"); //2 list.add(new Integer(5)); //3 for(Object obj : list){ Integer numbers=(Integer) obj; }
Строка 1 мы создали ArrayList Строка 2 мы добавляем строку в список Строка 3 мы добавляем целое число в список
Подождите, что!! Сначала мы добавили строку, а теперь целое число в один и тот же список
Хорошо, что дальше, мы перебираем список и пытаемся вручную ввести приведение его к целому числу
Итак, мы добавили строку и целое число, и теперь мы ожидаем, что оба они будут преобразованы в целое число
Ты знаешь, что будет дальше
ClassCastException Исключение класса 🎆 💥
Мы все знаем кого-то, кто способен на это 😉
Какие проблемы мы обнаружили до сих пор:
- Нам нужна абстракция поверх них, чтобы нам не нужно было каждый раз преобразовывать ее вручную
- Нам нужна проверка во время компиляции, чтобы спасти нас от ClassCastException
Это то, в чем нам помогает наш спаситель дженериков
Как? Давайте выясним
ListlistOfString = new ArrayList<>(); //1 listOfString.add("Generics"); //2 listOfString.add("are"); //3 listOfString.add("Awesome"); //4 for(String str : listOfString){ //5 //no type casting needed, avoids ClassCastException }
Большинство строк в приведенном выше коде выглядит так же, как и раньше , за исключением того , что ручная типизация исчезла 😌 .
Также есть кое-что новое в строке 1 List ArrayList<>();
<>
<>
Вот и все, что нам нужно сделать для добавления безопасности типов во время компиляции.
Что делать, если кто-то попытается добавить целое число в приведенный выше список?
Если мы попытаемся добавить целое число, мы получим ошибку во время компиляции.
Итак, можем ли мы использовать дженерики только с коллекциями?
Нет, Коллекции – это всего лишь одно из мест, где мы можем использовать дженерики. Давайте посмотрим на другие места, где мы можем использовать дженерики.
Давайте начнем с малого
Универсальный интерфейс
interface Print{ void display(T input); }
Мы можем создавать универсальные интерфейсы, которые могут быть реализованы классами с соответствующим типом данных
class PrintInteger implements Print{ @Override public void display(Integer input) { System.out.println("Printing an int "+input); } }
Мы реализовали интерфейс печати и указали тип данных как целое число.
Генетический класс и общие методы
Давайте рассмотрим ситуацию, когда мы хотим сохранить имя, фамилию и идентификатор сотрудника, но тип данных идентификатора может быть интересным, длинным или строковым.
Код без дженериков
class EmployeeWithIntegerId { private String firstName; private String lastName; private int id; } class EmployeeWithLongId { private String firstName; private String lastName; private long id; } class EmployeeWithStringId { private String firstName; private String lastName; private String id; }
Мы видим, что без использования дженериков мы должны создать 3 разных класса, чтобы соответствовать ситуации.
Теперь давайте используем дженерики, чтобы выбраться из этой ситуации
Код с использованием дженериков
class Employee{ private String firstName; private String lastName; private T id; }
T в приведенном выше коде представляет тип данных нашего идентификатора, который будет определен нами при создании экземпляра класса.
Является ли T обязательным для использования?
Нет, T – это просто наиболее часто используемый вариант, но он не является обязательным. Мы можем использовать любой алфавит, который нам нравится, вместо T .
Для людей, которые интересуются лучшими практиками в области именования
The most commonly used type parameter names are: E - Element (used extensively by the Java Collections Framework) K - Key N - Number T - Type V - Value S,U,V etc. - 2nd, 3rd, 4th types
любезно предоставлено javadocs
Теперь, когда мы используем дженерики, нам нужно было создать только один класс, где единственное, что нужно было решить, – это тип нашей переменной id.
Хорошо, но как будут работать геттеры, сеттеры и конструкторы??
Конструктор
public Employee(String firstName, String lastName, T id) { this.firstName = firstName; this.lastName = lastName; this.id = id; }
Конструктор будет выглядеть так же, как и раньше. Это один из лучших и простых для понимания примеров универсальных методов.
Добытчики и сеттеры
public T getId() { return id; } public void setId(T id) { this.id = id; }
Итак, мы определили класс, так как же мы можем его использовать??
Employeeemployee= new Employee<>("yash","sugandh","1234");
В приведенном выше примере мы создали объект класса Employee и при его создании использовали оператор <>
для определения типа данных T как String.
Существует ограничение на тип данных, из-за которого мы не можем использовать примитивные типы данных, поэтому мы не можем использовать int, мы должны использовать Integer, Long вместо long и так далее.
Теперь мы знаем о генетике и о том, как она помогает нам в коллекциях, интерфейсе, классах и методах.
Но все еще остается несколько вопросов
- Что такое подстановочные знаки в дженериках?
- Что означает ограниченный и неограниченный подстановочный знак?
- Как Java обеспечивает обратную совместимость от кода с дженериками к коду без дженериков?
Давайте рассмотрим все эти темы в следующем посте.
Пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы в комментариях ниже.
Увидимся в забавных газетах 🚀
Оригинал: “https://dev.to/yashsugandh/generics-in-java-3mb5”