Showing posts with label Java Method Overriding and Dynamic Binding. Show all posts
Showing posts with label Java Method Overriding and Dynamic Binding. Show all posts

Sunday, 31 May 2015

Java Method Overriding and Dynamic Binding

Method Overriding

In a class hierarchy, when a instance method in a subclass has the same signature as an instance method (non-private) in its super class, then the method in the sub-class is said to override the method in the super-class. When an overridden method is called from within a sub-class, it will always refer to the version of that method defined by the sub-class. The version of the method defined by the super-class will be hidden.

Please not that the concept of overriding is applicable only for instance methods. Static methods cannot be overridden. It is not possible to override even an instance method if its visibility is private.

While overriding a method, we cannot decrease the visibility. The visibility of the method in the child class should be same or higher. The visibility of a method overriding a public method can be public only. The visibility of a method overriding a protected method can be protected or public. Similarly the visibility of a method overriding a method with the package visibility can be package, protected or public.

Dynamic Method Binding (Dynamic Method Dispatch or Run-time Binding) 

Method overriding forms the basis for one of Java’s most powerful concepts. Dynamic method dispatch is the mechanism by which a call to an overridden instance method is resolved at run time, rather than compile time.

Dynamic method dispatch is important because this is how Java implements run-time polymorphism.
A super-class reference variable can refer to a sub-class object. Java uses this fact to resolve calls to overridden methods at run time. When an overridden method is called through a super-class reference, Java determines which version of that method to execute based upon the type of the object being referred to at the time the call occurs.

Thus, this determination is made at run time, when different types of objects are referred to; different versions of an overridden method will be called.

In other words, it is the type of the object being referred to (not the type of reference) that determines which version of an overridden method will be executed.

Why Overridden Methods?

Overridden methods allow Java to support run-time polymorphism. Polymorphism is essential for OOP for one reason: It allows a general class to specify methods that will be common to all of its derivatives, while allowing sub classes to define the specific implementation of some or all of these methods.

By combining inheritance with overridden methods, a super-class can define the general form of method that will be used by all of its sub classes.

The ability of existing code libraries to call methods on instances of new classes without recompiling while maintaining a clean abstract interface is a profoundly powerful tool.

Example: The following example illustrates the concept of method overriding and dynamic binding.

1 class A

2 { int i,j;

3 A(int a, int b)

4 {

5 i = a; j = b;

6 }

7 void show()

8 {

9 System.out.println(i);

10 System.out.println(j);

11 }

12 }

1 class B extends A

2 { int k = 5;

3 B(int a, int b, int c)

4 {

5 super(a,b);

6 k = c;

7 }

8 void show()

9 { System.out.println(k);

10 }

11 }

1 class OverridingDemo1

2 { static public void main(String args[])

3 { B b = new B(1,2,3); b.show();

4     A a = b;

5     a.show();

6   }

7 }

Output:
3
3

Example: The following example illustrates that the overridden method of the super class can be invoked through super keyword.

1 class A

2 { int i,j;

3 A(int a, int b)

4 { i = a; j = b;

5 }

6 void show()

7 { System.out.println(i);

8 System.out.println(j);

9 }

10 }

1 class B extends A

2 { int k = 5;

3 B(int a, int b, int c)

4 {

5 super(a,b);

6 k = c;

7 }

8 void show()

9 { super.show(); //this call’s the A’s show()

10 System.out.println(k);

11 }

12 }

1 class OverridingDemo2

2 { static public void main(String args[])

3 { B b = new B(1,2,3); b.show();

4 A a = b;

5 a.show();

6 }

7 }

Output:

1
2
3
1
2
3

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

Method overriding occurs only when the signature of the two methods are identical. If not, then the two methods are simply overloaded.

Example: The following example illustrates that the show() method in the sub-class does not override the show() method in the super-class as their signatures are different. The show() method is just overloaded.

1 class A

2 { int i,j;

3 A(int a, int b)

4 { i = a; j = b;

5 }

6 void show()

7 { System.out.println(i);

8 System.out.println(j);

9 }

10 }

1 class B extends A

2 { int k = 5;

3 B(int a, int b, int c)

4 { super(a,b);

5 k = c;

6 }

7 void show(String msg)

8 { System.out.println(msg + k);

9 }

10 }

1 class OverridingDemo3

2 { static public void main(String args[])

3   { B b = new B(1,2,3); b.show("Value of k: "); b.show();

4     }

5 }

Output:

Value of k:

3
1
2

Example:

