Java. Generalizations. Limited types. Metacharacter arguments. Examples

Generalizations. Limited types. Metacharacter arguments. Examples


Contents


Search other websites:




1. The concept of limited types. General form. Example

There are times when classes need to restrict the use of certain types. Examples of such classes can be classes that contain mathematical calculations, for which the use of character, string, or logical types is unacceptable (does not make sense). The Java language also provides such cases.

In the simplest case, to restrict the use of certain types in a generic class (interface) that uses type T, the following general form is applied:

class ClassName<T extends SuperClassName> {
  ...
}

here

  • ClassName – the name of the generic class that uses the type T;
  • SuperClassName – the name of the base class (superclass) that restricts the use of the ClassName to some types. The superclass SuperClassName declares (sets) an upper bound on the allowed types for type T.

If the class ClassName for type T has restrictions on the use of types available in the superclass SuperClassName, then all objects of type T in the class can call methods of this superclass. This makes it possible to convert objects of type T to superclass types using special methods.

For example. If you want the ClassName class to process only numeric types (Integer, Float, Double), then the general form of declaring such a class is as follows

class ClassName<T extends Number> {
}

here Number – the name of the superclass that declares the upper bound for the type T.

 

2. An example of a Point class that handles only numeric types

In order for the class to restrict itself to using only numeric types, you need to inherit the class type from the Number type. The Number type is a supertype for all numeric types (Integer, Float, Long, Double).

The example implements the Point class, which operates on numeric types. The type parameter T in the Point class inherits the type Number.

// Class is limited to numeric types
class Point<T extends Number> {
  private T x, y;

  public Point(T x, T y) {
    this.x = x;
    this.y = y;
  }

  // The distance from the point to the origin
  public double Length()
  {
    return  Math.sqrt(x.doubleValue()*x.doubleValue()+y.doubleValue()*y.doubleValue());
  }
}

public class GenInterface {
  public static void main(String[] args) {
    // 1. For type Double
    Point<Double> pd = new Point<Double>(2.2, 3.5);
    double length = pd.Length();
    System.out.println("length = " + length);

    // 2. For type Integer
    Point<Integer> pi = new Point<Integer>(3, 4);
    length = pi.Length();
    System.out.println("length = " + length);
  }
}

The result of the program

length = 4.1340053217188775
length = 5.0

 

3. An example that demonstrates the restrictions on the use of types for interfaces and classes that implement it

 

// Declare an interface that works with generic type T
interface Figure<T> {
  public double Area(); // calculate the area of a figure
}

// A class that implements the Figure interface and is limited to using numeric types
class Rectangle<T extends Number> implements Figure<T> {
  private T x1, y1, x2, y2; // coordinates of the rectangle

  // Constructor
  public Rectangle(T x1, T y1, T x2, T y2) {
    this.x1=x1;
    this.y1=y1;
    this.x2=x2;
    this.y2=y2;
  }

  // Return the area of a rectangle
  public double Area() {
    return Math.abs(x1.doubleValue()-x2.doubleValue())
           * Math.abs(y1.doubleValue()-y2.doubleValue());
  }
}

// A class that implements the Figure interface
class Circle<T extends Number> implements Figure<T> {
  private T radius;
  private T x, y;

  // Constructor
  public Circle(T x, T y, T radius) {
    this.x = x;
    this.y = y;
    this.radius = radius;
  }

  // Return the area
  public double Area() {
    return Math.PI * Math.pow(radius.doubleValue(), 2);
  }
}

public class GenInterface {
  public static void main(String[] args) {
    // Rectangle<Integer> class test
    Rectangle<Integer> rInt = new Rectangle<Integer>(1, 2, 3, 5);
    double areaRect = rInt.Area();
    System.out.println("areaRect = " + areaRect);

    // Rectangle<Double> class test
    Rectangle<Double> rDouble = new Rectangle<Double>(1.5, 2.3, 0.8, 0.7);
    areaRect = rDouble.Area();
    System.out.println("areaRect = " + areaRect);

    // Circle<Float> class test
    Circle<Float> cFloat = new Circle<Float>(1.5f, 2.2f, 3.0f);
    double areaCircle = cFloat.Area();
    System.out.println("areaCircle = " + areaCircle);

    // You can't do that !!!
    // Circle<Boolean> cBoolean = new Circle<Boolean>(true, false, false); - error, Boolean type is not numeric
  }
}

 

