Thursday 9 March 2017

Creating a Multilevel Hierarchy, When Constructors Are Called & Method Overriding - Java Tutorials

Creating a Multilevel Hierarchy

Up to this point, we have been using simple class hierarchies that consist of only a superclass and a subclass. However, you can build hierarchies that contain as many layers of inheritance as you like. As mentioned, it is perfectly acceptable to use a subclass as a superclass of another. For example, given three classes called A, B, and C, C can be a subclass of B, which is a subclass of A. When this type of situation occurs, each subclass inherits all of the traits found in all of its superclasses. In this case, C inherits all aspects of B and A. To see how a multilevel hierarchy can be useful, consider the following program. In it, the subclass BoxWeight is used as a superclass to create the subclass called Shipment. Shipment inherits all of the traits of BoxWeight and Box, and adds a field called cost, which holds the cost of shipping such a parcel.

  // Extend BoxWeight to include shipping costs.

  // Start with Box.
  class Box {
    private double width;
    private double height;
    private double depth;

    // construct clone of an object
    Box(Box ob) { // pass object to constructor
      width = ob.width;
      height = ob.height;
      depth = ob.depth;
    }

    // constructor used when all dimensions specified
    Box(double w, double h, double d) {
      width = w;
      height = h;
      depth = d;
    }

    // constructor used when no dimensions specified
    Box() {
      width = -1; // use -1 to indicate
      height = -1; // an uninitialized
      depth = -1; // box
    }

    // constructor used when cube is created
    Box(double len) {
      width = height = depth = len;
    }

    // compute and return volume
    double volume() {
      return width * height * depth;
    }
  }

  // Add weight.
  class BoxWeight extends Box {
    double weight; // weight of box

    // construct clone of an object
    BoxWeight(BoxWeight ob) { // pass object to constructor
      super(ob);
      weight = ob.weight;
    }

    // constructor when all parameters are specified
    BoxWeight(double w, double h, double d, double m) {
      super(w, h, d); // call superclass constructor
      weight = m;
    }

    // default constructor
    BoxWeight() {
      super();
      weight = -1;
    }

    // constructor used when cube is created
    BoxWeight(double len, double m) {
      super(len);
      weight = m;
    }
  }

  // Add shipping costs
  class Shipment extends BoxWeight {
    double cost;

    // construct clone of an object
    Shipment(Shipment ob) { // pass object to constructor
      super(ob);
      cost = ob.cost;
    }

    // constructor when all parameters are specified
    Shipment(double w, double h, double d,
             double m, double c) {
      super(w, h, d, m); // call superclass constructor
      cost = c;
    }

    // default constructor
    Shipment() {
      super();
      cost = -1;
    }

    // constructor used when cube is created
    Shipment(double len, double m, double c) {
      super(len, m);
      cost = c;
    }
  }

  class DemoShipment {
    public static void main(String args[]) {
      Shipment shipment1 =
                new Shipment(10, 20, 15, 10, 3.41);
      Shipment shipment2 =
                new Shipment(2, 3, 4, 0.76, 1.28);

      double vol;

      vol = shipment1.volume();
      System.out.println("Volume of shipment1 is " + vol);
      System.out.println("Weight of shipment1 is "
                          + shipment1.weight);
      System.out.println("Shipping cost: $" + shipment1.cost);
      System.out.println();

      vol = shipment2.volume();
      System.out.println("Volume of shipment2 is " + vol);
      System.out.println("Weight of shipment2 is "
                          + shipment2.weight);
      System.out.println("Shipping cost: $" + shipment2.cost);
    }
  }

The output of this program is shown here:

  Volume of shipment1 is 3000.0
  Weight of shipment1 is 10.0
  Shipping cost: $3.41

  Volume of shipment2 is 24.0
  Weight of shipment2 is 0.76
  Shipping cost: $1.28

Because of inheritance, Shipment can make use of the previously defined classes of Box and BoxWeight, adding only the extra information it needs for its own, specific application. This is part of the value of inheritance; it allows the reuse of code.

This example illustrates one other important point: super( ) always refers to the constructor in the closest superclass. The super( ) in Shipment calls the constructor in BoxWeight. The super( ) in BoxWeight calls the constructor in Box. In a class hierarchy, if a superclass constructor requires parameters, then all subclasses must pass those parameters “up the line.” This is true whether or not a subclass needs parameters of its own.

