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.

Java Multilevel Hierarchy example

Example: The following example demonstrates a multi-level hierarchy.

1 class Box

2 { private double width, height, depth; //data hiding

3 double volume()

4 { double vol = width * height * depth;

5 return vol;

6 }

7 Box(double w, double h, double d)

8 { width = w; height = h; depth = d;

9 }

10 Box()

11 { width = -1; height = -1; depth = -1;

12 }

13 Box(double len)

14 { width = height = depth = len;

15 }

16 Box(Box ob)

17 {  width = ob.width; height = ob.height; depth = ob.depth;

18 }

19 }

1 class BoxWeight extends Box

2 { double weight;

3 BoxWeight(BoxWeight ob)

4 { super(ob);

5 weight = ob.weight;

6 }

7 BoxWeight(double w, double h, double d, double m)

8 { super(w,h,d); weight = m;

9 }

10 BoxWeight()

11 { super(); weight = -1;

12 }

13 BoxWeight(double len, double m)

14 { super(len); weight = m;

15 }

16 }

1 class Shipment extends BoxWeight

2 {

3 double cost;

4 Shipment(Shipment ob)

5 { super(ob); cost = ob.cost;

6 }

7 Shipment(double w, double h, double d, double m, double c)

8 { super(w,h,d,m); cost = c;

9 }

10 Shipment()

11 { super(); cost  = -1;

12 }

13 Shipment(double len, double m, double c)

14 { super(len,m); cost = c;

15 }

16 }

1 class ShipmentDemo

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

3    { Shipment s = new Shipment(10,20,15,34.3,1000);

4 double vol = s.volume();

5 System.out.println(vol);

6 System.out.println(s.weight);

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

8 }

9 }

Output:
3000.0
34.3
1000.0

This example illustrates one important point: super() always refers to the constructor in the immediate super class. The super() in shipment calls the constructor in BoxWeight. The super() in BoxWeight calls the constructor in Box.

In a class hierarchy if a super class 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.

1. Order in which Constructors are called

When a class hierarchy is created, in what order are the constructors for the classes that make up the hierarchy called? The answer is that in a class hierarchy, constructors are called in order of derivation, from super class to subclass.

Further, since super() must be the first statement executed in a subclass’s constructor; this order is the same whether or not super() is used. If super() is not used, then the default or parameter less constructor of each super class will be executed.

Example: The following example illustrates the order in which constructors are called when child class object is created in case of a multi-level hierarchy.

1 class A

2 { A()

3 { System.out.println("A's Constructor");

4 }

5 }

1 class B extends A

2 { B()

3 { System.out.println("B's Constructor");

4 }

5 }

1 class C extends B

2 { C()

3 { System.out.println("C's Constructor");

4 }

5 }

1 class ConstOrderDemo

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

3    { C c = new C();

4 }

5 }

Output:

A's Constructor
B's Constructor
C's Constructor

As you can see constructors are called in order of derivation. It makes sense that constructors are executed in order of derivation. Because a super class has no knowledge of any sub class, any initialization it needs to perform is separate and possibly pre-requisite to any initialization performed by the sub class. Therefore it must be executed first.


Saturday, 30 May 2015

Java Member Hiding

If a sub-class member has the same name (and same signature in case of methods) as that of a super-class member then it hides the super-class member.

Although both the members might be available in the sub-class but using member name we can only access sub-class member as it hides the member of the same name in the super-class.

There is a way of accessing a hidden member, which is discussed in the next section. 


Java keyword super

In the previous example, class BoxWeight derived from Box was not implemented as efficiently or as robustfully as it could have been.

For example, the constructor for BoxWeight explicitly initializes the width, height, and depth fields of Box(). Not only does this duplicate code of its super class, which is in-efficient, but it implies that a subclass must be granted access to these members.

However, there will be times when you will want to create a super class that keeps the details of its implementation to itself (that is, it keeps its data members private). In this case, there would be no way for a subclass to directly access or initialize these variables on its own.

Since encapsulation is a primary attribute of OOP, it is not surprising that Java provides a as solution to this problem.

Whenever a subclass needs to refer to its immediate super class, it can do so by use of keyword super.

The super has two general uses:


  • Calling super class’s constructor
  • Accessing a member of the super-class that has been hidden by a member of a sub-class.


1. Using super to call super class constructors

A subclass can call a constructor method defined by its super class by use of the following form of super:

super(parameter-list);

Here, parameter-list specifies any parameters needed by the constructor in the super class. In fact super() must always be the first statement executed inside a subclass’s constructor.

To see how super() is used, consider the improved version of the BoxWeight() class in the following example.

Example:

1 class Box

2 { private double width, height, depth; //data hiding

3 double volume()

4 { double vol = width * height * depth;

5 return vol;

6 }

7 Box(double w, double h, double d)

8 { width = w; height = h; depth = d;

9 }

10 Box()

11 { width = -1; height = -1; depth = -1;

12 }

13 Box(double len)

14 { width = height = depth = len;

15 }

16 Box(Box ob)

17 { width = ob.width; height = ob.height; depth = ob.depth;

18 }

19 }

1 class BoxWeight extends Box

2 { double weight;

3 BoxWeight(double w, double h, double d, double m)

4 { super(w,h,d);

5 weight = m;

6 }

7 }

1 class BoxWeightDemo1

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

