Polymorphism and Interfaces
Polymorphism and Dynamic Binding
If a piece of code is designed to work with an object of type X, it will
also work with an object of a class type that is derived from X (any
subclass of X). This is a feature known as polymorphism
and is implemented by the Java interpreter through a mechanism
called dynamic binding. Examples:
- Suppose there is a base class called Shape. Suppose that
Rectangle, Triangle, and Circle are all
subclasses of Shape
- Then it is legal to attach derived objects to the base reference
variables:
Shape s1 = new Circle();
Shape s2 = new Rectangle();
Shape s3 = new Triangle();
- Suppose a method findArea() is called through one of these
variables (s1, s2, s3) in the above example. The method must exist in the
Shape class, but there can be override versions in the subclasses. If
so, then through dynamic binding, the method that runs will be based on
the attached object's type, as a priority over the reference variable
type (Shape):
s1.findArea(); // runs the findArea() method from the Circle class
s2.findArea(); // runs the findArea() method from the Rectangle class
s3.findArea(); // runs the findArea() method from the Triangle class
// if any of these subclasses did not override the finArea() method,
// then the Shape class' findArea() method will run
- If a method expects a parameter of type X, it is legal to pass in an
object of a type derived from X in that slot:
// Sample method
public int draw(Shape s)
{ // definition code }
// sample calls
Shape s1 = new Shape();
Shape s2 = new Circle();
Shape s3 = new Rectangle();
draw(s1); // normal usage
draw(s2); // passing in a Circle object
draw(s3); // passing in a Rectangle object
Another example
Notice that a useful application of polymorphism is to store many related
items, but with slightly different types (i.e. subclasses of the same
superclass), in one storage container -- for example, an array -- and then
do common operations on them through overridden functions.
Example: Assume the setup in the previous example, base class
Shape and derived classes Rectangle, Circle,
Triangle. Suppose the base class has a findArea()
method (probably abstract, since we don't know how to compute the area for
a generic shape), and each derived class has its own findArea()
method.
Shape[] list = new Shape[size]; // create an array of Shape reference variables
list[0] = new Circle(); // attach a Circle to first array slot
list[1] = new Rectangle(); // attach a Rectangle to second slot
list[2] = new Triangle(); // attach a Triangle to third slot
// .... continue in a like manner, attaching a variety of different
// shapes to the array. Note that there could be MANY subcategories
// of shapes and MANY array elements. Notice that we are using
// the polymorphism feature
for (int i = 0; i < list.length; i++)
System.out.println("The area of shape # " + i + " = " + list[i].findArea())
Note that in this for-loop, the appropriate area methods are called for
each shape attached to the array, without the need for separate
storage for different shape types (i.e. no need for an array of circles,
and a separate array of rectangles, etc).
Casting
Since a derived object can always be attached to a corresponding base
class reference variable, this is a type of casting that is implicitly
allowed. Similarly, direct assignment between variables (derived type
assigned into base type) in this order is also allowed, as are explicit
cast operations.
Shape s1, s2; // Shape is the base class
Circle c; // Circle is a derived class
s1 = new Circle(); // automatically legal (as seen above)
s2 = c; // automatically legal
s1 = (Shape)c; // explicit cast used, but equivalent to above
To convert an instance of a superclass (base) to an instance of a subclass
(derived), the explicit cast operation must be used:
c = s1; // would be illegal -- cast needed
c = (Circle)s1; // legal (though not always so useful)
The instanceof operator
The instanceof operator checks to see if the first operand (a
variable) is an instance of the second operand (a class), and returns a
response of type boolean.
Shape s1;
Circle c1;
// other code.....
if (s1 instanceof Circle)
c1 = (Circle)s1; // cast to a Circle variable
This example illustrates basic inheritance with a Shape class
and a Circle class, and it contains a few of the elements
discussed here.
Interfaces
Java does not allow multiple inheritance.
- A subclass can only be derived directly from one base class with the
keyword extends
- In Java, the interface can obtain a similar effect to multiple
inheritance
Interface - A construct, similar to a class, which can contain
only constants, method signatures (abstract), default methods, static
methods, and nested types
- Note that a default method has a concrete implementation but
must be declard with the keyword default, which specifies this
as a default implementation of the method
- A static method in an interface will also be fully defined, and it
has the same access restrictions as with a class
- The key elements of the interface are the method signatures, which
are automatically abstract and NOT defined in the interface. These are
equivalent to abstract methods in a normal class
- So an interface is similar to an abstract class, but it cannot
contain normal instance variables (other than constants)
- In earlier versions of Java (through 7), interfaces could only
contain abstract methods. Default methods and static methods were added
in version 8
Format for declaring an interface
modifier interface Name
{
// constant declarations
// abstract method signatures - keyword "abstract" not needed
// for these.
}
Example (from libraries we will study later):
interface MouseMotionListener
{
public void mouseDragged(MouseEvent e);
public void mouseMoved(MouseEvent e);
}
Note that in this example, the two methods are both abstract. The only methods
that would be defined in an interface are those labelled with the modifiers default or static
How to use an interface
Use the keyword implements to state that a class will use a
certain interface. In this example, Comparable is the name of an
interface. The class ComparableCircle inherits the data from the
Comparable interface, and would then need to implement the
methods (to be able to use them).
class ComparableCircle extends Circle implements Comparable
{
// ....
}
Other rules:
- Only single inheritance for classes, with extends
- Interfaces can inherit other interfaces (even multiple), with
extends, along with a comma-separated list
public interface NewInterface extends interface1, ..., interfaceN
- classes can implement more than one interface with implements (comma-separated list)
public class NewClass extends BaseClass implements interface1, ..., interfaceN
- A class that implements an interface must either specify a
concrete implementation of each of the abstract methods from the
interface, OR it must be declared as an abstract class itself
- An interface reference variable can be attached to any object implementing that interface
- This is very similar to the "base variable can attach to child object" feature of inheritance
- As such, this allows for polymorphic calls in the same way as inheritance
The purpose of an interface is that implementing classes are
providing a contract that they will have the features available
that are declared in the interface. This will be enforced by the
compiler.
Tagging (or marker) interfaces
A tagging interface, also known as a marker interface, is
an interface with no constants or methods in it -- i.e. empty. The
purpose of such an interface is to set up a rule that applies to specific
classes, adding the "is-a" relationship on those classes.
The Cloneable interface
A special interface in the Java.lang package which happens to be empty.
This is an example of a marker interface:
public interface Cloneable
{
}
A class can use the clone() method (inherited
from class Object) only if it implements Cloneable.
Functional interfaces
A functional interface is a Java interface that contains exactly
one (and only one) abstract method. Many such interfaces will be used
with Java's lambda expression capabilities.
Some functional interfaces that will appear in course examples:
- java.lang.Comparable -- contains one method called compareTo
- java.lang.Comparator -- contains one abstract method compare, although it has many default and static methods as well
- java.awt.ActionListener -- contains one method called actionPerformed
These are a few inheritance and interface examples from Deitel (edition
7, slightly older edition)