Что такое generics в java
Перейти к содержимому

Что такое generics в java

  • автор:

32. Java – Дженерики (обобщения)

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

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

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

Используя концепцию Java Generic, мы можем написать общий метод для сортировки массива объектов, а затем вызвать общий метод с массивами Integer, Double, String и т.д., чтобы отсортировать элементы массива.

Общие методы

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

  • Все объявления обобщенного метода имеют раздел параметров типа, разделенный угловыми скобками ( ), который предшествует возвращаемому типу метода ( в следующем примере).
  • Раздел параметров каждого типа содержит один или несколько параметров типа, разделенных запятыми. Параметр типа, также известный как переменная типа, является идентификатором, который указывает общее имя типа.
  • Параметры типа могут использоваться для объявления возвращаемого типа и действуют как заполнители для типов аргументов, переданных в общий метод, которые известны как аргументы фактического типа.
  • Тело общего метода объявлено как тело любого другого метода. Обратите внимание, что параметры типа могут представлять только ссылочные типы, а не примитивные типы (например, int, double и char).

Пример

Следующий пример показывает, как мы можем выводить массив различного типа, используя один общий:

public class GenericMethodTest < // Общий метод printArray public static < E >void printArray( E[] inputArray ) < // Отображаем элементы массива for(E element : inputArray) < System.out.printf("%s ", element); >System.out.println(); > public static void main(String args[]) < // Создание массивов типа Integer, Double и Character Integer[] intArray = < 1, 2, 3, 4, 5 >; Double[] doubleArray = < 1.1, 2.2, 3.3, 4.4 >; Character[] charArray = < 'П', 'Р', 'И', 'В', 'Е', 'Т'>; System.out.println("Массив integerArray содержит:"); printArray(intArray); // передать массив Integer System.out.println("\nМассив doubleArray содержит:"); printArray(doubleArray); // передать массив Double System.out.println("\nМассив characterArray содержит:"); printArray(charArray); // передать массив Character > > 

Получим следующий результат:

Массив integerArray содержит: 1 2 3 4 5 Массив doubleArray содержит: 1.1 2.2 3.3 4.4 Массив characterArray содержит: П Р И В Е Т 

Параметры ограниченного типа

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

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

Пример

Следующий пример иллюстрирует, как extends используется в общем смысле для обозначения либо «extends» (как в классах), либо «implements» (как в интерфейсах). Это пример общего метода для возврата самого большого из трех объектов Comparable:

public class MaximumTest < // Определяем наибольший из трёх Comparable объектов public static > T maximum(T x, T y, T z) < T max = x; // Предположим, что x изначально максимальный if(y.compareTo(max) >0) < max = y; // здесь y пока что наибольший >if(z.compareTo(max) > 0) < max = z; // здесь z наибольший сейчас >return max; // возвращается максимальный объект > public static void main(String args[]) < System.out.printf("Максимумом из %d, %d и %d является %d\n\n", 3, 4, 5, maximum( 3, 4, 5 )); System.out.printf("Максимумом из %.1f,%.1f и %.1f является %.1f\n\n", 6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 )); System.out.printf("Максимумом из %s, %s и %s является %s\n","груша", "яблоко", "апельсин", maximum("груша", "яблоко", "апельсин")); >> 

Получим следующий результат:

Максимумом из 3, 4 и 5 является 5 Максимумом из 6.6,8.8 и 7.7 является 8.8 Максимумом из груша, яблоко и апельсин является груша 

Общие классы

Объявление общего класса выглядит как объявление не общего класса, за исключением того, что за именем класса следует раздел параметров типа.

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

Пример

Следующий пример показывает, как мы можем определить общий класс:

public class Box  < private T t; public void add(T t) < this.t = t; >public T get() < return t; >public static void main(String[] args) < BoxintegerBox = new Box(); Box stringBox = new Box(); integerBox.add(new Integer(10)); stringBox.add(new String("Привет Мир")); System.out.printf("Значение Integer :%d\n\n", integerBox.get()); System.out.printf("Значение String :%s\n", stringBox.get()); > > 

Получим следующий результат:

Значение Integer :10 Значение String :Hello World 

