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 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 create the
complete set of methods defined 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 non-extensible 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.
Note
|
Interfaces
add most of the functionality that is required for many applications that
would normally resort to using multiple inheritances in a language such as
C++.
|
Defining an Interface:
An interface is defined much like a class. This is the general form of
an interface:
General Form
|
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 specifier 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; there can be no default implementation of any method specified within
an interface. Each class that includes an interface must implement all of the
methods. 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 Definition
|
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 defined by the interface. The general form of a class that includes the implements clause
looks like this:
Implementing Interfaces
|
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.
Example
|
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 specifier.
Remember
|
When
you implement an interface method, it must be declared as public.
|
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( ):
Example
|
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.”
Caution
|
Because
dynamic lookup of a method at run time incurs a significant overhead when
compared with the normal method invocation in Java, you should be careful not
to use interfaces casually in performance-critical code.
|
The following example calls the callback( ) method via an
interface reference variable:
Example
|
class
TestIface {
public
static void main(String args[]) {
Callback
c = new Client();
c.callback(42);
}
}
|
Output
|
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 only has knowledge
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);
}
}
|
Output
|
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 defined by that interface, then that class
must be declared as abstract.
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.
Example
|
// 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.
Example
|
//
Define an integer stack interface.
interface
IntStack {
void
push(int item); // store an item
int
pop(); // retrieve an item
}
|
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.
Example
|
//
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.
Example
|
/*
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”:
Example
|
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.
Output
|
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 defined within the interface inheritance chain. Following is an
example:
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 defined by that interface, including any that are inherited from other
interfaces. Although the examples we’ve included in this book do not make
frequent use of packages
or interfaces, both of these tools are an important part of the Java
programming environment. Virtually all real programs that you write in Java
will be contained within packages. A number will probably implement interfaces
as well. It is important, therefore, that you be comfortable with their usage.
Link to this presentation, Click here
No comments:
Post a Comment