|
|
|
Sponsored Link •
|
|
Advertisement
|
Interfaces and polymorphism
In my quest to understand the interface, the diamond problem
explanation made some sense to me, but it didn't really satisfy me.
Sure, the interface represented Java's way of dealing with the diamond
problem, but was that the key insight into the interface? And how did
this explanation help me understand how to use interfaces in my
programs and designs?
As time went by I began to believe that the key insight into the interface was not so much about multiple inheritance as it was about polymorphism (see the explanation of this term below). The interface lets you take greater advantage of polymorphism in your designs, which in turn helps you make your software more flexible.
Ultimately, I decided that the "point" of the interface was:
Java's interface gives you more polymorphism than you can get with singly inherited families of classes, without the "burden" of multiple inheritance of implementation.
A refresher on polymorphism
This section will present a quick refresher on the meaning of
polymorphism. If you are already comfortable with this fancy word, feel
free to skip to the next section, "Getting more
polymorphism."
Polymorphism means using a superclass variable to refer to a subclass object. For example, consider this simple inheritance hierarchy and code:
abstract class Animal {
abstract void talk();
}
class Dog extends Animal {
void talk() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
void talk() {
System.out.println("Meow.");
}
}
Given this inheritance hierarchy, polymorphism allows you to hold a
reference to a Dog object in a variable of type
Animal, as in:
Animal animal = new Dog();
The word polymorphism is based on Greek roots that mean "many shapes."
Here, a class has many forms: that of the class and any of its
subclasses. An Animal, for example, can look like a
Dog or a Cat or any other subclass of
Animal.
Polymorphism in Java is made possible by dynamic binding, the
mechanism by which the Java virtual machine (JVM) selects a method
implementation to invoke based on the method descriptor (the method's
name and the number and types of its arguments) and the class of the
object upon which the method was invoked. For example, the
makeItTalk() method shown below accepts an
Animal reference as a parameter and invokes
talk() on that reference:
class Interrogator {
static void makeItTalk(Animal subject) {
subject.talk();
}
}
At compile time, the compiler doesn't know exactly which class of
object will be passed to makeItTalk() at runtime. It only
knows that the object will be some subclass of Animal.
Furthermore, the compiler doesn't know exactly which implementation of
talk() should be invoked at runtime.
As mentioned above, dynamic binding means the JVM will decide at
runtime which method to invoke based on the class of the object. If the
object is a Dog, the JVM will invoke Dog's
implementation of the method, which says, "Woof!". If the
object is a Cat, the JVM will invoke Cat's
implementation of the method, which says, "Meow!". Dynamic
binding is the mechanism that makes polymorphism, the "subsitutability"
of a subclass for a superclass, possible.
Polymorphism helps make programs more flexible, because at some future
time, you can add another subclass to the Animal family,
and the makeItTalk() method will still work. If, for
example, you later add a Bird class:
class Bird extends Animal {
void talk() {
System.out.println("Tweet, tweet!");
}
}
you can pass a Bird object to the unchanged
makeItTalk() method, and it will say, "Tweet,
tweet!".
Getting more polymorphism
Interfaces give you more polymorphism than singly inherited families of
classes, because with interfaces you don't have to make everything fit
into one family of classes. For example:
interface Talkative {
void talk();
}
abstract class Animal implements Talkative {
abstract public void talk();
}
class Dog extends Animal {
public void talk() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
public void talk() {
System.out.println("Meow.");
}
}
class Interrogator {
static void makeItTalk(Talkative subject) {
subject.talk();
}
}
Given this set of classes and interfaces, later you can add a new class
to a completely different family of classes and still pass instances of
the new class to makeItTalk(). For example, imagine you
add a new CuckooClock class to an already existing
Clock family:
class Clock {
}
class CuckooClock implements Talkative {
public void talk() {
System.out.println("Cuckoo, cuckoo!");
}
}
Because CuckooClock implements the Talkative
interface, you can pass a CuckooClock object to the
makeItTalk() method:
class Example4 {
public static void main(String[] args) {
CuckooClock cc = new CuckooClock();
Interrogator.makeItTalk(cc);
}
}
With single inheritance only, you'd either have to somehow fit
CuckooClock into the Animal family, or
not use polymorphism. With interfaces, any class in any family can
implement Talkative and be passed to
makeItTalk(). This is why I say interfaces give you more
polymorphism than you can get with singly inherited families of
classes.
|
Sponsored Links
|