Chapter 7: Inheritance and Polymorphism

7.1: Keyword super for inheritance

If you want to call a base-class method in the overriding method in derived class, in c++ you must use the base-class name plus :: plus the constructor name. The class name is used to avoid recursion.

In Java, because a class can only inherit from one class, there is no need to mention the class name. So keyword super is introduced to literally represent the base-class name. When you want to access a member of the base class you write "super . member" , when you want to call the base-class constructor you write "super( )":

   
   public class Base {
      private int member1;
   
      public Base(int m)
      {  member1 = m; }
   
      public void print()
      {  System.out.println("Base-class member = " + member1); }
   }
   
   public class Derived extends Base {
      private int member2;
   
      public Derived(int m1, int m2)
      {
         super(m1);    // calling base-class constructor
         member2 = m2;
      }
   
      public void print()
      {
         super. print();    // accessing base-class member
         System.out.println("Derived-class member = " + member2);
      }
   }
   
   public class Test {
      public static void main(String [] s)
      {
         Derived d1 = new Derived(123, 1234);
         d1.print();
       }
    }

7.2: No Shrinking Inheritance

The accessibility of a method through inheritance can only be enhanced but not reduced. Following is a list of all the accessibility identifiers in the order of accessibility:

   
   public -> friendly -> protected -> private

Compared with C++, Java has less flexibility here. In C++ you have three kinds of inheritance: public, protected and private inheritance. If you choose protected or private inheritance, it is shrinking inheritance. In Java you have only one kind of inheritance - expending inheritance. Of course you can still achieve the same goal with private composition.

7.3: Overloaded Method Is Not Suppressed

In C++, when a method in base class is overloaded in the derived class, it is treated as overriding, and the base-class method is suppressed. In Java a base-class method can be overloaded in the derived class and both methods are accessible to clients (if they are public):

   
   class Base {
      int print(int i)
      {  System.out.println("Base-class version!");
         return i;  }
   }
   
   class Derived extends Base {
      char print(char c)
      {  System.out.println("Derived-class version!");
         return c;  }
   }
   
   public class Test {
      public static void main(String [] s)
      {
         Derived d1 = new Derived();
         char c = 'a';
         int i = 1234;
         d1.print(i);
         d1.print(c);
      }
   }

Output will be:

   
   Base-class version!
   Derived-class version!

7.4: Composition vs. Inheritance

When you want to use the features of a class, but do not want to use its interface, you can use composition. You can make it a private object so that clients can not directly access it.

Despite of the strong emphasis on inheritance in OO programming, when you start a design you should generally prefer composition first and use inheritance only when you want to use its interface, e.g., to use polymorphism. Composition tends to be more flexible, one reason is that you can determine the type of the composition object at run-time (you can assign any type which inherits from the composition type to that composition handle), while inheritance requires the type to be known at compiling time.

7.5: Keyword final

* final Objects and Arguments

When used on objects, final has the same meaning as const in C++. You can make a primitive object or a non-primitive object's handle final, but you can not make a non-primitive object itself final. So in Java there is no constant non-primitive object. In other words, there is no language support to prevent an object from being modified.

When a primitive object is made final, you can not modify its value. When an object handle is made final, you can not point it to another object.

When an argument is made final, this argument can not be modified in the method.

Just like C++, normal final handles must be initialized when declared, except as a class member. You can leave the initialization in the constructor. Such a field is called blank final.

* final Methods

When a final method is inherited, it can NOT be overridden in its derived class, so that its behavior can NOT be altered during inheritance. So it has different meaning as const method in C++, in which no data member is allowed to be modified.

Private methods are by default final.

* final Classes

A final class can not be inherited.

7.6: Abstract Methods and classes

Declaring a method abstract is equal to declaring it pure in C++. In C++, if you declare a method abstract, the class is automatically abstract - actually it is the only way in C++ to make a class abstract. In Java, if you declare a method abstract, you must meantime declare the class abstract, otherwise compiler will complain. You can also directly declare a class abstract without having any abstract method.

Just like C++, an abstract class forces the derived class to implement all its abstract methods. Otherwise you have to declare the derived class abstract.

7.7: Polymorphism

Polymorphism provides another dimension of separation of interface from implementation - to decouple what from how. It deals with decoupling in terms of types.

Connecting a method call to a method is called binding. When binding is performed before the program is run (by the compiler or linker), it is called early binding or static binding (in C++). If the binding occurs at run-time, it is called late binding or dynamic binding or run-time binding.

As said before, all methods are by default dynamic binding. To ensure early binding you have to use keyword final.

7.8: Interface

keyword interface is used in place of class to declare an abstract class in which no implementation is allowed. To inherit from it, keyword implements is used in place of extends:

   
   interface Test0 {
      int a = (int)(Math.random()*10.0);  // primitive member, static and final
      void fun1();
      void fun2();
   }
   
   class Test1 implements Test0 {
      void fun1()
      {...}
      void fun2()
      {...}
   }

Interface can also contain primitive data members, they are automatically static and final. They can not be blank finals and must be initialized.

From client's point of view, there is no difference whether the type he uses is an interface, an abstract class or a concrete class. Test1 which implements an interface is treated as a normal class.