3 { BoxWeight bw1 = new BoxWeight(10,20,15,34.3);

4 double vol;

5 vol = bw1.volume();

6 System.out.println(vol);

7 System.out.println(bw1.weight);

8 Box b1 = bw1;

9 vol = b1.volume();

10 System.out.println(vol);

11 //System.out.println(b1.weight); //will not compile

12 //BoxWeight bw2 = b1; //will not compile

13 BoxWeight bw2 = (BoxWeight) b1;

14 vol = bw2.volume();

15 System.out.println(vol);

16 System.out.println(bw2.weight);

17 Box b2 = new Box(5);

18 BoxWeight bw3 = (BoxWeight) b2;

19 System.out.println(bw3.weight);

20

21 }

22 }

Output:

3000.0
34.3
3000.0
3000.0
34.3

Exception in thread "main" java.lang.ClassCastException: Box

        at BoxWeightDemo1.main(BoxWeightDemo1.java:18)

Here, BoxWeight() constructor calls super() with the parameters w, h, d. This causes the Box(double w, double h, double d)  constructor to be called, which initializes width, height and depth using these values. BoxWeight no longer initialize these values itself. It only needs to initialize the value unique to it i.e. weight. This leaves Box free to make these values private if desired.

In the preceding example, super() was called with three arguments. Since constructors can be overloaded, super() can use any form defined by the super class. The constructor executed will be one that matches the arguments.

The line number 18 in the BoxWeightDemo1.java class will result in run-time exception as we are trying to down-cast a super class reference which is actually pointing to a super-class object and hence is not a valid down-cast.

Example: The following example illustrates how super can be used to invoke overloaded constructors of the super class.

1 class Box

2 { private double width, height, depth; //data hiding

3 double volume()

4 { double vol = width * height * depth;

5 return vol;

6 }

7 Box(double w, double h, double d)

8 { width = w; height = h; depth = d;

9 }

10 Box()

11 { width = -1; height = -1; depth = -1;

12 }

13 Box(double len)

14 { width = height = depth = len;

15 }

16 Box(Box ob)

17 { width=ob.width; height=ob.height; depth=ob.depth;

18 }

19 }

1 class BoxWeight extends Box

2 { double weight;

3 BoxWeight(BoxWeight ob)

4 { super(ob);

5 weight = ob.weight;

6 }

7 BoxWeight(double w, double h, double d, double m)

8 { super(w,h,d);

9 weight = m;

10 }

11 BoxWeight()

12 { super(); weight = -1;

13 }

14 BoxWeight(double len, double m)

15 { super(len); weight = m;

16 }

17 }

1 class BoxWeightDemo2

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

3  { BoxWeight bw1 = new BoxWeight(10,20,15,34.3);

4 BoxWeight bw2 = new BoxWeight(2,3,4,14.2);

5 BoxWeight bw3 = new BoxWeight();

6 BoxWeight bw4 = new BoxWeight(3,5);

7 BoxWeight bw5 = new BoxWeight(bw1);

8 double vol;

9 vol = bw1.volume();

10 System.out.println(vol);

11 System.out.println(bw1.weight);

12 vol = bw2.volume();

13 System.out.println(vol);

14 System.out.println(bw2.weight);

15 vol = bw3.volume();

16 System.out.println(vol);

17 System.out.println(bw3.weight);

18 vol = bw4.volume();

19 System.out.println(vol);

20 System.out.println(bw4.weight);

21 vol = bw5.volume();

22 System.out.println(vol);

23 System.out.println(bw5.weight);

24 }

25 }

Output:

3000.0
34.3
24.0
14.2
-1.0
-1.0
27.0
5.0
3000.0
34.3

Notice that Box(Box ob) is called using super() by passing a parameter of type BoxWeight, not of Box.

As mentioned earlier, super class variable can be used to reference any object derived from that class. Thus, we are able to pass a BoxWeight object to the Box constructor. Of course, Box only has knowledge of its own members.

When a subclass calls super(), it is calling the constructor of its immediate super class. Thus, super() always refers to the super class immediately above the calling class. This is true even in a multi-level hierarchy.

Also, super() must always be the first statement executed inside a sub-class constructor. If a sub-class constructor does not call the super-class’s constructor explicitly then the compiler implicitly puts the call to super-class’s no-argument constructor.

2. Using super to access hidden member of the super class.

The keyword super can be used to access the hidden members of the super as follows:

super.member;

Here, member can be either a method or a data member.

Example: The following example illustrates how to access hidden members of the super- class from sub-class

1 class A

2 { protected int x = 10;

3 public void m1()

4 { System.out.println("m1() of A");

5 }

6 }

1 class B extends A

2 { int x = 5;

3 public void m1()

4 { System.out.println("m1() of B");

5 }

6 public void m2()

7 { System.out.println(super.x);

8 System.out.println(x);

9 m1();

10 super.m1();

11 }

12 public static void main(String args[])

13 { B b = new B(); b.m2();

14 }

15 }

Output:

10
5
m1() of B
m1() of A

Example: The following example illustrates use of super in a multi-level hierarchy.

1 class A

2 { int x = 1;

3 }

1 class B extends A

2 { int y = 2;

3 }

1 class C extends B

2 { int x = 3;

3 void show()

4 { System.out.println(x);

5 System.out.println(y);

6 System.out.println(super.x);

7 }

8 }

1 class Super

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

3 { C c = new C();

4 c.show();

5   }

6 }

Output:

3
2
1

Example: The following example also illustrates use of super in a multi-level hierarchy where

all the classes in the hierarchy have the members with the same name.

1 class A

2 { int x = 1;

3 }

1 class B extends A

2 { int y = 2;

3 int x = 4;

4 int getSuperX()

5 {

6 return super.x;

7 }

8 }

1 class C extends B

2 { int x = 3;

3 void show()

4 { System.out.println(x);

5 System.out.println(y);

6 System.out.println(getSuperX());

7 System.out.println(super.x);

8 }

9 }

