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)

 


Связанные темы