Interfaces
Using
the keyword interface, you can fully
abstract a class’ interface from its implementation. That is, using interface, you can specify what a class
must do, but not how it does it. Interfaces are syntactically similar to
classes, but they lack instance variables, and, as a general rule, their
methods are declared without any body. In practice, this means that you can
define interfaces that don’t make assumptions about how they are implemented.
Once it is defined, any number of classes can implement an interface. Also, one class can implement any number of interfaces.
To implement an interface, a
class must provide the complete set of methods required by the interface.
However, each class is free to determine the details of its own implementation.
By providing the interface keyword,
Java allows you to fully utilize the “one interface, multiple methods” aspect
of polymorphism.
Interfaces
are designed to support dynamic method resolution at run time. Normally, in
order for a method to be called from one class to another, both classes need to
be present at compile time so the Java compiler can check to ensure that the
method signatures are compatible. This requirement by itself makes for a static
and nonextensible classing environment. Inevitably in a system like this,
functionality gets pushed up higher and higher in the class hierarchy so that
the mechanisms will be available to more and more subclasses. Interfaces are
designed to avoid this problem. They disconnect the definition of a method or
set of methods from the inheritance hierarchy. Since interfaces are in a
different hierarchy from classes, it is possible for classes that are unrelated
in terms of the class hierarchy to implement the same interface. This is where
the real power of interfaces is realized.
Defining
an Interface
An interface is defined much
like a class. This is a simplified general form of an interface:
access interface name {
return-type method-name1(parameter-list); return-type method-name2(parameter-list);
type final-varname1 = value; type final-varname2 = value; //...
return-type method-nameN(parameter-list); type final-varnameN = value;
}
When no access modifier is
included, then default access results, and the interface is only available to
other members of the package in which it is declared. When it is declared as public, the interface can be used by
any other code. In this case, the interface must be the only public interface declared in the file, and the file must have
the same name as the interface. name
is the name of the interface, and can be any valid identifier. Notice that the
methods that are declared have no bodies. They end with a semicolon after the
parameter list. They are, essentially, abstract methods. Each class that
includes such an interface must implement all of the methods.
Before continuing an
important point needs to be made. JDK 8 added a feature to interface that makes a significant change to its capabilities.
Prior to JDK 8, an interface could
not define any implementation whatsoever. This is the type of interface that
the preceding simplified form shows, in which no method declaration supplies a
body. Thus, prior to JDK 8, an interface could define only “what,” but not
“how.” JDK 8 changes this. Beginning with JDK 8, it is possible to add a default implementation to an interface
method. Thus, it is now possible for interface
to specify some behavior. However, default methods constitute what is, in
essence, a special-use feature, and the original intent behind interface still remains. Therefore, as
a general rule, you will still often create and use interfaces in which no
default methods exist. For this reason, we will begin by discussing the
interface in its traditional form. The default method is described at the end
of this chapter.
As the
general form shows, variables can be declared inside of interface declarations.
They are implicitly final and static, meaning they cannot be changed
by the implementing class. They must also be initialized. All methods and
variables are implicitly public.
Here is
an example of an interface definition. It declares a simple interface that
contains one method called callback( )
that takes a single integer parameter.
interface Callback {
void callback(int param);
}
Implementing
Interfaces
Once an interface has been defined, one or more classes can implement that
interface. To implement an interface, include the implements clause in a class definition, and then create the
methods required by the interface. The general form of a class that includes
the implements clause looks like
this:
class classname [extends superclass] [implements interface
[,interface...]] { // class-body
}
If a
class implements more than one interface, the interfaces are separated with a
comma. If a class implements two interfaces that declare the same method, then
the same method will be used by clients of either interface. The methods that
implement an interface must be declared public.
Also, the type signature of the implementing method must match exactly the type
signature specified in the interface
definition.
Here is a small example class
that implements the Callback
interface shown earlier:
class Client implements Callback { // Implement
Callback's interface
public void callback(int p) {
System.out.println("callback called with
" + p);
}
}
Notice that callback( ) is declared using the public access modifier.
It is
both permissible and common for classes that implement interfaces to define
additional members of their own. For example, the following version of Client implements callback( ) and adds the method
nonIfaceMeth( ):
class Client implements Callback { // Implement Callback's interface
public void callback(int p) {
System.out.println("callback called with
" + p);
}
void nonIfaceMeth() {
System.out.println("Classes
that implement interfaces " + "may also define other members,
too.");
}
}
Accessing
Implementations Through Interface References
You can declare variables as
object references that use an interface rather than a class type. Any instance
of any class that implements the declared interface can be referred to by such
a variable. When you call a method through one of these references, the correct
version will be called based on the actual instance of the interface being
referred to. This is one of the key features of interfaces. The method to be
executed is looked up dynamically at run time, allowing classes to be created later
than the code which calls methods on them. The calling code can dispatch
through an interface without having to know anything about the “callee.” This
process is similar to using a superclass reference to access a subclass object,
as described in Chapter 8.
The following example calls
the callback( ) method via an
interface reference variable:
class TestIface {
public static void main(String args[]) {
Callback c = new Client();
c.callback(42);
}
}
The output of this program is
shown here:
callback called with 42
Notice
that variable c is declared to be of
the interface type Callback, yet it
was assigned an instance of Client.
Although c can be used to access the
callback( ) method, it cannot access
any other members of the Client
class. An interface reference variable has knowledge only of the methods
declared by its interface
declaration. Thus, c could not be
used to access nonIfaceMeth( ) since
it is defined by Client but not Callback.
While the preceding example
shows, mechanically, how an interface reference variable can access an
implementation object, it does not demonstrate the polymorphic power of such a
reference. To sample this usage, first create the second implementation of Callback, shown here:
// Another implementation of Callback.
class AnotherClient implements Callback {
// Implement Callback's interface
public void callback(int p) {
System.out.println("Another version of callback");
System.out.println("p squared is " +
(p*p));
}
}
Now, try the following class:
class TestIface2 {
public static void main(String args[]) {
Callback c = new Client();
AnotherClient ob = new AnotherClient();
c.callback(42);
c = ob; // c now refers to AnotherClient object
c.callback(42);
}
}
The output from this program
is shown here:
callback called with 42
Another version of callback p squared is 1764
As you can see, the version
of callback( ) that is called is
determined by the type of object that c
refers to at run time. While this is a very simple example, you will see
another, more practical one shortly.
Partial
Implementations
If a class includes an
interface but does not fully implement the methods required by that interface,
then that class must be declared as abstract.
For example:
abstract class Incomplete implements Callback {
int a, b;
void show() {
System.out.println(a + " " + b);
}
//...
}
Here, the class Incomplete does not implement callback( ) and must be declared
as abstract. Any class that inherits Incomplete must implement callback( ) or be declared abstract itself.
Nested
Interfaces
An interface can be declared
a member of a class or another interface. Such an interface is called a member interface or a nested interface. A nested interface can
be declared as public, private, or protected. This differs from a top-level interface, which must
either be declared as public or use the default access level,
as previously described. When a nested interface is used outside of its
enclosing scope, it must be qualified by the name of the class or interface of
which it is a member. Thus, outside of the class or interface in which a nested
interface is declared, its name must be fully qualified.
Here is an example that
demonstrates a nested interface:
A nested interface example.
This class contains a member interface. class A
{
this is a nested interface
public interface NestedIF {
boolean isNotNegative(int x);
}
}
// B implements the nested interface.
class B implements A.NestedIF {
public boolean isNotNegative(int x) {
return x < 0 ? false: true;
}
}
class NestedIFDemo {
public static void main(String args[]) {
// use a nested interface reference
A.NestedIF nif = new B();
if(nif.isNotNegative(10))
System.out.println("10 is not negative");
if(nif.isNotNegative(-12)) System.out.println("this
won't be displayed");
}
}
Notice that A defines a member interface called NestedIF and that it is declared public. Next, B
implements the nested interface by specifying
implements A.NestedIF
Notice
that the name is fully qualified by the enclosing class’ name. Inside the main( ) method, an A.NestedIF reference called nif
is created, and it is assigned a reference to a B object. Because B
implements A.NestedIF, this is
legal.
Applying
Interfaces
To
understand the power of interfaces, let’s look at a more practical example. In
earlier chapters, you developed a class called Stack that implemented a simple fixed-size stack. However, there
are many ways to implement a stack. For example, the stack can be of a fixed
size or it can be “growable.” The stack can also be held in an array, a linked
list,
a binary
tree, and so on. No matter how the stack is implemented, the interface to the
stack remains the same. That is, the methods push( ) and pop( )
define the interface to the stack independently of the details of the
implementation. Because the interface to a stack is separate from its
implementation, it is easy to define a stack interface, leaving it to each
implementation to define the specifics. Let’s look at two examples.
First,
here is the interface that defines an integer stack. Put this in a file called IntStack.java. This interface will be
used by 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 called FixedStack that
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."); return 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(mystack1.pop());
System.out.println("Stack
in mystack2:"); for(int i=0; i<8; i++)
System.out.println(mystack2.pop());
}
}
Following is another
implementation of IntStack that
creates a dynamic stack by use of the same interface
definition. In this implementation, each stack is constructed with an initial
length. If this initial length is exceeded, then the stack is increased in
size. Each time more room is needed, the size of the stack is doubled.
// Implement a "growable" stack.
class DynStack implements 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 larger stack
if(tos==stck.length-1) {
int temp[] = new int[stck.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 mystack2 =
new DynStack(8);
// these loops cause each stack to 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 following class uses both
the FixedStack and DynStack implementations. It does so
through an interface reference. This means that calls to push( ) and pop( ) are
resolved at run time rather than at compile time.
/* Create
an interface variable and access stacks through it.
*/
class IFTest3 {
public static void main(String args[]) {
IntStack mystack; // create an interface reference variable
DynStack ds = new DynStack(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());
}
}
In this program, mystack is a reference to the IntStack interface. Thus, when it
refers to ds, it uses the versions
of push( ) and pop( ) defined by 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 that Java achieves run-time
polymorphism.
Variables
in Interfaces
You can
use interfaces to import shared constants into multiple classes by simply
declaring an interface that contains variables that are initialized to the
desired values. When you include that interface in a class (that is, when you
“implement” the interface), all of those variable names will be in scope as
constants. (This is similar to using a header file in C/C++ to create a large
number of #defined constants or const declarations.) If an interface contains
no methods, then any class that includes such an interface doesn’t actually
implement anything. It is as if that class were importing the constant fields
into the class name space as final
variables. The next example uses this technique to implement an automated
“decision maker”:
import java.util.Random;
interface SharedConstants {
int NO = 0;
int YES = 1; int MAYBE = 2;
int LATER = 3; int SOON = 4; int NEVER = 5;
}
class Question implements
SharedConstants { Random rand = new Random();
int ask() {
int prob = (int) (100 * rand.nextDouble()); if
(prob < 30)
return NO; // 30%
else if (prob < 60)
return YES; // 30%
else if (prob < 75)
return LATER; // 15%
else if (prob < 98)
return SOON; // 13%
else
return NEVER; // 2%
}
}
class AskMe implements SharedConstants { static
void answer(int result) {
switch(result) { case NO:
System.out.println("No");
break; case YES:
System.out.println("Yes");
break; case MAYBE:
System.out.println("Maybe");
break; case LATER:
System.out.println("Later");
break; case SOON:
System.out.println("Soon");
break; case NEVER:
System.out.println("Never");
break;
}
}
public static void
main(String args[]) { Question q = new Question();
answer(q.ask());
answer(q.ask());
answer(q.ask());
answer(q.ask());
}
}
Notice that this program
makes use of one of Java’s standard classes: Random. This class provides pseudorandom numbers. It contains
several methods that allow you to obtain random numbers in the form required by
your program. In this example, the method nextDouble(
) is used. It returns random numbers in the range 0.0 to 1.0.
In this sample program, the
two classes, Question and AskMe, both implement the
SharedConstants interface where NO, YES, MAYBE, SOON, LATER, and NEVER are
defined. Inside each class, the code refers to these constants as if each class
had defined or inherited them directly. Here is the output of a sample run of
this program. Note that the results are different each time it is run.
Later
Soon
No
Yes
Interfaces
Can Be Extended
One interface can inherit
another by use of the keyword extends.
The syntax is the same as for inheriting classes. When a class implements an
interface that inherits another interface, it must provide implementations for
all methods required by the interface inheritance chain. Following is an
example:
One interface can extend another.
interface A {
void meth1(); void meth2();
}
B now includes meth1() and meth2() -- it adds
meth3(). interface B extends A {
void meth3();
}
This class must implement all of A and B
class MyClass implements B {
public void meth1() {
System.out.println("Implement
meth1().");
}
public void meth2() {
System.out.println("Implement meth2().");
}
public void meth3() {
System.out.println("Implement meth3().");
}
}
class IFExtend {
public static void main(String arg[]) { MyClass
ob = new MyClass();
ob.meth1();
ob.meth2();
ob.meth3();
}
}
As an experiment, you might
want to try removing the implementation for meth1( ) in MyClass.
This will cause a compile-time error. As stated earlier, any class that
implements an interface must implement
all methods required by that interface, including any that are inherited from
other interfaces.
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.