1 class Super2

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

3 { C c = new C();

4 c.show();

5  }

6 }

Output:

3
2
1
4

Java Down-Casting and Up-Casting

A reference to a sub-class object can be stored into a reference type variable of base-class/super-class without any explicit conversion, this is called up-casting. This is like widening conversion used in case of primitive types and takes place implicitly.

A reference to a base-class/super-class object can not be stored into a reference type variable of sub-class without explicit conversion, this is called down-casting. This is like narrowing conversion used in case of primitive types and needs explicit type-casting. The down-casting is valid only if the super class reference points to the sub-class object before down-casting.

Super class variable can refer to a sub-class object

Example: The following example illustrates that a super-class variable can refer to a sub-class object.

Reference variable of a super class can be assigned a reference to any subclass derived from that super class. It is important to understand that it is the type of the reference variable not the type of the object that it refers to that determine what can be accessed.

That is, when a reference to a subclass object is assigned to a super class reference variable, you will have access only to those parts of the object defined by the super class. This is why Box reference cannot access weight even when it refers to a BoxWeight object. If you think about it, this makes sense, because the super class has no knowledge of what a subclass adds to it. This is why the last line of following code is commented out. It is not possible for a Box reference to access the weight field, because it does not define one.

1 class Box

2 { double width, height, depth;

3 double volume()

4 { double vol = width * height * depth;

5 return vol;

6 }

7 Box(double w, double h, double d)

8 { width = w;

9 height = h;

10 depth = d;

11 }

12 Box()

13 { width = -1;

14 height = -1;

15 depth = -1;

16 }

17 Box(double len)

18 { width = height = depth = len;

19 }

20 Box(Box ob)

21 { width = ob.width;

22 height = ob.height;

23 depth = ob.depth;

24 }

25 }

1 class BoxWeight extends Box

2 {

3 double weight;

4 BoxWeight(double w, double h, double d, double m)

5 { width = w;

6 height = h;

7 depth = d;

8 weight = m;

9 }

10 }

1 class BoxWeightDemo

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

3   { BoxWeight bw1 = new BoxWeight(10,20,15,34.3);

4    BoxWeight bw2 = new BoxWeight(2,3,4,51);

5 double vol;

6 vol = bw1.volume();

7 System.out.println(vol);

8 System.out.println(bw1.weight);

9 vol = bw2.volume();

10 System.out.println(vol);

11 System.out.println(bw2.weight);

12 Box b1 = new Box();

13 vol = b1.volume();

14 System.out.println(vol);

15 b1 = bw1;

16 vol = b1.volume();

17 System.out.println(vol);

18 //System.out.println(b1.weight); will not compile

19 }

20 }

Output:

3000.0
34.3
24.0
51.0
-1.0
3000.0

Here BoxWeight inherits all of the characteristics of Box and adds the weight component. A major advantage of inheritance is that once you have created a super class that defines the
attributes common to a set of objects, it can be used to create any number of more specific
subclasses. Each subclass can precisely tailor its own classification.

Remember once you have created a super class that defines the general aspects of an object, that super class can be inherited to form specialized classes. Each subclass simply adds its own, unique attributes.

Java Object Class

Even if you write a simple class (i.e. it is not extending any base-class), it implicitly extends built-in Object class. So the Object class is at the root of all the hierarchies in Java. Thus the features of this class will be available in all the classes.

Example: The following example illustrates the basic concepts of inheritance.

class A

{ int i,j; // or protected int i,j; or public int i,j;

void showij()

{ System.out.println(i); System.out.println(j);

}

}

class B extends A

{ int k;

void showk()

{ System.out.println(k);

}

void sum()

{ System.out.println(i+j+k);

}

}

class InheritanceDemo

{ public static void main(String args[])

{ A a = new A();

B b = new B();

a.i = 10; a.j = 20;

a.showij();

b.i = 7; b.j = 8; b.k = 9;

b.showij(); b.showk();

a.showij();

b.sum();

}

}

Output:

10
20
7
8
9
10
20
24

Example: The following example illustrates that although private members are not inherited but
they can be accessed through methods.

1 class A

2 { private int i,j;

3 void showij()

4 { System.out.println(i); System.out.println(j);

5 }

6 void setI(int x)

7 { i = x;

8 }

9 void setJ(int x)

10 { j = x;

11 }

12 int getI()

13 { return i;

14 }

15 int getJ()

16 { return j;

17 }

18 }

19 class B extends A

20 { int k;

21 void showk()

22 { System.out.println(k);

23 }

24 void sum()

25 { System.out.println(getI()+getJ()+k);

26 }

27 }

28 class InheritanceDemo1

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

30 { A a = new A();

31 B b = new B();

32 a.setI(10); a.setJ(20);

33 a.showij();

34 b.setI(7); b.setJ(8); b.k = 9;

35 b.showij(); b.showk();

36 a.showij();

37 b.sum();

38 }

39 }

Output:

10
20
7
8
9
10
20
24

Example: The following example defines class Stack with two operations push() and pop(). We
will then extend this class to provide some more features (like adding operations display(), isStackEmpty() and isStackFull().

1 class Stack

2 {

3 int top = -1;

4 int a[] = new int[10];

5 void push(int x)

6 {

7 if(top == 9)

8 { System.out.println("Stack Full");

9 return;

10 }

11 top = top + 1;

12 a[top] = x;

13 }

14 int pop()

15 {

16 if(top ==  -1)

17 { System.out.println("Stack Empty");

18 return -1; //assuming that –1 is not a valid data

19 }

20 int x = a[top];

21 top = top - 1;

22 return x;

23 }

24 }

The following class makes use of the Stack class defined above. In the main method, we have created an instance of the class Stack, pushed 10 elements on the stack and then finally we are removing the elements from the stack using pop() and displaying them. Accepting input from the user to decide what operation is to be performed can further extend the program.

1 class StackDemo

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

3 { Stack s = new Stack();

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

5 { s.push(100+i);

6 }

7 int x;

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

9 {

10 x = s.pop();

11 System.out.println(x);

12 }

13 }

14 }

