Узагальнення. Обмежені типи. Метасимвольні аргументи. Приклади
Зміст
- 1. Поняття про обмежені типи. Загальна форма. Приклад
- 2. Приклад класу Point, який працює тільки з числовими типами
- 3. Приклад, що демонструє обмеження на використання типів для інтерфейсів та класів, що їх реалізують
- 4. Метасимвольні аргументи. Поняття
- 5. Приклад класу, що містить методи, які використовують метасимвольний аргумент
- 6. Обмеження на метасимвольні аргументи. Приклад
- Зв’язані теми
Пошук на інших ресурсах:
1. Поняття про обмежені типи. Загальна форма. Приклад
Бувають випадки, коли у класах потрібно обмежити використання деяких типів. Прикладами таких класів можуть бути класи, що містять математичні розрахунки, для яких використання символьних, рядкових чи логічного типів є недопустимим (не має сенсу). У мові Java такі випадки також передбачені.
У найпростішому випадку для того, щоб обмежити використання деяких типів в узагальненому класі (інтерфейсі), що використовує тип Т, застосовується наступна загальна форма:
class ClassName<T extends SuperClassName> { ... }
тут
- ClassName – ім’я узагальненого класу, що використовує тип T;
- SuperClassName – ім’я базового класу (суперкласу), що обмежує використання класу ClassName для деяких типів. Суперклас SuperClassName оголошує (встановлює) верхню межу допустимих типів для типу T.
Якщо в класі ClassName для типу T встановлено обмеження на використання типів доступних в суперкласі SuperClassName, то всі об’єкти типу T в класі можуть викликати методи цього суперкласу. Це дає змогу конвертувати об’єкти типу T в типи суперкласу з допомогою спеціальних методів.
Наприклад. Якщо потрібно щоб клас ClassName обробляв тільки числові типи (Integer, Float, Double), то загальна форма оголошення такого класу наступна
class ClassName<T extends Number> { }
тут Number – ім’я суперкласу, який оголошує верхню межу типів для типу T.
⇑
2. Приклад класу Point, який працює тільки з числовими типами
Для того, щоб клас обмежився використанням тільки числових типів, потрібно успадкувати тип класу від типу Number. Тип Number є супертипом для усіх числових типів (Integer, Float, Long, Double).
У прикладі реалізовано клас Point, який оперує числовими типами. Параметр типу T в класі Point успадковує тип Number.
// Клас обмежується числовими типами class Point<T extends Number> { private T x, y; public Point(T x, T y) { this.x = x; this.y = y; } // Відстань від точки до початку координат public double Length() { return Math.sqrt(x.doubleValue()*x.doubleValue()+y.doubleValue()*y.doubleValue()); } } public class GenInterface { public static void main(String[] args) { // 1. Для типу Double Point<Double> pd = new Point<Double>(2.2, 3.5); double length = pd.Length(); System.out.println("length = " + length); // 2. Для типу Integer Point<Integer> pi = new Point<Integer>(3, 4); length = pi.Length(); System.out.println("length = " + length); } }
Результат виконання програми
length = 4.1340053217188775 length = 5.0
⇑
3. Приклад, що демонструє обмеження на використання типів для інтерфейсів та класів, що їх реалізують
// Оголосити інтерфейс, який працює з узагальненим типом T interface Figure<T> { public double Area(); // обчислити площу фігури } // Клас, що реалізує інтерфейс Figure та обмежується використанням числових типів class Rectangle<T extends Number> implements Figure<T> { private T x1, y1, x2, y2; // координати прямокутника // Конструктор public Rectangle(T x1, T y1, T x2, T y2) { this.x1=x1; this.y1=y1; this.x2=x2; this.y2=y2; } // Повернути площу прямокутника public double Area() { return Math.abs(x1.doubleValue()-x2.doubleValue()) * Math.abs(y1.doubleValue()-y2.doubleValue()); } } // Клас, що реалізує інтерфейс Figure class Circle<T extends Number> implements Figure<T> { private T radius; private T x, y; // Конструктор public Circle(T x, T y, T radius) { this.x = x; this.y = y; this.radius = radius; } // Повернути площу public double Area() { return Math.PI * Math.pow(radius.doubleValue(), 2); } } public class GenInterface { public static void main(String[] args) { // Тест класу Rectangle<Integer> Rectangle<Integer> rInt = new Rectangle<Integer>(1, 2, 3, 5); double areaRect = rInt.Area(); System.out.println("areaRect = " + areaRect); // Тест класу Rectangle<Double> Rectangle<Double> rDouble = new Rectangle<Double>(1.5, 2.3, 0.8, 0.7); areaRect = rDouble.Area(); System.out.println("areaRect = " + areaRect); // Тест класу Circle<Float> Circle<Float> cFloat = new Circle<Float>(1.5f, 2.2f, 3.0f); double areaCircle = cFloat.Area(); System.out.println("areaCircle = " + areaCircle); // Так робити не можна!!! // Circle<Boolean> cBoolean = new Circle<Boolean>(true, false, false); - помилка, тип Boolean не числовий } }
⇑
4. Метасимвольні аргументи. Поняття
Метасимвольний аргумент – це позначення невідомого типу, який використовується в узагальнених класах. Метасимвольний аргумент позначається символом ? (знак питання). Метасимвольні аргументи використовуються, коли потрібно оперувати різними типами.
Загальна форма оголошення метасимвольного аргументу наступна:
ClassName<?> instance
тут
- ClassName – ім’я узагальненого класу;
- ? – позначення метасимвольного аргументу, що означає будь-який тип;
- instance – змінна-екземпляр узагальненого класу, що може оперувати будь-яким типом.
Якщо деякий метод отримує параметром метасимвольний аргумент типу, то загальна форма оголошення такого методу в класі наступна
return_type MethodName(ClassName<?> obj) { ... }
тут
- return_type – тип, який повертає метод;
- MethodName – ім’я методу;
- ClassName – ім’я класу, який може оперувати різними типами. Ці типи можуть бути визначені (обмежені) в деякій ієрархії класів;
- obj – конкретний екземпляр (об’єкт).
⇑
5. Приклад класу, що містить методи, які використовують метасимвольний аргумент
Нехай задано узагальнений клас Complex<T>, що реалізує комплексне число. У класі в демонстраційних цілях реалізовано два методи, що отримують параметром метасимвольний аргумент.
Клас містить наступні складові:
- внутрішні змінні real, imag – відповідно дійсна та уявна частина комплексного числа;
- конструктор;
- метод Equals(), який порівнює значення комплексного числа поточного екземпляру зі значенням комплексного числа, що є вхідним параметром;
- метод PrintComplex(), який виводить значення комплексного числа Complex у зручній формі.
Метод Equals() отримує вхідним параметром рядок виду
Complex<?> obj
Тут символ <?> означає, що використовується метасимвольний аргумент. Це, в свою чергу, означає що метод може отримувати екземпляри різних типів класу Complex, наприклад Complex<Integer>, Complex<Double> та інші.
Статичний метод PrintComplex() також отримує вхідним параметром метасимвольний аргумент
Complex<?> cm
Так само, як і метод Equals(), метод PrintComplex() може бути викликаний для різнотипних екземплярів класу Complex.
Якщо в методах Equals() або PrintComplex() спробувати замінити метасимвольний аргумент ? на тип T, наприклад так
public boolean Equals(Complex<T> obj) { ... } ... public static void PrintComplex(String comment, Complex<T> cm) { ... } ...
то компілятор видасть помилку на етапі компіляції. У цьому випадку спрацює типова безпека. Ця помилка буде означати, що можна викликати методи Equals() та PrintComplex() тільки для екземплярів однакових типів. Тобто, при виклику методу Equals() екземпляр класу Complex<Double> не можна порівнювати з екземпляром Complex<Integer>. Цей екземпляр допускається порівнювати тільки з іншим екземпляром типу Complex<Double>. Те саме стосується екземплярів класів інших типів.
// Узагальнений клас, що описує комплексне число class Complex<T extends Number> { private T real; // дійсна частина комплексного числа private T imag; // уявна частина комплексного числа // Конструктор public Complex(T _real, T _imag) { real = _real; imag = _imag; } // Метод порівняння поточного комплексного числа з іншим комплексним числом. // Тут використовується метасимвольний аргумент ? public boolean Equals(Complex<?> obj) { if ((real.doubleValue()==obj.real.doubleValue()) && (imag.doubleValue()==obj.imag.doubleValue())) return true; return false; } // Статичний метод, що роздруковує будь-яке комплексне число Complex у форматі z = a+b*j, // яке отримується вхідним параметром. // Тут використовується обмежений метасимвольний аргумент. // Метод отримує параметром рядок коментарію comment. public static void PrintComplex(String comment, Complex<?> cm) { System.out.print(comment); System.out.print(" z = " + cm.real); if (cm.imag.doubleValue() < 0) System.out.println(cm.imag + "*j"); else System.out.println("+" + cm.imag + "*j"); } } public class TestComplex { public static void main(String[] args) { // 1. Використання класу Complex для різних типів аргументів Complex<Double> cDouble = new Complex<Double>(2.2, 3.5); Complex<Integer> cInt = new Complex<Integer>(2, -5); // Роздрукувати екземпляри cDouble, cInt Complex.PrintComplex("cDouble:", cDouble); Complex.PrintComplex("cInt:", cInt); // Викликати метод Equals() для екземплярів cInt та cDouble if (cDouble.Equals(cInt)) System.out.println("cDouble == cInt"); else System.out.println("cDouble != cInt"); // 2. Використання класу Complex для різних типів аргументів, // значення чисел в екземплярах рівне Complex<Long> cLong = new Complex<Long>(2l, -5l); Complex.PrintComplex("cLong:", cLong); // Порівняти екземпляр cLong з cInt if (cLong.Equals(cInt)) System.out.println("cLong == cInt"); else System.out.println("cLong != cInt"); // Порівняти cInt з cLong if (cInt.Equals(cLong)) System.out.println("cInt == cLong"); else System.out.println("cInt != cLong"); } }
⇑
6. Обмеження на метасимвольні аргументи. Приклад
Так само як і на узагальнені типи, на метасимвольні аргументи можуть накладатись обмеження. Щоб на метасимвольні аргументи накладались обмеження класи повинні утворювати ієрархію успадкування. В межах цієї ієрархії можуть накладатись обмеження. Застосування метасимвольних аргументів в ієрархіях класів є особливо важливим.
Якщо метод отримує обмежений метасимвольний аргумент в якості параметру, то загальна форма такого методу наступна
return_type MethodName(ClassName<? extends SuperClass> obj) {
...
}
тут
- return_type – тип, що повертає метод;
- MethodName – ім’я методу;
- ClassName – ім’я класу, що оперує типами в межах деякої ієрархії в якій базовим класом є клас SuperClass;
- SuperClass – базовий клас (суперклас) в ієрархії класів, який обмежує використання метасимвольного аргументу;
- obj – екземпляр (об’єкт) узагальненого класу ClassName.
У нижченаведеному прикладі оголошується 5 класів. Перші три класи Point, Line, Triangle утворюють ієрархію успадкування. В подальшому, в межах цієї ієрархії метасимвольний аргумент буде обмежуватись.
Безпосереднє використання ієрархії Point–Line–Triangle реалізується в узагальненому класі Figure. Цей клас отримує параметром деякий тип T, який обмежуюється класом Point, що розміщений в вершині ієрархії.
У тестувальному класі TestClass реалізовано статичні методи PrintValuesPoint(), PrintValuesLine(), PrintValuesTriangle() які отримують парметром метасимвольний аргумент ?. Цей метасимвольний аргумент в методах обмежується відповідно класами Point, Line, Triangle. Наприклад, у методі PrintValuesLine() обмеження типу до класу Line виглядає так
Figure<? extends Line> fg
З врахуванням обмеження, оголошення методу наступне
static void PrintValuesLine(Figure<? extends Line> fg) { ... }
У методі main() класу TestClass() демонструється оголошення фігур та виклик відповідних статичних методів.
// Обмеження на використання метасимвольних аргументів // Клас, що описує точку class Point { public double x, y; // Конструктор public Point(double x, double y) { this.x = x; this.y = y; } } // Клас Line - розширює можливості класу Point до лінії class Line extends Point { public double x2, y2; // Конструктор Line(double x, double y, double x2, double y2) { super(x, y); // викликати конструктор суперкласу Point this.x2 = x2; this.y2 = y2; } } // Клас Triangle - розширюється до трикутника class Triangle extends Line { public double x3, y3; // Конструктор класу Triangle(double x, double y, double x2, double y2, double x3, double y3) { super(x, y, x2, y2); this.x3 = x3; this.y3 = y3; } } // Клас, що реалізує деяку фігуру class Figure<T extends Point> { T figure; // Конструктор класу public Figure(T _figure) { figure = _figure; } } // Клас, в якому проводиться тестування public class TestClass { // Статичний метод, що отримує метасимвольний аргумент, // який обмежений класом Point static void PrintValuesPoint(Figure<? extends Point> fg) { System.out.print("Point: "); System.out.print("( " + fg.figure.x + "; "); System.out.println(fg.figure.y + ")"); } // Статичний метод, в якому метасимвольний аргумент обмежений типом Line static void PrintValuesLine(Figure<? extends Line> fg) { System.out.print("Line: "); System.out.print("( " + fg.figure.x + "; "); System.out.print(fg.figure.y + ") - ( "); System.out.println(fg.figure.x2 + "; " + fg.figure.y2 + ")"); } // Статичний метод, в якому метасимвольний аргумент обмежений типом Triangle static void PrintValuesTriangle(Figure<? extends Triangle> fg) { System.out.print("Triangle: "); System.out.print("( " + fg.figure.x + "; " + fg.figure.y + ") - "); System.out.print("( " + fg.figure.x2 + "; " + fg.figure.y2 + ") - "); System.out.println("( " + fg.figure.x3 + "; " + fg.figure.y3 + ")"); } public static void main(String[] args) { // 1. Оголосити фігуру Point Point pt = new Point(2.0, 8.0); Figure<Point> fPt = new Figure<Point>(pt); // Викликати статичний метод PrintValuesPoint() TestClass.PrintValuesPoint(fPt); // 2. Оголосити фігуру Line Line ln = new Line(1.4, 2.2, 3.3, 8.1); Figure<Line> fLn = new Figure<Line>(ln); // Вивести значення координат лінії TestClass.PrintValuesLine(fLn); // 3. Оголосити фігуру Triangle Triangle tr = new Triangle(2.3, 2.8, 3.1, 4.2, 8.5, -3.4); Figure<Triangle> fTr = new Figure<Triangle>(tr); TestClass.PrintValuesTriangle(fTr); } }
Результат виконання програми
Point: ( 2.0; 8.0) Line: ( 1.4; 2.2) - ( 3.3; 8.1) Triangle: ( 2.3; 2.8) - ( 3.1; 4.2) - ( 8.5; -3.4)
⇑
Зв’язані теми
- Узагальнення. Параметризовані типи. Узагальнені класи, інтерфейси, методи
- Забезпечення типової безпеки з допомогою узагальнень. Узагальнені інтерфейси. Приклади
⇑