Java. Узагальнення. Параметризовані типи. Узагальнені класи




Java. Узагальнення. Параметризовані типи. Узагальнені класи


Зміст


Пошук на інших ресурсах:

1. Що таке “узагальнення” в мові Java? Що таке параметризований тип? Особливості застосування узагальнень

Узагальнення – це механізм побудови програмного коду для деякого типу з довільним іменем з метою його подальшого конвертування (перетворення) у будь-який конкретний посилальний тип. Реалізацію конвертування з узагальненого типу в деякий конкретний здійснює компілятор.

Узагальнення можуть бути застосовані до класів, інтерфейсів чи методів. Якщо клас, інтерфейс чи метод оперує деяким узагальненим типом T, то цей клас (інтерфейс, метод) називається узагальненим. Тип, що отримує узагальнений клас в якості параметру називається параметризованим типом. Ім’я параметризованого типу можна задавати будь-яким (T, Type, TT і т.д.).

 

2. Переваги застосування узагальнень

Використання узагальнень в мові Java дає наступні переваги:

  • забезпечується компактність програмного коду;
  • завдяки узагальненням усі операції приведення типів виконуються автоматично і неявно. Повторно використовуваний код обробляється більш легше та безпечніше;
  • узагальнення забезпечують типову безпеку типів на відміну від використання посилань на тип Object. Як відомо, у мові Java усі класи чи інтерфейси є підкласами класу Object. Це означає, що з допомогою посилання на клас Object можна оперувати різнотипними об’єктами. Однак, такий спосіб не забезпечує типової безпеки типів.

 

3. Загальна форма оголошення узагальненого класу та оголошення посилання на узагальнений клас

Щоб метод деякого класу оперував узагальненим типом Type потрібно щоб клас (інтерфейс) був оголошений як узагальнений. У мові Java допускається оголошення класу для одного або декількох узагальнених типів. Загальна форма класу, що використовує узагальнені типи, наступна:

class ClassName<Type1, Type2, ..., TypeN> {

    // ...

}

тут

  • ClassName – ім’я класу;
  • Type1, Type2, TypeN – типи, з якими оперує клас ClassName.

Найчастіше узагальнений клас оперує з одним типом. У цьому випадку загальна форма класу має вигляд:

class ClassName<Type> {
    // ...
}

Загальна форма оголошення посилання на узагальнений клас наступна

ClassName<Type1, Type2, ..., TypeN> VarName =
    new ClassName<Type1, Type2, ..., TypeN>(argument_list);

де

  • ClassName – ім’я класу;
  • VarName – ім’я посилання на екземпляр класу, який створюється оператором new;
  • argument_list – список аргументів, які отримує конструктор класу.

Наприклад, якщо в програмі оголосити клас, що отримує параметром тип T

class SomeClass<T> {

}

то в цьому класі можна реалізовувати змінні та методи, які мають тип T

class SomeClass<T> {
    // Оголошення змінних з типом T
    T var1, var2, ..., varN;

    // Оголошення методу, що використовує тип T
    public return_type SomeMethod(T param1, T param2, ..., T paramN) {
        // Реалізація методу SomeMethod(), що використовує тип T
        // ...
    }
}

Після оголошення, використання вищенаведеного класу для типу Integer буде таким

SomeClass<Integer> objInt = new SomeClass<Integer>();
objInt.SomeMethod(arg1, arg2, ..., argN);

У вищенаведеному оголошенні тип Integer є аргументом типу.

Створення екземпляру класу для типу Double буде таким

SomeClass<Double> objDouble = new SomeClass<Double>();

Подібним чином узагальнений клас SomeClass<T> може використовуватись для будь-яких інших типів.

 

4. Які типи забороняється використовувати в узагальнених класах в якості параметризованих типів?

При використанні узагальнених класів параметр типу повинен бути тільки посилального типу. При використанні узагальненого класу забороняється використовувати базові типи (int, char, double і т.д.) в якості аргументу типу.

Тобто, якщо задано клас

class SomeClass<T> {
    // ...
}

то оголосити екземпляр типу int чи іншого базового типу не вдасться

SomeClass<int> objInt = new SomeClass<int>(); // Помилка

Замість типу int потрібно використовувати клас-обгортку Integer.
Аналогічно для інших базових типів потрібно використовувати класи-обгортки: double -> Double, float->Float, boolean->Boolean і т.д.

 