The following class adds new features to the class Stack by extending it. The methods push() and pop() and instance variables top and a[] are available in the extended class because of inheritance.

1 class ExtStack extends Stack

2 { void display()

3 { for(int i = top; i >= 0; i--)

4 { System.out.println(a[i]);

5 }

6 }

7 boolean isStackEmpty()

8 { if(top == -1)

9 return(true);

10 else

11 return(false);

12 }

13 boolean isStackFull()

14 { if(top == 9)

15 return(true);

16 else

17 return(false);

18 }

19 }

The following class makes use of the ExtStack class, which is derived by extending the class Stack. You can see that it uses the features of the base-class as well as derived class.

1 class StackDemo1

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

3 { ExtStack s = new ExtStack();

4 int i=0;

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

6 { s.push(100+i);

7 }

8 s.push(i); //will lead to stack overflow

9 if(s.isStackFull())

10 System.out.println("Stack is full");

11 s.display(); //Stack will be displayed without deleting

12 int x; //elements are popped() and  displayed

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

14 { x = s.pop();  System.out.println(x);

15 }

16 s.pop(); //will lead to stack underflow

17 if(s.isStackEmpty())

18 System.out.println("Stack is empty");

19 }

20 }

Java Extending a Class

To inherit a class, you simply incorporate the definition of one class into another by using the extends keyword.

Java supports only Single-Inheritance in the case of classes to avoid ambiguity and complexity although it supports Multiple-Inheritance in the case of interfaces.

The general form for deriving a sub-class from an existing class is as follows:

class <sub-class> extends <base-class>
{

<class-definition>

}

The derived class will have all the features of base-class in addition to the new features defined in the extended class. Java does not support multiple-inheritance so sub-class can extend only one base-class. It is possible that two different classes can extend the same base-class. A sub-class can also be further extended and the hierarchy can go to any depth. Thus by extending a base-class we can form hierarchy of classes all derived from the same base class.

Java Inheritance

Inheritance is a mechanism to inherit the properties of a class in another class. In the terminology of java, a class that is inherited is called a super-class or base-class or parent class. The class that does the inheriting is called a sub-class or child-class or derived class.

Inheritance allows us to extend an existing class (base class) by adding additional features to it as discussed earlier. It encourages the code reusability, which helps in reducing the code size although it may lead to complexity in some cases.

If a built-in class provides most of the needed functionality except one or two features then we
have two alternatives: (i) rewriting the class, and (ii) extending the class.  In most of the cases, second alternative will be a better choice, as we have to write only small piece of code. At the same time we won’t have to spend much time in debugging and testing as the base class is already tested and is in use for a long period of time.

Moreover it is possible that we do not have access to the source code of an existing class as someone else has developed it and what we have is only the class file. So the only way to add new feature is by extending the class to derive a new class with added features. We would not like to modify the existing class even if the source code is available as it might be in use in many other classes and changes to it may affect the other classes using it.

Java Method Returning Boolean Value

The Java supports primitive data type boolean, which can be used as return type.

Example: The following program uses a method that returns boolean value true or false depending on whether a given positive integer number greater than 2 is prime or not.

1 class CheckPrime

2 { static boolean checkPrime(int number)

3 { int root = (int) Math.sqrt(number);

4 for(int i  = 2; i <= root; i++)

5 { if( number % i = = 0)

6 return false; 

7 }

8 return true;

9 } 

10 public static void main(String a[])

11 { int n = Integer.parseInt(a[0]);

12 boolean prime = checkPrime(n);

13 if(prime)

14 System.out.println("The number " + n + " is prime");

15 else

16 System.out.println("The number " + n + " is not prime");

17 }

18 }

13. Examples

Example: The following example shows the static implementation (using array) of stack.

1 class Stack

2 { private int top = -1;

3 private int a[] = new int[10];

4 void push(int x)

5 { if(top == 9)

6 { System.out.println("Stack Full");

7 return;

8 }

9 top = top + 1;

10 a[top] = x;

11 }

12 int pop()

13 { if(top ==  -1)

14 { System.out.println("Stack Empty");

15 return -1; //assuming that -1 is not a valid data

16 }

17 int x = a[top]; top = top - 1;

18 return x;

19 }

20 }

class StackDemo

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

2 { Stack s1 = new Stack();

3 Stack s2 = new Stack();

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

5 { s1.push(i);

6 }

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

8 { s2.push(i);

9 }

10 int x;

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

12 { x = s1.pop();

13 System.out.print(x + " ");

14 }

15 System.out.println();

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

17 { x = s2.pop();

18 System.out.print(x + " ");

19 }

20 }

Output:

9 8 7 6 5 4 3 2 1 0

19 18 17 16 15 14 13 12 11 10

Example: The following example demonstrates that only one copy per class is created in case of static members. Thus the implementation is not correct if data member top and a[] are declared to be static as in that case all the objects of Stack class will share the same memory.

1 class Stack

