Java. Обобщения (шаблоны). Параметризованные типы. Обобщенные классы
Содержание
- 1. Что такое «обобщение» (шаблон) в языке Java? Что такое параметризованный тип? Особенности применения обобщений
- 2. Преимущества применения обобщений
- 3. Общая форма объявления обобщенного класса и объявление ссылки на обобщенный класс
- 4. Какие типы запрещается использовать в обобщенных классах
- 5. Пример обобщенного класса, который реализует метод поиска элемента в двумерной матрице
- 6. Пример реализации метода, который осуществляет циклический сдвиг в массиве обобщенного типа Type
- 7. Пример класса, который получает два параметризованных типа
- Связанные темы
Поиск на других ресурсах:
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, ..., Type>(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;
⇑
Связанные темы
- Обеспечение типовой безопасности с помощью обобщений. Обобщенные интерфейсы. Примеры
- Обобщения. Ограниченные типы. Метасимвольные аргументы. Примеры
⇑