# 12. Обобщенные типы. Автоупаковка и автораспаковка

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

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MIntLftgNRFnouIpYp4%2F-MInwkRjYdAAbfiTlRnK%2Fimage.png?alt=media\&token=9ffd5aad-deb4-409a-9f6f-b3de80ac38c3)

Обобщенный код будет автоматически работать с типом данных, переданным ему в качестве параметра. Многие алгоритмы выполняются одинаково, независимо от того, к данным какого типа они будут применяться. Например, сортировка не зависит от типа данных, будь то `String`, `Student` или любой другой пользовательский класс, объекты которого можно сравнить между собой. Используя обобщения, можно реализовать алгоритм один раз, а затем применять его без дополнительных усилий к любому типу данных.

Рассмотрим небольшой пример. Рассмотрим класс `Box`, который может содержать в себе один объект.

```java
class Box {
    
    private String item;

    public Box(String item) {
        put(item);
    }

    public void put(String item) {
        this.item = item;
    }

    public String get() {
        return item;
    }
}
```

В данном случае наша коробка хранит объекты класса `String`. Но что, если нам в нашей программе необходима коробка, которая хранила бы не только объекты класса `String`, но и любого другого класса, как стандартного из библиотек Java, так и нашего созданного? Как мы можем реализовать это с помощью уже известных механизмов ООП?

Мы можем использовать механизм полиморфизма. Например, мы можем использовать класс `Object`, который, как известно, является суперклассом для всех классов Java.&#x20;

```java
class Box {

    private Object item;

    public Box(Object item) {
        put(item);
    }

    public void put(Object item) {
        this.item = item;
    }

    public Object get() {
        return item;
    }
}
```

Второй вариант - объявить интерфейс `BoxItem` и использовать его в качеcтве типа ссылочной переменной. Тогда в коробку можно будет "положить" объект класса, который реализует этот интерфейс.

```java
interface BoxItem {}

class Box {

    private BoxItem item;

    public Box(BoxItem item) {
        put(item);
    }

    public void put(BoxItem item) {
        this.item = item;
    }

    public BoxItem get() {
        return item;
    }
}
```

Метод стал чуть более общим и может использоваться в большем количестве мест. Однако, огромный недостаток такого подхода - необходимость нисходящего преобразования извлеченного из коробки объекта. Если же мы не знаем заранее, объект какого класса будет помещен в коробку, то и нисходящее преобразование реализовать будет достаточно затруднительно.

```java
Box box = new Box(new Item());
Object obj = box.get();

if (obj instanceof Item) {
    Item item = (Item)obj;
}
```

Какой выход может быть из данной ситуации? В Java и других ОО-языках программирования существует **механизм обобщенных типов**. Этот механизм позволяет нам создавать классы, методы и интерфейсы, которые будут автоматически работать с типами данных, которые будут переданы позднее, при создании объекта этого класса.

### Обобщенные классы

Реализуем класс `Box` с помощью механизма обобщенных типов

```java
class Box<T> {

    private T item;

    public Box(T item) {
        put(item);
    }

    public void put(T item) {
        this.item = item;
    }

    public T get() {
        return item;
    }
}
```

Такой класс называется обобщенным классом. Под термином **обобщение** следует понимать "**применимость к большой группе классов**". Создадим объект класса `Box`

```java
Box<Item> box = new Box<>(new Item());
```

После создания объекта обобщенного класса, вместо `T` будет подставлен тип `Item`

```java
Box<Item> box = new Box<>(new Item());

box.put(new Item());
Item i = box.get();
```

То есть, нам нет необходимости заниматься нисходящим преобразованием и приведением типов - объект класса `Box` будет работать с классом `Item`.

#### Механизм обобщения работает только с объектами

Когда объявляется экземпляр обобщенного типа, аргумент передаваемый параметру типа должен быть ссылочным типом. Использовать для этого примитивные типы, например, `int` или `char`, нельзя.&#x20;

```java
Box<int> box = new Box<>(20); // ошибка компиляции
```

Для преодоления этой проблемы в Java предусмотрен механизм **автоупаковки (autoboxing)** и **автораспаковки (unboxing)**, который позволяет использовать классы-оболочки типов данных (их еще называют "**обертки**", **wrapper**).

### Механизм автоупаковки и автораспаковки

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

Как мы знаем, в Java предусмотрены 8 примитивных типов данных. Примитивные типы данных имеют преимущества по сравнению с объектами, но механизм обобщенных типов и многие структуры данных в Java предполагают работу с объектами и поэтому в них нельзя хранить данные простых типов.&#x20;

