Applying Interfaces
For understand the power of interfaces, let's take look at a more practical instance. Within earlier lesson you developed a class called Stack which implemented a simple fixed size stack. Moreover there are several ways to implement a stack. For instance, the stack could be of a fixed size or it could be "growable". The stack can also be held in an array, a binary tree, a linked list, and many more. No matter how the stack is implemented, the interface to the stack remains the similar. That is, the functions push ( ) and pop ( ) declare the interface to the stack independently of the details of the implementation. Since the interface to a stack is divided from its implementation, it is simple to define a stack interface, leaving it to each implementation to declare specifies. Let's look at two instances.
First, here is the interface which defines an integer stack. Put this in a file known as IntStack Java .This interface will be used through both stack implementations.
/ / Define an integer stack interface.
interface Intstack {
void push (int item); / / store an item
int pop ( ) ; retrieve an item
}
The following program creates a class with the name of FixedStack which implements a fixed length version of an integer Stack.
/ / An implementation of IntStack that uses fixed storage.
class FixedStack implements Intstack {
private int stck [ ];
private int tos ;
/ / allocate and initialize stack
FixedStack (int size) {
stck = new int (size);
tos = -1;
}
/ / Push an item onto the stack
public void push ( int item) {
if (tos = =stck.length -1) / / use length member
System.out.println ("Stack is full.");
else
Stck [+ +tos] = item;
}
/ / Pop an item from the stack
public int pop ( ) {
if (tos < 0) {
System.out.println ("Stack underflow.");
retrun 0;
}
else
return stck [tos - -];
}
}
class IFTest {
public static void main(String args [ ]) {
FixedStack mystack1 = new FixedStack (5) ;
FixedStack mystack2 = new Fixedstack (8);
/ / push some numbers onto the stack
for (int i=0; i<5; i++ ) mystack1. push(I;
for (int I=0;I<8; I++) mystack2. push (I );
/ / pop those numbers off the stack
System.out.println ("Stack in mystack1:");
for (int I=0; I<5; I++);
system.out.println(mystacl1.pop ( ) );
system.out.println ("Stack in mystack2:");
for (int I=0; I<8;I+ +)
System.out.println(mystack2.pop ( ));
}
}
Further is another implementation of IntStack which creates a dynamic stack through use of the similar interface definition. Within this implementation, every stack is constructed along with an initial length. The stack is increased in size if this initial length is exceeded. Every time more room is required, the size of the stack is doubled.
/ /Implement a "growable" stack.
class DynStack implemts Intstack {
private int stck [ ];
private int tos ;
/ / allocate and initialize stack
DynStack (int size) {
stck=new int [size];
tos = -1;
}
/ / Push an item onto the stack
public void push (int item ) {
/ / if stack is full, allocate a large stack
if (tos= = stack.length -1) {
int temp [ ]=new int [stack.length * 2]; / / double size
for (int i=0; i<stck.length; i+ +) temp[i] = stck [ i ];
stck = temp;
stck[+ +tos] =item;
}
else
stck [+ +tos] = item;
}
/ / Pop an item from the stack'
public int pop( ) {
if (tos<0) {
System.out.println("Stack underflow.");
return 0;
}
else
return stck [tos - -];
}
}
class IFTest2 {
public static void main (string args [ ]) {
DynStack mystack1 = new DynStack (5);
DynStack mystack 2 = new DynStack (8);
/ / these loops cause every stack for grow
for (int i=0; i<12; i+ +) mystack1 push (i);
for (int i=0; i<20; i++) mystack2 .push (i);
System.out.println ("Stack in mystack1:");
for (int i= 0; i<12; i ++ )
system.out.println(mystack1.pop( ) ) ;
System.out.println ("Stack in mystack2:");
for (int i=0;i<20; i++)
System.out.println(mystack2.pop ( ));
}
}
The further class uses the FixedStack and DynStack implementation both. The class does so through an interface reference. Which means that calls to push ( ) and pop ( ) are resolved at run time rather than at compile time.
/* Create an interface variable.
*/
class IFTest 3
{
public static void main (String args [ ] ) {
IntStack mustack; / / create an interface reference variable
DynStack ds = new FixedStack (5);
FixedStack fs =new FixedStack ( 8);
mystack= ds; / / load dynamic stack
/ / push some numbers onto the stack
for (int i=0; i<12;i ++ ) mystack.push (i);
mystack = fs; / / load fixed stack
for (int i =0; i<8; i+ +) mystack.push (i);
mystack = ds
System.out.println ("Values in dynamic stack:")
for (int i=0; i<12; i++
system.out.println (mystack.pop ( ));
mystack = fs;
System.out.println ("Values in fixed stack:");
for (int i=0; i<8; i++) System.out.println(mystack.pop ( )) ;
}
}
Within this program, mystack is a reference to the IntStack interface. Therefore, when it refers to ds, it uses the versions of push ( ) and pop ( ) defined through the DynStack implementation. When it refers to fs, it uses the versions of push ( ) and pop ( ) defined by FixedStack. As explained, these determinations are made at run time. Accessing multiple implementations of an interface through an interface reference variable is the most powerful way in which Java achieves run-time polymorphism.