Java for Beginner
675 subscribers
559 photos
156 videos
12 files
856 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Дженерики

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

Зачем нужны дженерики?

Безопасность типов: Дженерики позволяют обнаруживать ошибки на этапе компиляции, предотвращая проблемы, связанные с приведением типов и некорректным использованием объектов.
Повторное использование кода: Дженерики позволяют создавать универсальные классы, интерфейсы и методы, которые могут работать с любыми типами данных.
Читаемость и удобство кода: Код с дженериками легче читать и понимать, так как не нужно явно приводить типы.


Основные концепции дженериков

Обобщенные классы: Классы, которые могут работать с любыми типами данных.
public class Box<T> {
private T value;

public void set(T value) {
this.value = value;
}

public T get() {
return value;
}
}


В этом примере T — это параметризованный тип. При создании экземпляра класса Box вы можете указать конкретный тип:
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
System.out.println(integerBox.get());

Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get());


Обобщенные методы: Методы, которые могут работать с любыми типами данных.
public class Util {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}


Вызов обобщенного метода:
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"one", "two", "three"};

Util.printArray(intArray);
Util.printArray(strArray);


Обобщенные интерфейсы: Интерфейсы, которые могут работать с любыми типами данных.
public interface Pair<K, V> {
K getKey();
V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;

public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}

@Override
public K getKey() {
return key;
}

@Override
public V getValue() {
return value;
}
}


Использование обобщенного интерфейса:
Pair<String, Integer> pair = new OrderedPair<>("One", 1);
System.out.println("Key: " + pair.getKey() + ", Value: " + pair.getValue());


Преимущества дженериков

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


Рассмотрим простой пример с использованием дженериков. Создадим класс, который может хранить пару объектов любого типа:
public class Pair<T, U> {
private T first;
private U second;

public Pair(T first, U second) {
this.first = first;
this.second = second;
}

public T getFirst() {
return first;
}

public U getSecond() {
return second;
}

public void setFirst(T first) {
this.first = first;
}

public void setSecond(U second) {
this.second = second;
}

@Override
public String toString() {
return "Pair{" + "first=" + first + ", second=" + second + '}';
}
}


Теперь мы можем использовать этот класс с любыми типами данных:
Pair<Integer, String> pair = new Pair<>(1, "One");
System.out.println(pair); // Output: Pair{first=1, second=One}

pair.setFirst(2);
pair.setSecond("Two");
System.out.println(pair); // Output: Pair{first=2, second=Two}


#Java #Training #Generics
Продвинутые возможности дженериков

Ограничения типов

Ограниченные параметры типов (Bounded Type Parameters): Позволяют задать верхние и нижние границы для параметризованных типов.
public class Box<T extends Number> {
private T value;

public void set(T value) {
this.value = value;
}

public T get() {
return value;
}
}

В этом примере T должен быть подклассом Number.


Множественные ограничения (Multiple Bounds): Параметр типа может быть ограничен несколькими типами.
public <T extends Number & Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (T element : array) {
if (element.compareTo(max) > 0) {
max = element;
}
}
return max;
}


Подстановочные знаки (Wildcards)

Неограниченные подстановочные знаки: Позволяют параметризованному типу принимать любой тип.
public void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}


Ограниченные сверху подстановочные знаки (Upper Bounded Wildcards): Позволяют параметризованному типу принимать любой тип, который является подклассом указанного.
public void addNumbers(List<? extends Number> list) {
double sum = 0;
for (Number number : list) {
sum += number.doubleValue();
}
System.out.println("Sum: " + sum);
}


Ограниченные снизу подстановочные знаки (Lower Bounded Wildcards): Позволяют параметризованному типу принимать любой тип, который является суперклассом указанного.
public void addIntegers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}


Преимущества и недостатки дженериков


Преимущества:

Повышение безопасности типов.
Уменьшение необходимости приведения типов.
Повышение читаемости и повторного использования кода.


Недостатки:

Усложнение кода для начинающих программистов.
Невозможность использовать примитивные типы в качестве параметров типов.
Ограничения, связанные с типизацией во время выполнения (Type Erasure).
Типизация во время выполнения (Type Erasure)
Дженерики в Java реализованы с использованием механизма стирания типов (Type Erasure). Это означает, что информация о параметризованных типах удаляется во время компиляции, и все операции с дженериками выполняются с использованием типов Object или соответствующих ограниченных типов. Это позволяет обеспечить совместимость с кодом, не использующим дженерики, но накладывает некоторые ограничения, такие как невозможность использования примитивных типов и невозможность создания массивов параметризованных типов.


Примеры сложных случаев использования дженериков

Рекурсивные обобщенные типы:
public class ComparableBox<T extends Comparable<T>> {
private T value;

public ComparableBox(T value) {
this.value = value;
}

public T getValue() {
return value;
}

public int compareTo(ComparableBox<T> other) {
return value.compareTo(other.getValue());
}
}


Обобщенные методы в обобщенных классах:
public class Util {
public static <T> boolean compare(Box<T> box1, Box<T> box2) {
return box1.get().equals(box2.get());
}
}


Использование:
Box<Integer> box1 = new Box<>();
box1.set(10);
Box<Integer> box2 = new Box<>();
box2.set(10);

boolean result = Util.compare(box1, box2); // true


#Java #Training #Generics