4. Metacharacter arguments. Concept

A metacharacter argument is an unknown type designator that is used in generic classes. A metacharacter argument is denoted by ? (question mark). Metacharacter arguments are used when you need to operate on different types.

The general form of declaring a metacharacter argument is as follows:

ClassName<?> instance

here

  • ClassName – generic class name;
  • ? – metacharacter argument notation that denotes any type;
  • instance – an instance variable of a generic class that can operate on any type.

If some method receives a metacharacter type argument as a parameter, then the general form of declaring such a method in the class is as follows

return_type MethodName(ClassName<?> obj) {
  ...
}

here

  • return_type – the type that the method returns;
  • MethodName – method name;
  • ClassName – the name of a class that can operate on different types. These types can be defined (limited) in some class hierarchy;
  • obj – specific instance (object).

 

5. An example of a class that contains methods that take a metacharacter argument

Let the generalized class Complex<T> be given, which implements a complex number. In the class, for demonstration purposes, two methods are implemented that receive a metacharacter argument as a parameter.

The class contains the following components:

  • internal variables real, imag are the real and imaginary parts of the complex number, respectively;
  • constructor;
  • the Equals() method, which compares the value of the complex number of the current instance with the value of the complex number, which is the input parameter;
  • the PrintComplex() method, which outputs the value of the Complex number in a convenient form.

The Equals() method receives as an input parameter a string of the form

Complex<?> obj

Here the <?> character indicates that a metacharacter argument is being used. This, in turn, means that the method can receive instances of different types of the Complex class, for example Complex<Integer>, Complex<Double> and others.

The static method PrintComplex() also receives a metacharacter argument as an input parameter

Complex<?> cm

Just like the Equals() method, the PrintComplex() method can be called on heterogeneous instances of the Complex class.

If in the Equals() or PrintComplex() methods try to replace the metacharacter argument ? to type T, for example

public boolean Equals(Complex<T> obj) {
  ...
}

...

public static void PrintComplex(String comment, Complex<T> cm) {
  ...
}

...

then the compiler will generate an error at compile time. In this case, typical safety will occured. This error means that you can call the Equals() and PrintComplex() methods only for instances of the same type. That is, when calling the Equals() method, an instance of the Complex<Double> class cannot be compared with an instance of Complex<Integer>. This instance can only be compared with another instance of the Complex<Double> type. This also applies to instances of classes of other types.

// A generalized class that describes a complex number
class Complex<T extends Number> {
  private T real; // real part of a complex number
  private T imag; // imaginary part of a complex number

  // Constructor
  public Complex(T _real, T _imag) {
    real = _real;
    imag = _imag;
  }

  // Method for comparing the current complex number with another complex number
  // This uses a metacharacter argument ?
  public boolean Equals(Complex<?> obj) {
    if ((real.doubleValue()==obj.real.doubleValue()) &&
        (imag.doubleValue()==obj.imag.doubleValue()))
      return true;
    return false;
  }

  // A static method that outputs any Complex number in the format z = a+b*j,
  // which is the input parameter.
  // A limited metacharacter argument is used here.
  // The method also receives the comment string as a parameter.
  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. Using the Complex class for different types of arguments
    Complex<Double> cDouble = new Complex<Double>(2.2, 3.5);
    Complex<Integer> cInt = new Complex<Integer>(2, -5);

    // Print the instances cDouble, cInt
    Complex.PrintComplex("cDouble:", cDouble);
    Complex.PrintComplex("cInt:", cInt);

    // Call Equals() method on cInt and cDouble instances
    if (cDouble.Equals(cInt))
      System.out.println("cDouble == cInt");
    else
      System.out.println("cDouble != cInt");