Для решения этой проблемы в Java предусмотрены **классы-обертки** (**wrappers**). Классы-обертки реализуются в классах `Double`, `Float`, `Long`, `Integer`, `Short`, `Byte`, `Character` и `Boolean`. Все обертки числовых типов данных являются производными от абстрактного класса `Number`.

Процесс преобразования значения примитивного типа в объект соответствующего класса-обертки называется **упаковкой** (**boxing**).

Процесс извлечения значения примитивного типа из объекта-обертки называется **распаковкой** (**unboxing**).

**Автоупаковка** - процесс автоматической упаковки (инкапсуляции) простого типа данных в объектную обертку без необходимости явного создания объекта.

```java
Integer i = 5; // автоупаковка
foo(5); // автоупаковка при передачи функции аргумента
Double result = bar(); // автоупаковка при получении результата

void foo(Integer i) {
    ...
}

double bar() {
    return 0.0;
}
```

**Автораспаковка** - это обратный процесс автоматической распаковки (извлечения) значения, упакованного в объектную оболочку.

```java
Integer i = 5;
int a1 = i; // автораспаковка

int a2 = new Integer(10); // старый вариант
int a3 = Integer.valueOf(10); // новый вариант

Integer a4 = 10;
foo(a4); // автораспаковка при передачи функции аргумента

double result = bar(); // автораспаковка при получении результата

static void foo(int i) {
    ...
}

static Double bar() {
    return 0.0;
}
```

### Передача нескольких параметризированных типов

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

```java
DualBox<String, Integer> box = new DualBox<>("Вася", 2);

class DualBox<T, V> {
    private T firstItem;
    private V secondItem;

    public DualBox(T firstItem, V secondItem) {
        this.firstItem = firstItem;
        this.secondItem = secondItem;
    }
}
```

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

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

Для этого необходимо после placeholder написать ключевое слово `extends` и указать имя класса (в этом случае будут доступны объекты этого типа или типов-наследников) или интерфейса (объекты типов, которые реализуют интерфейс).

```java
class NumericValue<T extends Number> {
    private T value;

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

NumericValue<Double> num1 = new NumericValue<>(100.0);
NumericValue<Integer> num2 = new NumericValue<>(100);
NumericValue<Long> num3 = new NumericValue<>(100L);
```

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

```java
class Pair<T, V extends T> {}
```

### Шаблон аргумента (wildcard)

Для того, чтобы иметь возможность использовать обобщенный класс в качестве аргумента метода, необходимо использовать так называемый "**шаблон аргумента**" (**wildcard**).

Предположим, нужно реализовать метод, который сравнивает два объекта класса `NumericValue` и возвращает **`true`**, если оба объекта равны.

Необходимо сообщить компилятору, что входным аргументом является объект обобщенного класса. Написать `NumericValue<T>` не получится, так как это не объявление класса, а записать какое-то конкретное значение мы не можем, так как мы хотим подать объект обобщенного типа с любым допустимым типом данных. Для такого случая используется специальный символ `?`, который и является шаблоном аргумента.

```java
boolean isEquals(NumericValue<?> obj1, NumericValue<?> obj2) {
    double d1 = Math.abs(obj1.value.doubleValue());
    double d2 = Math.abs(obj2.value.doubleValue());

    return d1 == d2;
}

class NumericValue<T extends Number> {
    T value;

    public NumericValue(T value) {
        this.value = value;
    }
}
```

Такая сигнатура метода означает, что метод `isEquals()` принимает на вход аргументы обобщенного типа `NumericValue`, где параметр типа может быть любым допустимым для типа `NumericValue`.

#### Ограничение снизу при использовании wildcard

Кроме "ограничения сверху", мы можем устанавливать "ограничение снизу", то есть установить в качестве корректного типа **этот тип и суперклассы выше по цепочке наследования**. Это реализуется с помощью ключевого слова `super`.

```java
void foo(GenericClass<? super C> obj1, GenericClass<? super B> obj2) {
    // типы obj1 могут быть C, B, A, Object
    // типы obj2 могут быть B, A, Object
}

class GenericClass<T> {}

class A {}
class B extends A {}
class C extends B {}
class D extends C {}
```

### Обобщенные методы

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

К примеру, реализуем методы, который будет сравнивать два массива обобщенных типов. Сначала объявим два класса

```java
class Student {
    private String name;
    private int avgMark;

    public Student(String name, int avgMark) {
        this.name = name;
        this.avgMark = avgMark;
    }
}

class PostGradStudent extends Student {
    private String phDTopic;

    public PostGradStudent(String name, int avgMark, String phDTopic) {
        super(name, avgMark);
        this.phDTopic = phDTopic;
    }
}
```