5. Приклад узагальненого класу, що реалізує метод пошуку елементу в двовимірній матриці

Оголошується узагальнений клас GenericMatrix<Type>, в якому реалізовано метод SearchKey() що обчислює кількість входжень заданого елементу key в матриці.
Метод SearchKey() отримує наступні параметри:

  • M – вихідна матриця узагальненого типу Type[][], в якій здійснюється пошук ключа key;
  • m, n – розмірність матриці, кількість рядків та стовпців відповідно;
  • key – ключ (елемент), кількість входжень якого в матрицю M потрібно обчислити.

 

// Узагальнений клас, який оперує з типом Type
class GenericMatrix<Type> {

  // Метод, що здійснює підрахунок кількості елементів key у двовимірній матриці M
  public int SearchKey(Type[][] M, int m, int n, Type key) {
    int count = 0;

    for (int i=0; i<m; i++)
      for (int j=0; j<n; j++)
        if (key==M[i][j])
          count++;

    return count;
  }
}

public class TestGeneric<Type> {

  public static void main(String[] args) {

    // Демонстрація використання методу SearchKey в класі GenericMatrix<Type>

    // Оголошення внутрішніх змінних
    int count;

    // Вихідна матриця
    Integer[][] MI = {
      { 2, 5, -8 },
      { 3, 1, 5 },
      { 4, 8, 2 },
      { 5, 1, 8 }
    };

    // Створити екземпляр класу GenericMatrix<Type>
    GenericMatrix<Integer> obj = new GenericMatrix<Integer>();

    // Викликати метод SearchKey() екземпляру
    count = obj.SearchKey(MI, 4, 3, 8);

    // Вивести результат
    System.out.println("count = " + count); // count = 2
  }
}

 

6. Приклад реалізації методу, що здійснює циклічний зсув у масиві узагальненого типу Type

У прикладі реалізовано клас GenericClass<Type>, який містить наступні методи:

  • CyclicShift() – реалізує циклічний зсув в одновимірному масиві, елементи якого мають узагальнений тип Type;
  • Print() – реалізує виведення масиву узагальненого типу Type на екран;
  • main() – демонструє використання класу.

 

// Клас, в якому є метод циклічного зсуву в масиві узагальненого типу Type
public class GenericClass<Type> {

  // Циклічний зсув в масиві A на count позицій.
  // Параметри:
  // - A - вихідний масив;
  // - count - кількість позицій, на які здійснюється зсув;
  // - direction - напрямок (true - вліво, false - вправо).
  public void CyclicShift(Type[] A, int count, boolean direction)
  {
    Type item;
    int iterations = (int)(count % A.length); // забрати зайві ітерації

    if (direction)
    {
      // Зсув вліво
      for (int i=0; i<iterations; i++)
      {
        item = A[0];
        for (int j=0; j<A.length-1; j++)
          A[j] = A[j+1];
        A[A.length-1] = item;
      }
    }
    else
    {
      // Зсув вправо
      for (int i=0; i<iterations; i++)
      {
        item = A[A.length-1];
        for (int j=A.length-2; j>=0; j--)
          A[j+1] = A[j];
        A[0] = item;
      }
    }
  }

  // Метод, що виводить масив узагальненого типу Type з коментарієм text.
  public void Print(Type[] A, String text) {
    System.out.println(text);
    for (int i=0; i<A.length; i++) {
      System.out.print(A[i] + " | ");
    }
    System.out.println();
  }

  public static void main(String[] args) {

    // Демонстрація використання методу CyclicShift() в класі TestGeneric<Type>
    // для типу String

    // Вихідний масив
    String[] AS = { "1-abc", "2-cde", "3-fgh", "4-jklmn", "5-jprst" };

    // Оголосити екземпляр класу TestGeneric<Type>
    TestGeneric<String> obj = new TestGeneric<String>();

    // Вивести масив AS
    obj.Print(AS, "Array AS. Before: ");

    // Реалізувати циклічний зсув для типу String
    obj.CyclicShift(AS, 3, true); // зсунути на 3 позиції вліво

    // Вивести знову масив AS
    obj.Print(AS, "Array AS. After:");

    // -------------------------------------
    // Використання методу CyclicShift() для посилального типу Double
    Double[] AD = { 1.5, 1.3, 1.1, 0.8, 0.5, 0.2 };
    TestGeneric<Double> objD = new TestGeneric<Double>();

    objD.Print(AD, "Array AD. Before: ");
    objD.CyclicShift(AD, 2, false); // Зсунути на 2 позиції вправо
    objD.Print(AD, "Array AD. After:");

    // Використання методу CyclicShift() для посилального типу Interger
    Integer[] AI = { 5, 3, 2, -1, -8, 4, 3, 0, 12 };
    TestGeneric<Integer> objI = new TestGeneric<Integer>();
    objI.Print(AI, "Array AI. Before:");
    objI.CyclicShift(AI, 6, true); // зсунути на 6 позицій вліво
    objI.Print(AI, "Array AI. After: ");
  }
}