2 { static int top = -1;

3 static int a[] = new int[10];

4 static void push(int x)

5 { if(top == 9)

6 { System.out.println("Stack Full"); 

7 return;

8 }

9 top = top + 1;

10 a[top] = x;

11 }

12 static int pop()

13 { if(top ==  -1)

14 { System.out.println("Stack Empty");

15 return -1; //assuming that -1 is not a valid data

16 }

17 int x = a[top];

18 top = top - 1;

19 return x;

20 }

21 }

1 class StackDemo

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

3 { Stack s1 = new Stack();

4 Stack s2 = new Stack();

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

6 { s1.push(i);

7 }

8 for(int i = 10; i < 20; i++)

9 { s2.push(i);

10 }

11 int x;

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

13 { x = s1.pop();

14 System.out.print(x + " ");

15 }

16 System.out.println();

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

18 { x = s2.pop();

19 System.out.print(x + " ");

20 }

21 }

22 }

Output:

Stack Full

Stack Full

Stack Full

Stack Full

Stack Full

Stack Full

Stack Full

Stack Full

Stack Full

Stack Full

9 8 7 6 5 4 3 2 1 0

Stack Empty

-1 Stack Empty

-1 Stack Empty

-1 Stack Empty

-1 Stack Empty

-1 Stack Empty

-1 Stack Empty

-1 Stack Empty

-1 Stack Empty

-1 Stack Empty

-1

Example: The following example shows the dynamic implementation (using linked list) of stack.

1 class Node

2 { Node nextNode;

3 int x;

4 }

1 class Stack

2 { Node top;

3 void push(int item)

4 { Node temp;

5 if(top == null)

6 { top = new Node();

7 top.x = item; top.nextNode = null;

8 }

9 else

10 { temp = new Node();

11 temp.x = item;

12 temp.nextNode = top;

13 top = temp;

14 }

15 }

16 int pop()

17 { if(top == null)

18 { System.out.println("Stack is empty");

19 return(-1);

20 }

21 else

22 { int x = top.x; top = top.nextNode;

23 return x;

24 }

25 }

26 } 

1 class StackTest

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

3 { Stack st = new Stack();

4 st.pop();

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

6 st.push(i); 

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

8 System.out.println(st.pop()); 

9 }

10 }

Output:

Stack is empty

4

3

2

1

0

Stack is empty

-1

Java Recursive Methods

This feature is not specific to object-oriented languages. The concept is exactly same as in case of a procedural language. Using this feature a method can call itself.

Example: The following programs make use of the recursive method fact to calculate the factorial of a given positive integer.

1 class Factorial

2 { long fact (int n)

3 { if(n == 1)

4 return 1;

5 else

6 return n*fact(n-1);

7 }

8 public static void main(String args[])

9 { Factorial f=new Factorial();

10 System.out.println("Factorial of 4 = "+f.fact(4));

11 }

12 }

1 class Factorial

2 { static long fact (int n)

3 { if(n == 1)

4 return 1;

5 else

6 return n*fact(n-1);

7 }

8 public static void main(String args[])

9 { System.out.println("Factorial of 4 = "+fact(4));

10 }

11 }

Java Argument Passing Mechanism (call by value)

When a primitive type is passed to a method, it is done by use of call-by-value approach. In case of objects what is actually passed is an object reference. As a point of interest, an object reference is also passed by using call-by-value approach. However, since the value being passed refers to an object, the copy of that value will still refer to the same object that its corresponding argument does.

Example: The following example illustrates that Java uses call-by-value when passing primitive data types.

1 class Args1

2 { void swap(int a, int b)

3 { int t= a;

4 a = b; b = t;

5 }

6 public static void main(String args[])

7 { int x = 5, y = 7;

8 System.out.println("x = " + x + " y = " + y);

9 Args1 obj = new Args1(); obj.swap(x,y);

10 System.out.println("x = " + x + " y = " + y);

11 }

12 }

Output:

x = 5 y = 7

x = 5 y = 7

Example: The following example also illustrates that Java uses call-by-value when passing primitive data types.

1 class Args2

2 { static void swap(int a, int b)

3 { int t= a;

4 a = b; b = t;

5 }

6 public static void main(String args[])

7 { int x = 5, y = 7;

8 System.out.println("x = " + x + " y = " + y);

9 //Args2.swap(x,y);

10 swap(x,y);

11 System.out.println("x = " + x + " y = " + y);

12 }

13 }

Output:

x = 5 y = 7

x = 5 y = 7

Example: The following example illustrates that Java uses call-by-value even when passing reference types.

1 class Args3

2 { int a,b;

3 Args3(int x, int y)

4 { a = x;

5 b = y;

6 }

7 static void swap(Args3 ob1, Args3 ob2)

8 { Args3 t;

9 t = ob1;

10 ob1 = ob2;

11 ob2 = t;

12 }

13 public static void main(String args[])

14 { Args3  obj1 = new Args3(5,7);

15 Args3  obj2 = new Args3(4,6);

16 System.out.print("obj1.a = " + obj1.a + " obj1.b = " + obj1.b);

17 System.out.println(" obj2.a = " + obj2.a + " obj2.b = " + obj2.b);

18 swap(obj1,obj2);

19 System.out.print("obj1.a = " + obj1.a + " obj1.b = " + obj1.b);

20 System.out.println(" obj2.a = " + obj2.a + " obj2.b = " + obj2.b);

21 }

22 }

Output:

obj1.a = 5 obj1.b = 7 obj2.a = 4 obj2.b = 6

obj1.a = 5 obj1.b = 7 obj2.a = 4 obj2.b = 6

Example: The following example illustrates that although Java uses call-by-value when passing reference types but we can still modify the object referred to by the argument inside the called method, as what is actually passed is a reference.

1 class Args4