In the preceding program, the entire class hierarchy, including Box, BoxWeight, and Shipment, is shown all in one file. This is for your convenience only. In Java, all three classes could have been placed into their own files and compiled separately. In fact, using separate files is the norm, not the exception, in creating class hierarchies.




When Constructors Are Called

When a class hierarchy is created, in what order are the constructors for the classes that make up the hierarchy called? For example, given a subclass called B and a superclass called A, is A’s constructor called before B’s, or vice versa? The answer is that in a class hierarchy, constructors are called in order of derivation, from superclass to subclass. Further, since super( ) must be the first statement executed in a subclass’ constructor, this order is the same whether or not super( ) is used. If super( ) is not used, then the default or parameterless constructor of each superclass will be executed. The following program illustrates when constructors are executed:

  // Demonstrate when constructors are called.
  
  // Create a super class.
  class A {
    A() {
      System.out.println("Inside A's constructor.");
    }
  }

  // Create a subclass by extending class A.
  class B extends A {
    B() {
      System.out.println("Inside B's constructor.");
    }
  }

  // Create another subclass by extending B.
  class C extends B {
    C() {
      System.out.println("Inside C's constructor.");
    }
  }

  class CallingCons {
    public static void main(String args[]) {
      C c = new C();
    }
  }

The output from this program is shown here:

  Inside A’s constructor
  Inside B’s constructor
  Inside C’s constructor

As you can see, the constructors are called in order of derivation.

If you think about it, it makes sense that constructors are executed in order of derivation. Because a superclass has no knowledge of any subclass, any initialization it needs to perform is separate from and possibly prerequisite to any initialization performed by the subclass. Therefore, it must be executed first.




Method Overriding

In a class hierarchy, when a method in a subclass has the same name and type signature as a method in its superclass, then the method in the subclass is said to override the method in the superclass. When an overridden method is called from within a subclass, it will always refer to the version of that method defined by the subclass. The version of the method defined by the superclass will be hidden. Consider the following:

  // Method overriding.
  class A {
    int i, j;
    A(int a, int b) {
      i = a;
      j = b;
    }

    // display i and j
    void show() {
      System.out.println("i and j: " + i + " " + j);
    }
  }

  class B extends A {
    int k;

    B(int a, int b, int c) {
      super(a, b);
      k = c;
    }

    // display k – this overrides show() in A
    void show() {
      System.out.println("k: " + k);
    }
  }

  class Override {
    public static void main(String args[]) {
      B subOb = new B(1, 2, 3);

      subOb.show(); // this calls show() in B
    }
  }

The output produced by this program is shown here:

  k: 3

When show( ) is invoked on an object of type B, the version of show( ) defined within B is used. That is, the version of show( ) inside B overrides the version declared in A.

If you wish to access the superclass version of an overridden function, you can do so by using super. For example, in this version of B, the superclass version of show( ) is  invoked within the subclass’ version. This allows all instance variables to be displayed.

  class B extends A {
    int k;

    B(int a, int b, int c) {
      super(a, b);
      k = c;
    }

    void show() {
      super.show(); // this calls A's show()
      System.out.println("k: " + k);
    }
  }

If you substitute this version of A into the previous program, you will see the following output:

  i and j: 1 2
  k: 3

Here, super.show( ) calls the superclass version of show( ).

Method overriding occurs only when the names and the type signatures of the two methods are identical. If they are not, then the two methods are simply overloaded. For example, consider this modified version of the preceding example:

  // Methods with differing type signatures are overloaded – not
  // overridden.
  class A {
    int i, j;

    A(int a, int b) {
      i = a;
      j = b;
    }

    // display i and j
    void show() {
      System.out.println("i and j: " + i + " " + j);
    }
  }

  // Create a subclass by extending class A.
  class B extends A {
    int k;

      B(int a, int b, int c) {
        super(a, b);
        k = c;
    }

    // overload show()
    void show(String msg) {
      System.out.println(msg + k);
    }
  }

  class Override {
    public static void main(String args[]) {
      B subOb = new B(1, 2, 3);

      subOb.show("This is k: "); // this calls show() in B
      subOb.show(); // this calls show() in A
    }
  }

The output produced by this program is shown here:

  This is k: 3
  i and j: 1 2

The version of show( ) in B takes a string parameter. This makes its type signature different from the one in A, which takes no parameters. Therefore, no overriding (or name hiding) takes place.

No comments:

Post a Comment