Оглавление

  • 1. Java – Самоучитель для начинающих
  • 2. Java – Обзор языка
  • 3. Java – Установка и настройка
  • 4. Java – Синтаксис
  • 5. Java – Классы и объекты
  • 6. Java – Конструкторы
  • 7. Java – Типы данных и литералы
  • 8. Java – Типы переменных
  • 9. Java – Модификаторы
  • 10. Java – Операторы
  • 11. Java – Циклы и операторы цикла
  • 11.1. Java – Цикл while
  • 11.2. Java – Цикл for
  • 11.3. Java – Улучшенный цикл for
  • 11.4. Java – Цикл do..while
  • 11.5. Java – Оператор break
  • 11.6. Java – Оператор continue
  • 12. Java – Операторы принятия решений
  • 12.1. Java – Оператор if
  • 12.2. Java – Оператор if..else
  • 12.3. Java – Вложенный оператор if
  • 12.4. Java – Оператор switch..case
  • 12.5. Java – Условный оператор (? 🙂
  • 13. Java – Числа
  • 13.1. Java – Методы byteValue(), shortValue(), intValue(), longValue(), floatValue(), doubleValue()
  • 13.2. Java – Метод compareTo()
  • 13.3. Java – Метод equals()
  • 13.4. Java – Метод valueOf()
  • 13.5. Java – Метод toString()
  • 13.6. Java – Метод parseInt()
  • 13.7. Java – Метод Math.abs()
  • 13.8. Java – Метод Math.ceil()
  • 13.9. Java – Метод Math.floor()
  • 13.10. Java – Метод Math.rint()
  • 13.11. Java – Метод Math.round()
  • 13.12. Java – Метод Math.min()
  • 13.13. Java – Метод Math.max()
  • 13.14. Java – Метод Math.exp()
  • 13.15. Java – Метод Math.log()
  • 13.16. Java – Метод Math.pow()
  • 13.17. Java – Метод Math.sqrt()
  • 13.18. Java – Метод Math.sin()
  • 13.19. Java – Метод Math.cos()
  • 13.20. Java – Метод Math.tan()
  • 13.21. Java – Метод Math.asin()
  • 13.22. Java – Метод Math.acos()
  • 13.23. Java – Метод Math.atan()
  • 13.24. Java – Метод Math.atan2()
  • 13.25. Java – Метод Math.toDegrees()
  • 13.26. Java – Метод Math.toRadians()
  • 13.27. Java – Метод Math.random()
  • 14. Java – Символы
  • 14.1. Java – Метод Character.isLetter()
  • 14.2. Java – Метод Character.isDigit()
  • 14.3. Java – Метод Character.isWhitespace()
  • 14.4. Java – Метод Character.isUpperCase()
  • 14.5. Java – Метод Character.isLowerCase()
  • 14.6. Java – Метод Character.toUpperCase()
  • 14.7. Java – Метод Character.toLowerCase()
  • 14.8. Java – Метод Character.toString()
  • 15. Java – Строки
  • 15.1. Java – Метод charAt()
  • 15.2. Java – Метод compareTo()
  • 15.3. Java – Метод compareToIgnoreCase()
  • 15.4. Java – Метод concat()
  • 15.5. Java – Метод contentEquals()
  • 15.6. Java – Метод copyValueOf()
  • 15.7. Java – Метод endsWith()
  • 15.8. Java – Метод equals()
  • 15.9. Java – Метод equalsIgnoreCase()
  • 15.10. Java – Метод getBytes()
  • 15.11. Java – Метод getChars()
  • 15.12. Java – Метод hashCode()
  • 15.13. Java – Метод indexOf()
  • 15.14. Java – Метод intern()
  • 15.15. Java – Метод lastIndexOf()
  • 15.16. Java – Метод length()
  • 15.17. Java – Метод matches()
  • 15.18. Java – Метод regionMatches()
  • 15.19. Java – Метод replace()
  • 15.20. Java – Метод replaceAll()
  • 15.21. Java – Метод replaceFirst()
  • 15.22. Java – Метод split()
  • 15.23. Java – Метод startsWith()
  • 15.24. Java – Метод subSequence()
  • 15.25. Java – Метод substring()
  • 15.26. Java – Метод toCharArray()
  • 15.27. Java – Метод toLowerCase()
  • 15.28. Java – Метод toString()
  • 15.29. Java – Метод toUpperCase()
  • 15.30. Java – Метод trim()
  • 15.31. Java – Метод valueOf()
  • 15.32. Java – Классы StringBuilder и StringBuffer
  • 15.32.1. Java – Метод append()
  • 15.32.2. Java – Метод reverse()
  • 15.32.3. Java – Метод delete()
  • 15.32.4. Java – Метод insert()
  • 15.32.5. Java – Метод replace()
  • 16. Java – Массивы
  • 17. Java – Дата и время
  • 18. Java – Регулярные выражения
  • 19. Java – Методы
  • 20. Java – Потоки ввода/вывода, файлы и каталоги
  • 20.1. Java – Класс ByteArrayInputStream
  • 20.2. Java – Класс DataInputStream
  • 20.3. Java – Класс ByteArrayOutputStream
  • 20.4. Java – Класс DataOutputStream
  • 20.5. Java – Класс File
  • 20.6. Java – Класс FileReader
  • 20.7. Java – Класс FileWriter
  • 21. Java – Исключения
  • 21.1. Java – Встроенные исключения
  • 22. Java – Вложенные и внутренние классы
  • 23. Java – Наследование
  • 24. Java – Переопределение
  • 25. Java – Полиморфизм
  • 26. Java – Абстракция
  • 27. Java – Инкапсуляция
  • 28. Java – Интерфейсы
  • 29. Java – Пакеты
  • 30. Java – Структуры данных
  • 30.1. Java – Интерфейс Enumeration
  • 30.2. Java – Класс BitSet
  • 30.3. Java – Класс Vector
  • 30.4. Java – Класс Stack
  • 30.5. Java – Класс Dictionary
  • 30.6. Java – Класс Hashtable
  • 30.7. Java – Класс Properties
  • 31. Java – Коллекции
  • 31.1. Java – Интерфейс Collection
  • 31.2. Java – Интерфейс List
  • 31.3. Java – Интерфейс Set
  • 31.4. Java – Интерфейс SortedSet
  • 31.5. Java – Интерфейс Map
  • 31.6. Java – Интерфейс Map.Entry
  • 31.7. Java – Интерфейс SortedMap
  • 31.8. Java – Класс LinkedList
  • 31.9. Java – Класс ArrayList
  • 31.10. Java – Класс HashSet
  • 31.11. Java – Класс LinkedHashSet
  • 31.12. Java – Класс TreeSet
  • 31.13. Java – Класс HashMap
  • 31.14. Java – Класс TreeMap
  • 31.15. Java – Класс WeakHashMap
  • 31.16. Java – Класс LinkedHashMap
  • 31.17. Java – Класс IdentityHashMap
  • 31.18. Java – Алгоритмы Collection
  • 31.19. Java – Iterator и ListIterator
  • 31.20. Java – Comparator
  • 32. Java – Дженерики
  • 33. Java – Сериализация
  • 34. Java – Сеть
  • 34.1. Java – Обработка URL
  • 35. Java – Отправка Email
  • 36. Java – Многопоточность
  • 36.1. Java – Синхронизация потоков
  • 36.2. Java – Межпоточная связь
  • 36.3. Java – Взаимная блокировка потоков
  • 36.4. Java – Управление потоками
  • 37. Java – Основы работы с апплетами
  • 38. Java – Javadoc

Generics

Начиная с JDK 1.5, в Java появляются новые возможности для программирования. Одним из таких нововведений являются Generics. Generics являются аналогией с конструкцией «Шаблонов»(template) в С++, но имеет свои нюансы. Generics позволяют абстрагировать множество типов. Наиболее распространенными примерами являются Коллекции.

Вот типичное использование такого рода (без Generics):

1. List myIntList = new LinkedList(); 2. myIntList.add(new Integer(0)); 3. Integer x = (Integer) myIntList.iterator().next();

Как правило, программист знает, какие данные должны быть в List’e. Тем не менее, стоит обратить особое внимание на Приведение типа («Cast») в строчке 3. Компилятор может лишь гарантировать, что метод next() вернёт Object, но чтобы обеспечить присвоение переменной типа Integer правильным и безопасным, требуется Cast. Cast не только создает беспорядки, но дает возможность появление ошибки «Runtime Error» из-за невнимательности программиста.

И появляется такой вопрос: «Как с этим бороться? » В частности: «Как же зарезервировать List для определенного типа данных?»

Как раз такую проблему решают Generics.

1. List myIntList = new LinkedList (); 2. myIntList.add(new Integer(0)); 3. Integer x = myIntList.iterator().next();

Обратите внимание на объявления типа для переменной myIntList. Он указывает на то, что это не просто произвольный List, а List. Мы говорим, что List является generic-интерфейсом, который принимает параметр типа — в этом случае, Integer. Кроме того, необходимо обратить внимание на то, что теперь Cast выполняется в строчке 3 автоматически.

Некоторые могут задуматься, что беспорядок в коде увеличился, но это не так. Вместо приведения к Integer в строчке 3, у нас теперь есть Integer в качестве параметра в строчке 1. Здесь существенное отличие. Теперь компилятор может проверить этот тип на корректность во время компиляции.

И когда мы говорим, что myIntList объявлен как List, это будет справедливо во всем коде и компилятор это гарантирует.

Эффект от Generics особенно проявляется в крупных проектах: он улучшает читаемость и надежность кода в целом.

Свойства

  • Строгая типизация
  • Единая реализация
  • Отсутствие информации о типе

Пример реализации Generic-класса

public interface List  < E get(int i); set(int i, E e); add(E e); Iteratoriterator(); … >

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

После того как было объявлено имя generic-типа его можно использовать как обычный тип внутри метода. И когда в коде будет объявлен, к примеру, List, то Е станет Integer для переменной list (как показано ниже).

Теперь рассмотрим чем старая реализация кода отличается от новой:

List ─ список элементов E

List list = new List(); list.add(new Integer(1)); Integer i = (Integer) list.get(0);
List list = new List(); list.add(new Integer(1)); Integer i = list.get(0);

Как видите, больше не нужно приводить Integer, так как метод get() возвращает ссылку на объект конкретного типа (в данном случае – Integer).

Несовместимость generic-типов

Это одна из самых важных вещей, которую вы должны узнать о Generics

Как говорится: «В бочке мёда есть ложка дегтя». Для того чтобы сохранить целостности и независимости друг от друга Коллекции, у Generics существует так называемая «Несовместимость generic-типов».

Пусть у нас есть тип Foo, который является подтипом Bar, и еще G - наследник Коллекции. То G не является наследником G.
List li = new ArrayList(); List lo = li;
lo.add(“hello”); // ClassCastException: String -> int Integer li = lo.get(0);

Проблемы реализации Generics

  • Решение 1 — Wildcard

Пусть мы захотели написать метод, который берет Collection и выводит на экран. И мы захотели вызвать dump для Integer.

void dump(Collection c) < for (Iteratori = c.iterator(); i.hasNext(); ) < Object o = i.next(); System.out.println(o); >>
List l; dump(l); List l; dump(l); // Ошибка 

В этом примере List не может использовать метод dump, так как он не является подтипом List.

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

Для решения этой проблемы используется Wildcard («?»). Он не имеет ограничения в использовании(то есть имеет соответствие с любым типом) и в этом его плюсы. И теперь, мы можем вызвать dump с любым типом коллекции.

void dump(Collection c) < for (Iteratori = c.iterator(); i.hasNext(); ) < Object o = i.next(); System.out.println(o); >>
  • Решение 2 – Bounded Wildcard

Пусть мы захотели написать метод, который рисует List. И у Shape есть наследник Circle. И мы хотим вызвать draw для Circle.

void draw(List c) < for (Iteratori = c.iterator(); i.hasNext(); ) < Shape s = i.next(); s.draw(); >>
List l; draw(l); List l; draw(l); // Ошибка 

Проблема в том, что у нас не получится из-за несовместимости типов. Предложенное решение используется, если метод который нужно реализовать использовал бы определенный тип и его подтипов. Так называемое «Ограничение сверху». Для этого нужно вместо прописать .

void draw(List c) < for (Iteratori = c.iterator(); i.hasNext(); ) < Shape s = i.next(); s.draw(); >>
  • Решение 3 – Generic-Метод

Пусть вы захотели сделать метод, который берет массив Object и переносить их в коллекцию.

void addAll(Object[] a, Collection c) < for (int i = 0; i < a.length; i++) < c.add(a[i]); >>
addAll(new String[10], new ArrayList()); addAll(new Object[10], new ArrayList()); addAll(new Object[10], new ArrayList()); // Ошибка addAll(new String[10], new ArrayList()); // Ошибка 

Напомним, что вы не можете просто засунуть Object в коллекции неизвестного типа. Способ решения этой проблемы является использование «Generic-Метод» Для этого перед методом нужно объявить и использовать его.

 void addAll(T[] a, Collection c) < for (int i = 0; i < a.length; i++) < c.add(a[i]); >>

Но все равно после выполнение останется ошибка в третьей строчке :

addAll(new Object[10], new ArrayList()); // Ошибка 
  • Решение 4 – Bounded type argument

Реализуем метод копирование из одной коллекции в другую

 void addAll(Collection c, Collection c2) < for (Iteratori = c.iterator(); i.hasNext(); ) < M o = i.next(); c2.add(o); >>
addAll(new AL(), new AL()); addAll(new AL(), new AL()); //Ошибка

Проблема в том что две Коллекции могут быть разных типов (несовместимость generic-типов). Для таких случаев было придуман Bounded type argument. Он нужен если метод ,который мы пишем использовал бы определенный тип данных. Для этого нужно ввести (N принимает только значения M). Также можно корректно писать . (Принимает значения нескольких переменных)

 void addAll(Collection c, Collection c2) < for (Iteratori = c.iterator(); i.hasNext(); ) < N o = i.next(); c2.add(o); >>
  • Решение 5 – Lower bounded wcard

Реализуем метод нахождение максимума в коллекции.

> T max(Collection c)

List il; Integer I = max(il); class Test implements Comparable  List tl; Test t = max(tl); // Ошибка
  • > обозначает что Т обязан реализовывать интерфейс Comparable.

Ошибка возникает из за того что Test реализует интерфейс Comparable. Решение этой проблемы — Lower bounded wcard(«Ограничение снизу»). Суть в том что мы будет реализовывать метод не только для Т, но и для его Супер-типов(Родительских типов). Например: Если мы напишем

List list;

Мы можем заполнить его List, List или List.

> T max(Collection c)

  • Решение 6 – Wildcard Capture

Реализуем метод Swap в List

void swap(List list, int i, int j) < list.set(i, list.get(j)); // Ошибка >

Проблема в том, что метод List.set() не может работать с List, так как ему не известно какой он List. Для решение этой проблемы используют «Wildcard Capture» (или «Capture helpers»). Суть заключается в том, чтобы обмануть компилятор. Напишем еще один метод с параметризованной переменной и будем его использовать внутри нашего метода.

void swap(List list, int i, int j) < swapImpl(list, i, j); > void swapImpl(List list, int i, int j)

Ограничения Generic

Также нужно запомнить простые правила для работы с Generics.

  • Невозможно создать массив параметра типа
Collection c; T[] ta; new T[10]; // Ошибка !!
  • Невозможно создать массив Generic-классов
new ArrayList>(); List[] la = new List[10]; // Ошибка !!

Преобразование типов

В Generics также можно манипулировать с информацией, хранящийся в переменных.

  • Уничтожение информации о типе
List l = new ArrayList();
  • Добавление информации о типе
List l = (List) new ArrayList(); List l1 = new ArrayList();

Примеры кода

  • Первый пример:
List ls; List li; ls.getClass() == li.getClass() // True ls instanceof List // True ls instanceof List // Запрещено
  • Второй пример:

Нахождение максимума в Коллекции Integer.

Collection c; Iterator i = c.iterator(); Integer max = (Integer) i.next(); while(i.hasNext()) < Integer next = (Integer) i.next(); if (next.compareTo(max) > 0) < max = next; >>
  • С помощью Generics
Collection c; Iterator i = c.iterator(); Integer max = i.next(); while(i.hasNext()) < Integer next = i.next(); if (next.compareTo(max) >0) < max = next; >>

Generics

Дженерики (generics) в языке программирования Java — это сущности, которые могут хранить в себе данные только определенного типа. Например, список элементов, в котором могут быть одни числа. Но не только: дженерик — обобщенный термин для разных структур.

Освойте профессию «Java-разработчик»

Можно представить дженерик как папку для бумаг, куда нельзя положить ничего, кроме документов определенного формата. Это удобно: помогает разделить разные данные и не допустить ситуаций, когда в сущность передается что-то не то.

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

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

Для чего нужны дженерики

С дженериками работают программисты на Java. Без этой возможности писать код, который работает только с определенным видом данных, было сложнее. Существовало два способа, и оба неоптимальные:

  • указывать проверку типа вкоде. Например, получать данные — и сразу проверять, а если они не те, выдавать ошибку. Это помогло бы отсеять ненужные элементы. Но если бы класс понадобилось сделать более гибким, например, создать его вариацию для другого типа, его пришлось бы переписывать или копировать. Не получилось бы просто передать другой специальный параметр, чтобы тот же класс смог работать еще с каким-то типом;
  • полагаться на разработчиков. Например, оставлять в коде комментарий «Этот класс работает только с числами». Слишком велик риск, что кто-то не заметит комментарий и передаст в объект класса не те данные. И хорошо, если ошибка будет заметна сразу, а не уже на этапе тестирования.

Поэтому появились дженерики: они решают эту проблему, делают написание кода проще, а защиту от ошибок надежнее.

Профессия / 14 месяцев
Java-разработчик

Освойте востребованный язык

Group 1321314345 (4)

Как работают дженерики

Чтобы вернее понять принцип работы, нужно представлять, как устроены сущности в Java. Есть классы — это как бы «чертежи» будущих сущностей, описывающие, что они делают. И есть объекты — экземпляры классов, непосредственно существующие и работающие. Класс — как схема машины, объект — как машина.

Когда разработчик создает дженерик-класс, он приписывает к нему параметр в треугольных скобках — метку. К примеру, так:

Теперь при создании объекта этого класса нужно будет указать на месте T название типа, с которым будет работать объект. Например, myClass для целых чисел или myClass для строк. Сам класс остается универсальным, то есть общим. А вот каждый его объект специфичен для своего типа.

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

Что такое raw types

В Java есть понятие raw types. Так называют дженерик-классы, из которых удалили параметр. То есть изначально класс описали как дженерик, но при создании объекта этого класса тип ему не передали. То есть что-то вроде myClass<> — тип не указан.

Дословно это название переводится как «сырые типы». Пользоваться ими сейчас в коммерческой разработке — чаще всего плохая практика. Но в мире все еще много старого кода, который написали до появления дженериков. Если такой код еще не успели переписать, в нем может быть очень много «сырых типов». Это надо учитывать, чтобы не возникало проблем с совместимостью.

Дженерики-классы и дженерики-методы

Выше мы говорили, что дженериками могут быть разные сущности. Разберемся подробнее:

  • дженерик-классы (generic classes) это классы, «схемы» объектов с параметром. При создании объекта ему передается тип, с которым он будет работать;
  • дженерик-методы (generics methods) это методы, работающие по такому же принципу. Метод — это функция внутри объекта, то, что он может делать. Методу тип передается при вызове, сразу перед аргументами. Так можно создавать более универсальные функции и применять одну и ту же логику к данным разного типа.

Кстати, дженериками могут быть и встроенные классы или методы, и те, которые разработчик пишет самостоятельно. Например, встроенный ArrayList — список-массив — работает как дженерик.

Станьте Java-разработчиком
и создавайте сложные сервисы
на востребованном языке

Что будет, если передать дженерику не тот тип

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

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

А если отправить «не тот» тип объекту без дженерика, действия с ним выполнятся с ошибкой. Но по этой ошибке не всегда очевидно, чем она вызвана. Худший вариант — код успешно запустится, но сработает неправильно: так ошибку будет найти еще сложнее.

Особенности дженериков

У дженериков есть несколько особенностей, о которых стоит знать при работе с ними. Если не учитывать эти детали, программировать будет как минимум менее удобно. А как максимум можно допустить ошибку и не понять, куда она закралась.

Выведение типа. Эта особенность касается объявления экземпляра класса, то есть создания объекта. Полная запись создания будет выглядеть так:

myClass objectForIntegers = new myClass();

objectForIntegers — это название объекта, оно может быть любым. То, что находится после знака «равно», — непосредственно команда «создать новый экземпляр класса».

Но полная запись очень громоздкая. Поэтому современные компиляторы Java способны на выведение типа — автоматическую его подстановку в записи после первого упоминания. То есть конструкцию myClass понадобится написать только один раз.

Запись, в которой программист пользуется возможностью выведения типа, будет выглядеть так:

myClass objectForIntegers = new myClass<>();

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

Стирание типов. Важная деталь, которая касается работы дженериков, — они существуют только на этапе компиляции. В этом их суть: «не пропускать» данные ненужного типа в объект, а такие вещи определяет компилятор.

После компиляции код на Java превращается в байт-код. И на этом уровне никаких дженериков нет. myClass и myClass в байт-коде будут идентичны, просто с разными данными внутри.

Это называется стиранием типов. Суть в том, что внутри дженерик-класса нет информации о его параметре и после компиляции эти сведения просто исчезают. Так сделали, потому что дженерики появились в Java не сразу. Если бы информацию о параметре добавили в байт-код, это сломало бы совместимость с более старыми версиями.

О стирании типов важно помнить. Для запущенной программы в байт-коде дженериков не существует, и это может вызвать ошибки. Например, при сравнении myClass и myClass программа скажет, что они одинаковые. А иногда в объект в запущенном коде и вовсе получается передать данные другого типа.

«Дикие карты». Еще одна интересная и полезная особенность дженериков — так называемые wildcards, или «дикие карты». Это термин из спорта, означающий особое приглашение спортсмена на соревнование в обход правил. А в карточных играх так называют карты, которые можно играть вместо других, например джокера.

В основе wildcards в Java лежит такая же идея: изменить предустановленное поведение и сделать что-то в обход установленных рамок. Когда объявляется «дикая карта», в треугольных скобках вместо названия типа ставится вопросительный знак. Это означает, что сюда можно подставить любой тип.

Подставить wildcard можно не везде. Например, при создании класса это сделать не получится, а при объявлении объекта этого класса — получится. Чаще всего «дикую карту» используют при работе с переменными и с коллекциями.

Ограниченные «дикие карты». Кроме стандартной wildcard, существует еще несколько типов — ограниченные «дикие карты». С их помощью можно передать в объект данные не только конкретного типа, но и унаследованных от него — «потомков». Или же «предков» — типов, от которых был унаследован упомянутый.

Ограниченный wildcard описывается как вопросительный знак, за которым следует правило.

Есть два вида ограничений:

  • upper bounding — ограничение сверху. За вопросительным знаком следует слово extends и название типа. В такой дженерик можно передавать названный тип и его потомков;
  • lower bounding — ограничение снизу. Ситуация наоборот: за вопросительным знаком слово super и тип, а подставлять можно элементы этого типа и его предков.

Скорее всего, впервые столкнуться с дженериками придется еще в начале изучения Java, просто новичку не сразу понятно, что это такое. Со временем появляется понимание, как работает эта конструкция, и становится легче решать более сложные задачи.

Java-разработчик

Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.

картинка (67)

Статьи по теме:

Использование Generics в Java с классом и интерфейсом

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

Существует типичная проблема, с которой можно столкнуться при работе с Generics. Предположим, требуется создать обобщенный класс, который может принимать только те классы, которые расширяют определенный суперкласс и реализуют определенный интерфейс. Возникает вопрос, как это сделать?

Рассмотрим следующие примеры:

Class
Class

В этих примерах класс ограничен суперклассом ClassA и интерфейсом InterfaceB соответственно. Однако, следует отметить, что в Java нельзя расширить более одного класса. Тем не менее, класс может реализовывать несколько интерфейсов. Это означает, что невозможно одновременно расширить ClassA и реализовать InterfaceB.

Однако, есть способ обойти это ограничение. Вместо того, чтобы пытаться прямо ограничить класс по суперклассу и интерфейсу, можно создать новый интерфейс, который расширяет интерфейс B и имеет метод, возвращающий экземпляр класса A.

Пример такого интерфейса:

interface C extends B

Теперь можно создать обобщенный класс, ограниченный только интерфейсом C:

Class<? extends C>

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

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

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