2 { int a,b;

3 Args4(int x, int y)

4 { a = x;

5 b = y;

6 }

7 static void swap(Args4 ob1, Args4 ob2)

8 { int x,y;

9 x = ob1.a; y = ob1.b; ob1.a = ob2.a; ob1.b = ob2.b;

10 ob2.a = x; ob2.b = y;

11 }

12 public static void main(String args[])

13 { Args4  obj1 = new Args4(5,7);

14 Args4  obj2 = new Args4(4,6);

15 System.out.print("obj1.a = " + obj1.a + " obj1.b = " + obj1.b);

16 System.out.println(" obj2.a = " + obj2.a + " obj2.b = " + obj2.b);

17 swap(obj1,obj2);

18 System.out.print("obj1.a = " + obj1.a + " obj1.b = " + obj1.b);

19 System.out.println(" obj2.a = " + obj2.a + " obj2.b = " + obj2.b);

20 }

21 }

Output:

obj1.a = 5 obj1.b = 7 obj2.a = 4 obj2.b = 6

obj1.a = 4 obj1.b = 6 obj2.a = 5 obj2.b = 7

Java Constructors

It is very common requirement to initialize an object immediately after creation. We can define instance methods for this purpose but they have to be invoked explicitly. Java has a solution for this requirement. Java allows objects to initialize themselves when they are created using constructors.

The syntax of the constructors is very similar to that of instance methods. They have the same name as the class and do not have any return type. This is because the implicit return type of a class’s constructor is the class itself. Constructors can be overloaded just like methods.

When operator new is used to create an instance/object of a class, JVM allocates memory for the object, then initializes the instance variables to their default initial values, and then calls the appropriate constructor to initialize the instance variables.

We have not explicitly used constructors so far. But every class has a default constructor that does not take any argument and its body does not have any statements. The compiler generates the default constructor automatically. The compilers stops generating default constructor as soon as you add your own constructor.

Note: The name constructor is a bit confusing. It appears as if the purpose of the constructor is to create an object/instance. The object is created and instance variables and static variables are initialized to their default initial values before constructor is called. So the purpose of the constructor is to initialize the instance variables after the object has been created.

1. Default Constructor

Example: The following program does not define any constructor so it uses the default constructor, which does not take any parameters.

1 class Box

2 { float width;

3 float height;

4 float depth;

5 public static void main(String args[])

6 { Box b = new Box();

7 float volume = b.width * b.height * b.depth;

8 System.out.println("Volume = " + volume);

9 }

10 }

The compiler would automatically generate the following default constructor while compiling the above program:

Box()

{

}

The output of the above program will be 0 (Zero) as constructor is not doing any initialization and default initial value of float type instance variables is 0 (Zero).

2. No Argument Constructor

You can replace the default constructor with your own no argument constructor. This will allow you to initialize the instance variables to any value.

Example: The following program defines a no argument constructor, which is called immediately after creation of object.

1 class Box

2 { private double width, height, depth; //data hiding

3 double volume()

4 { double vol = width * height * depth;

5 return vol;

6 }

7 Box() // No argument constructor

8 { System.out.println("Initializing Box");

9 width = 10;

10 height = 10;

11 depth = 10;

12 }

13 }

1 class BoxDemo7

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

3  { Box b1 = new Box();

4 Box b2 = new Box();

5 double volume = b1.volume();

6 System.out.println(volume);

7 volume = b2.volume();

8 System.out.println(volume);

9 }

10 }

The user-defined no argument constructor would be called immediately after the object creation and would initialize the values of the instance variables width, height and depth to 10. Thus the output of the above program would be 1000 instead of 0 (Zero).

3. Parameterized Constructors

The no argument constructor defined in the previous example is not of much use as it always initializes with the same value. A constructor, which can take parameters, will be more useful.

this keyword:

Sometimes a method will need to refer to the object that invoked it. To allow this, Java defines this keyword. It can be used inside any method to refer to the current object. That is, this is always a reference to the object on which the method was invoked. You can use this anywhere a reference to an object of the current class’s type is permitted. To better understand what this refers to, consider the following example.

Instance Variable Hiding

As you know, it is illegal in Java to declare two local variables with the same name inside the same or enclosing scopes. Interesting, you can have local variables, including formal parameter to methods, which overlap with the name of the class’s instance variables. However, when a local variable has the same as an instance variable, the local variable hides the instance variable.

This is why width, height, and depth were not used as the names of the parameters to the Box() constructor inside the box class. If they had been, then width would have referred to the formal parameter, hiding the instance variable width.

While it is usually easier to simply use different names, there is another way around this situation. Because this lets you refer directly to the object, you can use it to resolve any name space collisions that might occur between instance variables and local variables.

Example: The following program defines a parameterized constructor, which is used for initializing the object.

1  class Box

2  { private double width, height, depth; //data hiding

3 double volume()

4 { double vol = width * height * depth;

5 return vol;

6 }

7 Box(double w, double h, double d) //Parameterized constructor

8 { System.out.println("Initializing Box");

9 width = w; height = h; depth = d;

10 }

11 /* Box(double width,  double height, double depth)  //Parameterized constructor

12 { System.out.println("Initializing Box");

13 this.width = width;

14 this.height = height;

15 this.depth = depth;

16 }*/

17  }

1   class BoxDemo8

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

3   { Box b1 = new Box(10,20,15);

4 Box b2 = new Box(3,6,9);

5 double vol;

6 double volume = b1.volume(); System.out.println(volume);

7 volume = b2.volume(); System.out.println(volume);

8 }

9  }

The parameterized constructor allows you to initialize the box with any dimensions.

4. Overloading Constructors