The stack class defined earlier can hold only ten elements in the stack. We want a dynamic stack, which can hold any number of elements in the stack. We can rewrite the push() method of the existing class Stack or ExtStack. But the better alternative is to define another class by extending the Stack or ExtStack class and overriding the push() method. This will not affect any existing code, which is using Stack or ExtStack class. The push() method in the derived class will have exactly same signature as the push() method in the base-class but the body of the method will be different so as to implement the dynamic stack.

1 class DynamicStack extends ExtStack 

2 { public void push(int x)

3 { int b[];

4 if(top == a.length-1)

5 { b = new int[2*a.length];

6 for(int i = 0; i < a.length; i++)

7 b[i] = a[i];

8 a = b; //a now points to a new array of double size

9 }

10 top = top + 1;

11 a[top] = x;

12 }

13 }

The following program makes use of the overridden method push(). It is now possible to push any number of elements till the JVM has free memory.

1 class StackDemo2

2 { public static void main(String args[])

3 { DynamicStack s = new DynamicStack();

4 for(int i = 0; i < 10; i++)

5 { s.push(100+i);

6 }

7 for(int i = 0; i < 10; i++)

8 { s.push(100+i);

9 }

10 for(int i = 0; i < 10; i++)

11 { s.push(100+i);

12 }

13 s.display();

14 }

15 }

Example: It is possible to access the overridden method through base class reference. This can be achieved by creating object of sub-class and assigning its reference to a reference variable of base-class.

The following example demonstrates this concept. But a base-class reference can be used to access only features defined in the base-class even if it points to an object of the sub-class. If a reference of base-class actually points to an object of sub-class, you can typecast it to get sub-class reference and then use it to access the features, which are specific to the sub-class.

Following example demonstrates these concepts.

1 class StackDemo3

2 { public static void main(String args[])

3 { Stack s = new DynamicStack();

4 for(int i = 0; i < 10; i++)

5 { s.push(100+i);

6 }

7 for(int i = 0; i < 10; i++)

8 { s.push(100+i);

9 }

10 //boolean b = s.isStackFull();

11 //s.display();

12 DynamicStack ds = (DynamicStack) s;

13 boolean b = ds.isStackFull();

14 ds.display();

15 }

16 }

We have created an object of type DynamicStack and assigned the reference in a reference variable of type Stack, which is valid as it is up-casting and takes place implicitly as discussed above. But we can access only the features of the base-class through the reference of type Stack.

Hence the call to methods s.isStackFull() and s.display() are commented as they are not valid. But observe that call s.push() actually calls push() method of the sub-class as it is over-ridden. This is why we are able to insert more than 10 elements in the stack.

Finally, in this program reference of type Stack (s) is typecasted back to reference of type DynamicStack (ds). This is down-casting so explicit typecast is needed. Typecasting will be valid only if the referred object is of type DynamicStack. We can now call ds.isStackFull() and ds.display() methods.

Example: The following example illustrates that it is possible to write generic code using inheritance, method over-riding and dynamic binding features.

1 class Shape

2 { double puCost = 100;

3 double area()

4 { System.out.println("Area is not defined");

5 return 0;

6 }

7 double calCost()

8 { return puCost * area();

9 }

10 }

1 class Triangle extends Shape

2 { double s1,s2,s3;

3 Triangle(double a, double b, double c)

4 {  s1 = a; s2 = b; s3 = c;

5 }

6 double area()

7 { double s = (s1+s2+s3)/2;

8 return Math.sqrt(s*(s-s1)*(s-s2)*(s-s3));

9 }

10 }

1 class Rectangle extends Shape

2 { double s1,s2;

3 Rectangle(double a, double b)

4 {  s1 = a; s2 = b;

5 }

6 double area()

7 { return s1 * s2;

8 }

9 }

1 class Square extends Shape

2 { double s;

3 Square(double a)

4 {  s = a;

5 }

6 double area()

7 { return s * s;

8 }

9 }

1 class ShapeTest

2 { static public void main(String args[])

3 { Shape s = new Shape();

4   Shape s1 = new Triangle(3,4,5);

5  Shape s2 = new Rectangle(3,4);

6 Shape s3 = new Square(3);

7 System.out.println(s.area());

8   System.out.println(s1.area());

9   System.out.println(s2.area());

10   System.out.println(s3.area());

11   System.out.println(s.calCost());

12   System.out.println(s1.calCost());

13   System.out.println(s2.calCost());

14   System.out.println(s3.calCost());

15 }

16 }

Output:

Area is not defined
0.0
6.0
12.0
9.0
Area is not defined
0.0
600.0
1200.0
900.0

Note: If an object is derived from Shape, then its area can be obtained by calling area() method.
The interface to this operation is the same no matter what type of Shape is being used.