Результат роботи програми

Array AS. Before:
1-abc | 2-cde | 3-fgh | 4-jklmn | 5-jprst |
Array AS. After:
4-jklmn | 5-jprst | 1-abc | 2-cde | 3-fgh |
Array AD. Before:
1.5 | 1.3 | 1.1 | 0.8 | 0.5 | 0.2 |
Array AD. After:
0.5 | 0.2 | 1.5 | 1.3 | 1.1 | 0.8 |
Array AI. Before:
5 | 3 | 2 | -1 | -8 | 4 | 3 | 0 | 12 |
Array AI. After:
3 | 0 | 12 | 5 | 3 | 2 | -1 | -8 | 4 |

 

7. Приклад класу, що отримує два параметризовані типи

Клас може отримувати декілька типів в якості параметрів. Нижче наведено приклад класу, що отримує два типи T1, T2 в якості параметрів. У класі реалізовано метод Print(), який виводить значення масивів елементів типів T1 та T2.

// Клас, який отримує в якості параметрів два типи T1, T2
public class GenericClass<T1, T2> {

  // Метод, що виводить одиночні елементи типів T1, T2
  public void Print(T1 item1, T2 item2, String comment) {
    System.out.println(comment);
    System.out.println("item1 = " + item1);
    System.out.println("item2 = " + item2);
    System.out.println("The type of item1 = " + item1.getClass());
    System.out.println("The type of item2 = " + item2.getClass());
  }

  // Метод, що виводить масиви елементів узагальнених типів T1, T2
  public void Print(T1[] A1, T2[] A2, String comment) {
    System.out.println(comment);

    System.out.print("A1 = { ");
    for (int i=0; i<A1.length; i++)
      System.out.print(A1[i] + " ");
    System.out.println(" }");

    System.out.print("A2 = { ");
    for (int i=0; i<A2.length; i++)
      System.out.print(A2[i] + " ");
    System.out.println(" }");

    System.out.println("The type of array A1 = " + A1.getClass());
    System.out.println("The type of array A2 = " + A2.getClass());
  }

  public static void main(String[] args) {

    // Демонстрація використання класу GenericClass<T1, T2>, який отримує два параметри типів

    // 1. Використання методу Print() для одиночних елементів різних типів
    // 1.1. Оголосити змінні різних типів
    Integer item1 = 23;
    Double item2 = 2.85;

    // 1.2. Оголосити екземпляр obj1
    GenericClass<Integer, Double> obj1 = new GenericClass<Integer, Double>();

    // 1.3. Викликати метод Print() для одиночних елементів
    obj1.Print(item1, item2, "The values of item1 and item2:");

    // 2. Використання методу Print() для масивів
    // 2.1. Оголосити масиви
    Float[] AF = { 3.8f, 2.5f, -1.4f, 2.2f, 0.001f };
    Boolean[] AB = { true, true, false, true, false };

    // 2.2. Оголосити екземпляр obj2
    GenericClass<Float, Boolean> obj2 = new GenericClass<Float, Boolean>();

    // 2.3. Викликати метод Print() для масивів
    obj2.Print(AF, AB, "The values of arrays AF, AB");
  }
}

Результат виконання програми

The values of item1 and item2:
item1 = 23
item2 = 2.85
The type of item1 = class java.lang.Integer
The type of item2 = class java.lang.Double
The values of arrays AF, AB
A1 = { 3.8 2.5 -1.4 2.2 0.001 }
A2 = { true true false true false }
The type of array A1 = class [Ljava.lang.Float;
The type of array A2 = class [Ljava.lang.Boolean;

 


Зв’язані теми