It is possible to overload the constructor just like methods. For example, the constructor of class Box can be overloaded to initialize different types of boxes. If we have a cube then we need to pass just one argument as all the sides of the cube would be of same dimension.

Example: The following program makes use of overloaded constructor to create and initialize the different type of boxes.

1  class Box

2  { private double width, height, depth; //data hiding

3 double volume()

4 { double vol = width * height * depth;

5 return vol;

6 }

7 Box(double w, double h, double d)

8 { width = w; height = h; depth = d;

9 }

10 Box()

11 { width = -1; height = -1; depth = -1;

12 }

13 Box(double len)

14 { width = height = depth = len;

15 }

16 Box(Box ob)  // Copy Constructor

17 { width = ob.width; height = ob.height; depth = ob.depth;

18 }

19  }

1 class BoxDemo9

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

3  { Box b1 = new Box(10,20,15);

4 Box b2 = new Box();

5 Box b3 = new Box(5);

6 Box b4 = new Box(b1);

7 double vol;

8 vol = b1.volume();

9 System.out.println(vol);

10 vol = b2.volume();

11 System.out.println(vol);

12 vol = b3.volume();

13 System.out.println(vol);

14 vol = b4.volume();

15 System.out.println(vol);

16 }

17 }

Java Method Overloading

We can have more than one method with the same name as long as they differ either in number of parameters or type of parameters. This is called method overloading as discussed earlier.

While calling an overloaded method it is possible that type of the actual parameters passed may not match exactly with the formal parameters of any of the overloaded methods. In that case parameters are promoted to next higher type till a match is found. If no match is found even after promoting the parameters then a compilation error occurs.

Example: The following program overloads the method area() to find the area of a circle, square, rectangle and  a triangle.

1  class Area

2  { static final double PI = 3.1415;

3  static double area(double radius) //area of circle

4 { return PI * radius * radius;

5 }

6 static float area(float size) // area of square

7 { return size * size;

8 }

9 static float area(float length, float width) // area of rectangle

10 { return length * width;

11 }

12 static float area(float a, float b, float c) // area of triangle

13 { float s = (a+b+c)/2;

14 float  area = (float) Math.sqrt(s * (s-a) * (s-b) * (s-c));

15 return area;

16 }

17 public static void main(String a[])

18 { double carea = area(3.0); //Circle’s area() method is invoked

19 System.out.println(carea);

20 float sarea = area(3.0f); //Square’s area() method is invoked

21 System.out.println(sarea);

22 float rarea = area(3.0f,4.0f); //Rectangle’s area() method is invoked

23 System.out.println(rarea);

24 float tarea = area(3.0f,4.0f,5.0f); //Triangle’s area() method is invoked

25 System.out.println(tarea);

26 // next higher type matching with the parameter is float

27 float area = area(3);  //Squares’s area() method is invoked

28 System.out.println(area);

29 area = area(3,4);  //Rectangle’s area() method is invoked

30 System.out.println(area);

31 area = area(3,4,5);  //Triangles’s area() method is invoked

32 System.out.println(area);

33 }

34 }

Java Methods

Java methods are equivalent to C++ functions. Objects can communicate with each other using methods. Java’s methods can be classified in the two categories:


  • Instance Methods
  • Static Methods 


Just like static variables and instance variables, there are static methods and instance methods.

1. Instance Methods

The general form of an instance method is:

<return-type> <method-name> (parameter-list)

{

<method-body>

}

Method definition has four parts:


  • Name
  • Return Type
  • List of Parameters
  • Method Body

The return type can be a primitive data type or reference data type. In case, the method returns a 
reference, the return type will be the name of the class to which the object referred to by the returned reference belongs. It is must to declare the return type even if the method does not return any value. In this case, the return type should be declared as void.

The method’s formal parameters/arguments are specified within the parenthesis after the method name. The syntax is same as in C/C++.

The method body may contain any valid Java statement. If the method has a return type, then a value must be returned through return statement.

Invoking/Calling Instance Methods

The dot (.) operator is used to invoke/call the instance methods. The general form for calling the 

instance methods using the dot operator is: 

<object-reference>.<method-name>(paremeter-list)

Where <object-reference> is some reference variable pointing to an object and <method-  name> is the name of an instance method. For example, after creating an object of type Box and storing its reference in the reference variable b1, we can call the instance method  volume() using dot operator as follows: 

Box b1 = new Box();

b1.volume();

Instance methods can access the instance variables as well as static variables.

Example: The following program demonstrates how to call a instance method.

1 class Box 

2 { double width; //instance variables

3 double height;

4 double depth;

5 void volume() //instance method

6 { System.out.println("volume is:" + width * height * depth); 

7 }

8 }

1 class BoxDemo_4

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

3   { Box b = new Box();

4 b.width = 10; 

5 b.height = 20;

6 b.depth = 15;

7 b.volume();

8 }

9 }

In the main() method an instance of the class Box is created, its instance variables are initialized and then the instanced method is called. The instance method volume() calculates and displays the volume of the Box.

While accessing an instance variable inside main() method, we have used object reference. It is required, as we can not access an instance variable directly inside a static method. However, when an instance variable is accessed by a method that is defined in the same class as the instance variable, the instance variable can be referred directly. 

In fact we cannot specify the object reference in the method volume(), in the above example. This is due to the fact that unlike instance variables, there is only one copy of the instance methods. The instance methods use the copy of the instance variables of the object through which they are called. So the same method called through different object references will use different copy of the instance variables and hence the name instance method.