    // 2. Using the Complex class for different types of arguments,
    //    the value of numbers in instances is equal to each other
    Complex<Long> cLong = new Complex<Long>(2l, -5l);
    Complex.PrintComplex("cLong:", cLong);

    // Compare instance cLong with cInt
    if (cLong.Equals(cInt))
      System.out.println("cLong == cInt");
    else
      System.out.println("cLong != cInt");

    // Compare cInt with cLong
    if (cInt.Equals(cLong))
      System.out.println("cInt == cLong");
    else
      System.out.println("cInt != cLong");
  }
}

 

6. Restrictions on metacharacter arguments. Example

As with generic types, restrictions can be imposed on metacharacter arguments. In order to impose restrictions metacharacters arguments, classes should form an inheritance hierarchy. Constraints can be imposed within this hierarchy. The use of metacharacter arguments in class hierarchies is especially important.

If a method receives a bounded metacharacter argument as a parameter, the general form of such a method is

return_type MethodName(ClassName<? extends SuperClass> obj) {
  ...
}

here

  • return_type – the type returned by the method;
  • MethodName – method name;
  • ClassName – the name of a class that operates on types within a certain hierarchy in which the base class is the SuperClass class;
  • SuperClass – the base class (superclass) in the class hierarchy, which restricts the use of the metacharacter argument;
  • obj – an instance (object) of the generic class ClassName.

The following example declares 5 classes. The first three classes Point, Line, Triangle form an inheritance hierarchy. Further, within this hierarchy, the metacharacter argument will be constrained.

Direct use of the PointLineTriangle hierarchy is implemented in the generic Figure class. This class receives as a parameter some type T, which is limited to the Point class located at the top of the hierarchy.

The testing class TestClass implements the static methods PrintValuesPoint(), PrintValuesLine(), PrintValuesTriangle() that receive a metacharacter argument ? as a parameter. This metacharacter argument in methods is limited to the Point, Line, Triangle classes, respectively. For example, in the PrintValuesLine() method, the type constraint to the Line class looks like this

Figure<? extends Line> fg

Subject to the limitation, the method declaration is as follows

static void PrintValuesLine(Figure<? extends Line> fg) {
  ...
}

The main() method of TestClass() demonstrates declaring shapes and calling the corresponding static methods.

// Restriction on the use of arguments metacharacters
// The class that describes the point
class Point {
  public double x, y;

  // Constructor
  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }
}

// Line class - extends the capabilities of the Point class to a line
class Line extends Point {
  public double x2, y2;

  // Constructor
  Line(double x, double y, double x2, double y2) {
    super(x, y); // call the superclass constructor Point
    this.x2 = x2;
    this.y2 = y2;
  }
}

// Triangle class - expands to the triangle
class Triangle extends Line {
  public double x3, y3;

  // Class constructor
  Triangle(double x, double y, double x2, double y2, double x3, double y3) {
    super(x, y, x2, y2);
    this.x3 = x3;
    this.y3 = y3;
  }
}

// A class that implements some figure
class Figure<T extends Point> {
  T figure;

  // Class constructor
  public Figure(T _figure) {
    figure = _figure;
  }
}

// Testing class
public class TestClass {
  // A static method that takes a metacharacter argument
  // which is limited to the Point class
  static void PrintValuesPoint(Figure<? extends Point> fg) {
    System.out.print("Point: ");
    System.out.print("( " + fg.figure.x + "; ");
    System.out.println(fg.figure.y + ")");
  }

  // A static method in which the metacharacter argument is limited to 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 + ")");
  }

  // A static method in which the metacharacter argument is limited to type 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. Declare the figure Point
    Point pt = new Point(2.0, 8.0);
    Figure<Point> fPt = new Figure<Point>(pt);

    // Invoke the static method PrintValuesPoint()
    TestClass.PrintValuesPoint(fPt);

    // 2. Declare a figure Line
    Line ln = new Line(1.4, 2.2, 3.3, 8.1);
    Figure<Line> fLn = new Figure<Line>(ln);

    // Input values of line coordinates
    TestClass.PrintValuesLine(fLn);

    // 3. Declare the figure 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);
  }
}

The result of the program

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)

 


Related topics