The methods in an interface are by default public (but interface itself is not). So in its implementing class all relevant implementing methods must have keyword public - because Java do not allow shrinking inheritance.

7.9: Multiple Inheritance

An interface can extend from multiple interfaces using keyword extends. A class can implement multiple interfaces, but meantime it can (but not necessarily) extend only one class. A handle of any of the base interfaces or the class can be used to receive an object of the derived class.

   
   interface Inter1 {
      void print1();
   }
   interface Inter2 {
      void print2();
   }
   interface Inter3 {
      void print3();
   }
   interface Inter4 extends Inter1, Inter2 {
      void print4();
   }
   class Separate {
      void print0() {}
   }
   
   class Derived extends Separate implements Inter3, Inter4 {
      private void print(String s)
      {  System.out.println(s); }
   
      public void print1()
      {  print("Print1"); }
   
      public void print2()
      {  print("Print2"); }
   
      public void print3()
      {  print("Print3"); }
   
      public void print4()
      {  print("Print4"); }
   
      public void print0()
      {  print("Print0"); }
   }
   
   public class Test {
      static void print1(Inter1 obj) // Receiving Derived object with handle of Inter1
      {  obj.print1(); }
   
      static void print2(Inter2 obj) // Receiving Derived object with handle of Inter2
      {  obj.print2(); }
   
      static void print3(Inter3 obj) // Receiving Derived object with handle of Inter3
      {  obj.print3(); }
   
      static void print4(Inter4 obj) // Receiving Derived object with handle of Inter4
      {  obj.print4(); }
   
      static void print0(Separate obj) // Receiving Derived object with handle of Separate
      {  obj.print0(); }
   
      public static void main(String [] s)
      {
         Derived d1 = new Derived();
         print1(d1);
         print2(d1);
         print3(d1);
         print4(d1);
         print0(d1);
      }
   }

In this example, class "Derived" directly or indirectly inherits from interface "Inter1" ~ "Inter4" and class "Separate". Therefore, an object of class "Derived" can be received with a handle of each of these interfaces and class.

* Interface vs. Abstract Class

Because of the extra benefit interface has, if you do not have to have implementation, you should always try to make it an interface first.

* Grouping Constants

Because primitive members in an interface are automatically static and final, you can use interface to define a group of enums:

   
   public interface Months {
      int JANUARY = 1, FEBRUARY = 2, ... DECEMBER = 12;
   }

Then you can use Months . January instead of 1.

7.10: Base-class Default Constructor

If there is no base-class constructor at all, compiler will generate one implicitly and let you pass. But if a base class DOES have constructors but does NOT have a default constructor, and the derived class constructor doesn't explicit call any existing base-class constructor, compiler will prompt error, because it doesn't know which base-class constructor the derived class can call to initialize its base class.

   
   class Base {
      private int i;
   
      Base(int i0)
      {  i = i0; }
   }
   
   class Derived extends Base
   { }

If the derived class actually calls one of the existing base-class destructors, compile will pass:

   
   class Derived extends Base
   {
      Derived(int i)
      {  super(i); }
   }

7.11: Overriding finalize( ) in Inheritance

If you override the finalize function in the derived class, you should call the base-class finalize in the overriding function, so that the base-class portion also get finalized.

When finalizing an object, the derived-class finalize is called first, then the derived-class finalize.

7.12: Sequence of Constructing an Object

The sequence of initialization of an object of a class which derives from a base class is

1) The storage allocated for that object (including all base-class objects) are initialized to 0;

2) Base-class constructor is called;

3) Member initializers are called for member objects in the order of declaration;

4) Derived-class constructor is called.

7.13: Avoid Calling a Method from the Constructor

Suppose a constructor calls a normal method (default to be virtual) of the same class, and later another class inherits from this class and overrides that method. When an object of that derived class is created, the base-class constructor will be called first. When the base-class constructor call the base-class method, the derived-class method instead of the base-class one will be called - because the object being created is a derived-class object. This derived-class method may need to access the data members of the derived-class, which had not be initialized yet. This will cause unexpected error.

Such an subtle problem may only happen at the transient moment when the derived-class object is not yet created, and can only be triggered by the base-class constructor.

Therefore, as a general guideline, always try to be simple in a constructor and try not to call methods of its class. If you have to, make it a final methods, which will not be overridden, or a private method, which is by default final.

7.14: Down-Casting

When a method expects an argument as a base-class object but you pass a derived-class object, if the base class contains all the desired interface, polymorphism will do the job and down-casting is not necessary.

However, if in you want to use a method which is not contained in the base class, you have to down-cast the base-class handle to a lower level so that you can access the extra methods of the lower-level class.

In Java you just put the target-type name in "( )" before the object to be cast, in C++ you use dynamic_cast.

In C++ you have less chance to be forced to use down-cast, because the base class is normally designed to provide interfaces for all needed methods of the derived class.

In Java, because lots of methods take objects of type Object as arguments (especially when using collections, which have only Object handles), if you want to use any function beyond Object, you have to use down-casting.

If a wrong-type object is passed, exception ClassCastException will be thrown, so that you can correctly handle the problem. It is not good as compile-time checking, but it is still robust.

Make your own free website on Tripod.com