Implicitly an instance variable referred directly in a method uses this reference. For example, in the above program the direct reference to variables width, height and length is equivalent to this.width, this.height and this.length. The reference this inside a method refers to the current object (i.e. the object on which the method is called). The reference this is replaced by the reference of the invoking object at run-time. Hence the same instance method invoked through different objects will use different set of instance variables and hence will normally give different results.

Example: The following program demonstrates that calling non-static/instance method on different objects results in different output. 

1 class Box 

2 { double width; //instance variables

3 double height;

4 double depth;

5 void volume() //instance method

6 { System.out.println("volume is "+width*height*depth); 

7 }

8 }

1 class BoxDemo4

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

3   { Box b1 = new Box();

4 Box b2 = new Box();

5 b1.width = 10; b1.height = 20; b1.depth = 15;

6 b2.width = 3; b2.height = 6;  b2.depth = 9;
7 b1.volume();

8 b2.volume();

9   }

10 }

The first call to method volume() displays the volume of the box b1 correctly as 6000 and the 
second call to method volume() displays the volume of the box b2 as 162.

Method Returning a value

Example: The following program demonstrates use of method, which returns a value i.e. its 
return type, is not void.

1 class Box 

2 { double width;

3 double height;

4 double depth;

5 double volume()

6 { return width * height * depth; 

7 }

8 }

1 class BoxDemo5

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

3   { Box b1 = new Box();

4 Box b2 = new Box();

5 double vol;

6 b1.width = 10;

7 b1.height = 20;

8 b1.depth = 15;

9

10 b2.width = 3;

11 b2.height = 6;

12 b2.depth = 9;

13 vol = b1.volume();

14 System.out.println("Volume is : " + vol);

15 vol = b2.volume();

16 System.out.println("Volume is : " + vol);

17 }

18 }

Passing Arguments to Methods.

In the previous examples using class BoxDemo5, we initialized the instance variables directly to 
keep the things simple. This should not be done as it defeats the purpose of data encapsulation.

Moreover the instance variables can be made private so that they cannot be accessed from other 
classes. In that case it would not be possible to access the instance variables directly. The only 
alternative in that case is to define a method that takes width, height, and length as arguments 
and then initializes the instance variables.

Example: The following program defines two classes: Box and BoxDemo6. The class Box has a 
method that takes arguments. The classes may be defined in same source file or in two different 
source files. But in both the cases you will get two classes after compilation: Box.class and 

BoxDemo6.class. You can execute only class BoxDemo6.class as only this class has main() 
method. It also makes use of class Box for creating an instance/object and calling methods.

1. class Box

2. { private double width, height, depth; //data hiding

3. double volume()

4. { double vol = width * height * depth;

5. return vol;

6. }

7. void setDimensions(double w, double h, double d)

8. { width = w;

9. height = h;

10. depth = d; 

11. }

12. /* Instance Variable Hiding 

13. void setDimensions(double width,double height,double depth)

14. { width = width;

15. height = height;

16. depth = depth; 

17. }

18. */

19. /* Instance Variable Hiding 

20. void setDimensions(double width,double height,double depth)

21. { this.width = width;

22. this.height = height;

23. this.depth = depth; 

24. }

25. */

26. }

1 class BoxDemo6

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

3    { Box b1 = new Box();

4 Box b2 = new Box();

5 b1.setDimensions(10, 20, 30);

6 double volume = b1.volume();

7 System.out.println(volume);

8 b2.setDimensions(5, 10, 15);

9 volume = b2.volume();

10 System.out.println(volume);

11 }

12 }

You cannot access the instance variables of class Box though object reference in the class 
BoxDemo6 as instance variables are declared private. The private instance variables can be 
accessed only in the class in which they are declared.

Nesting of Instance Methods

One instance method can invoke the other instance method of the same class without qualifying 
i.e. by just specifying the method name. For example, in the following program, calCost() 
method, which is an instance method, can call the instance method area() of the same class 
simply by its name.

1 class Circle

2 { static final float  PI = 3.1415f;

3 float puCost; // say cost of painting unit area

4 float radius;

5 float area()

6 { return PI * radius * radius;

7 }

8 float calCost()

9 {

10 float cost = puCost * area();

11 return cost;

12 }

13 public static void main(String args[])

14 { Circle circle = new Circle();

15 circle.puCost = 100;

16 circle.radius = 3;

17 float area = circle.calCost();

18 System.out.println(area); 

19 }

20 }

2. static Methods 

The static methods can be accessed like static variables through the class name without creating 
any instance/object. The static methods can also be called using the object reference. The static 
methods can directly access only static variables irrespective of the fact that they are invoked 
through class name or object reference.

The general form of a static method is:

static <return-type> <method-name> (parameter-list)


<method-body>


The general form is same as that of a instance method with the only difference that the modifier 
static is used before the return type.

Invoking/Calling static Methods

Example: The following program demonstrates the use of static method. The method area() 
takes one argument representing the radius of the circle and calculates its area. The method area() does not use any instance variable so it is declared as static and can be invoked through class name without creating any instance/object.

1 class Circle

2 { static final float  PI = 3.1415f;

3 static float area(float radius)

4 { return PI * radius * radius;

5 }

6 public static void main(String args[])

7 { int r = 4;

8 float area = Circle.area(r); 

9 //float area = area(r); //Nesting of static methods 

10 System.out.println("Area: "+area); 

11 }

12 }

Here the static variable PI is declared to be constant by putting modifier before the type. This is 
equivalent to const of C/C++.

Nesting of Class Methods

One class method can invoke the other class method of the same class without qualifying i.e. by 
just specifying the method name. For example, in the above program main() method, which is 
static can call the static method area() of the same class simply by its name: 

float area = area(r);

This nesting can go to any depth.