Java. Узагальнення. Обмежені типи. Метасимвольні аргументи. Приклади

Узагальнення. Обмежені типи. Метасимвольні аргументи. Приклади


Зміст


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




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 утворюють ієрархію успадкування. В подальшому, в межах цієї ієрархії метасимвольний аргумент буде обмежуватись.

Безпосереднє використання ієрархії PointLineTriangle реалізується в узагальненому класі 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)

 


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