Далее объявим метод `compareArrays()`

```java
public <T extends Comparable<T>, V extends T> boolean compareArrays(T[] arg0, V[] arg1) {
    if (arg0.length != arg1.length)
        return false;
    
    for (int i = 0; i < arg0.length; i++) {
        if (!arg0[i].equals(arg1[i]))
            return false;
    }

    return true;
}
```

Выражение `T extends Comparable<T>` означает, что тип `T` должен реализовывать обобщенный интерфейс `Comparable<T>`, то есть чтобы мы могли сравнить объект типа `T` с другим объектом типа `T`. Выражение `V extends T` означает, что тип `V` должен быть или типом `T` или производным от `T` типом.

Создадим два массива и попытаемся вызвать метод `compareArrays()`.

```java
Student[] students1 = new Student[10];
PostGradStudent[] students2 = new PostGradStudent[10];

compareArrays(students1, students2);
```

Такой код вызовет ошибку компиляции, так как тип `Student` не реализует интерфейс `Comparable<Student>`.

```java
java: method compareArrays in class com.company.Main cannot be applied to given types;
  required: T[],V[]
  found:    com.company.Student[],com.company.PostGradStudent[]
  reason: inference variable T has incompatible bounds
    lower bounds: java.lang.Comparable<T>
    lower bounds: com.company.Student
```

Исправим данную ошибку

```java
class Student implements Comparable<Student> {
    private String name;
    private int avgMark;

    public Student(String name, int avgMark) {
        this.name = name;
        this.avgMark = avgMark;
    }

    @Override
    public int compareTo(Student o) {
        return this.avgMark - o.avgMark;
    }
}
```

Если мы перепишем класс `PostGradStudent` так, чтобы он не является наследником типа `Student`, то при исполнении кода получим следующую ошибку

```java
java: method compareArrays in class com.company.Main cannot be applied to given types;
  required: T[],V[]
  found:    com.company.Student[],com.company.PostGradStudent[]
  reason: inference variable V has incompatible bounds
    lower bounds: java.lang.Comparable<T>,T
    lower bounds: com.company.PostGradStudent
```

### Обобщенный конструктор

Так как конструктор является методом, то мы также можем объявлять обобщенные конструкторы. Обобщенный конструктор можно объявить даже тогда, когда сам класс не является обобщенным.

```java
class Accumulator {

    public <T extends Number> Accumulator(T number1, T number2) {
        // ...
    }
}

Accumulator accumulator = new Accumulator(5, 10);
Accumulator accumulator2 = new Accumulator(5.5d, 10L);
Accumulator accumulator3 = new Accumulator(5.5d, "four"); // вызовет ошибку
```

### Обобщенный интерфейс

Обобщенный интерфейс объявляется также как и обобщенный класс

```java
interface MyInterface<T> {
    void foo(T value);
}
```

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

```java
interface MyInterface<T> {
    void foo(T value);
}

// Ошибка, класс должен быть обобщенным
class MyClass1 implements MyInterface<T> {
    @Override
    public void foo(T value) {}
}

// Правильное объявление. Класс реализующий
// обобщенный интерфейс должен быть обобщенным
class MyClass2<T> implements MyInterface<T> {
    @Override
    public void foo(T value) {}
}

// Мы явно указали параметр типа интерфейса
// и класс может быть не обобщенным
class MyClass3 implements MyInterface<Double> {
    @Override
    public void foo(Double value) {}
}

```

В интерфейсе можно указать ограничение с помощью ключевого слова `extends`. Класс, который реализует обобщенный интерфейс с ограничением, также должен соблюдать эти ограничения. При объявлении класса ограничение следует прописать после названия класса, но после ключевого слова `implements` его дублировать не надо.

```java
interface Iface<T extends Number> {}

// Выдаст ошибку
class MyClass4<T> implements Iface<T>{}

// Всё правильно
class MyClass5<T extends Number> implements Iface<T> {}

// Выдаст ошибку
class MyClass6<T extends Number> implements Iface<T extends Number>{}

```

### Соглашение по правилам названия параметров типа

Согласно общепринятым правилам и конвенции кода, параметры типов записываются в виде одного символа алфавита в верхнем регистре. Рекомендуется использовать следующие символы алфавита:

* **E** (означает **Element**, часто используется в коллекциях);
* **K** (означает **Key**);
* **N** (означает **Number**);
* **T** (означает **Type**);
* **V** (означает **Value**);
* **S**, **U** - обычно вторые, третьи, четвертые параметры типа.
