Recall that inheritance implements the "is-a" relationship. For example, in the Introduction to Inheritance notes, class RaceHorse was defined to be a subclass of Horse (because every RaceHorse is a Horse). Therefore, it makes sense that a RaceHorse can be used in any context that expects a Horse. For example:
Horse h; RaceHorse r = new RaceHorse(); h = r;Variable h is of type Horse, so in an assignment of the form
Note that every Horse is not a RaceHorse, so in general, a Horse cannot be used when a RaceHorse is expected. For example, the following code causes a compile-time error:
Horse h = new Horse(); RaceHorse r = h;Here are three more examples of code that sets a Horse variable or parameter to point to a RaceHorse object:
(1) Horse h = new RaceHorse(); // h is of type Horse, but it points to a // RaceHorse object (2) public static void f( Horse h ) { ... } ... RaceHorse r = new RaceHorse(); f( r ); // f's formal parameter h is of type Horse, but the actual // parameter points to a RaceHorse object (3) public static RaceHorse g() { // return a pointer to a RaceHorse object } ... Horse h = g(); // h is of type Horse, but it now points to a RaceHorse // objectIf you know that at a particular point in your code a Horse variable is really pointing to a RaceHorse object, then you can use that variable in a context that expects a RaceHorse, but you must provide a cast. Note that there are two kinds of errors that can arise if you get this wrong:
Examples:
Assume that we have the following declarations of function f and variables h1 and h2:
Now consider the following three calls to f:
Note that when you use a cast you must think about what expression you are casting, and perhaps use parentheses (if that expression is part of a larger expression). For example, suppose variable h is of type Horse but actually points to a RaceHorse. You can call h's WinRace method (which is a RaceHorse method, but not a Horse method), but you have to use a cast, like this:
Assume that classes List, Horse, and RaceHorse have been defined as usual, and that the following statements have been executed:
List L = new List(); Horse h = new Horse(); L.AddToEnd(h); L.firstElement();Question 1: Fill in the following table by specifying, for each of the assignments:
Assignment Type of lhs | Type of rhs | Error?
| h = L.nextElement();
| h = (Horse)L.nextElement();
| RaceHorse r = L.nextElement();
| RaceHorse r = (RaceHorse)L.nextElement();
| |
Question 2: For each of the following method invocations, say whether the code causes a compile-time error, a run-time error, or no error.
((Horse)h).Age(); ((RaceHorse)h).Age();
Suppose that both the Horse and RaceHorse classes define the method
This approach to determining which version of an overridden method to call based on what the variable actually points to at the time of the call (rather than on the type of the variable) is called dynamic dispatch (or sometimes, dynamic binding). In C++, you get dynamic dispatch only if:
Note that in Java all non-primitive types are pointers, so point (1) above does not apply (all method calls are via pointers), and that all non-static methods are (implicitly) virtual, so point (2) does not apply, either. (But it is important to note that static methods do not have dynamic dispatch. So if the Horse and RaceHorse classes both have a static method named foo, then if variable h is of type Horse, h.foo() will always call the Horse version, even if h is pointing to a RaceHorse object. This is one reason why static methods should really be called by using the class name rather than a variable name; e.g., Horse.foo() or RaceHorse.foo() -- it makes it clear which method is being called, and you won't be fooled into thinking that dynamic dispatch applies in this case.)
Also note that dynamic dispatch applies only to methods, not fields.
For example, if Horse and RaceHorse both have a field named
myValue (i.e., the RaceHorse class hides the myValue field defined by
the Horse class), then if variable h is of type Horse, the expression
h.myValue always refers to the field defined in the Horse class,
even if h actually points to a RaceHorse object.
Consider the following program:
class Horse { public void Print() { System.out.println("Horse"); } } class RaceHorse extends Horse { public void Print() { System.out.println("RaceHorse"); } } class Test { public static void DoPrint( Horse h ) { h.Print(); } public static void main(String[] args) { Horse x = new Horse(); Horse y = new RaceHorse(); Racehorse z = new RaceHorse() DoPrint(x); DoPrint(y); DoPrint(z); } }What is printed when this program is executed?
It is possible for a subclass to "request" its superclass's version of an overridden method by using "super.xxx", where xxx is the name of the method. For example, assume as above that both Horse and RaceHorse define the Train method. Normally, a call to Train from inside a RaceHorse method would call the RaceHorse version; however, the Horse version can be called by using super.Train. For example, the RaceHorse class could include a Nurture method, defined as follows (assuming that the Horse class has provided a GetAge method to access the myAge field):
public void Nurture() { if (GetAge() < 3) super.Train(); // train young horses using the Horse method else Train(); // train older horses using the RaceHorse method }
Idea: An abstract method is a method that is declared in a class, but not defined. In order to be instantiated, a subclass must provide the definition. An abstract class is any class that includes an abstract method.
Motivation: Suppose you want to define a class hierarchy in which some method needs to be provided by all subclasses, but there is no reasonable default version (i.e., it is not possible to define a version of the method in the base class that makes sense for the subclasses). For example, assume that you want to use Java classes to define a data structure that will be used to represent flowcharts -- graphs in which nodes represent statements or conditions, and edges represent flow of control. Here's an example flowchart for a program that sums the numbers from 1 to N:
+---------+ | sum = 0 | +---------+ | v +---------+ | k = 1 | +---------+ | v +---------+ | read N | +---------+ | v +----------+ +--> | if k < N | | +----------+ | | | | | +-------+ | v | | +----------+ | | | sum += k | | | +----------+ | | | | | v | | +---------+ | +--- | k++ | | +---------+ | +---------+ | v +-----------+ | write sum | +-----------+You might decide to use inheritance to define the different kinds of nodes, since different kinds of nodes need to store different information; for example:
Node / \ Statement Condition / | \ Assign Read WriteIt will be useful to have a Print method for flowcharts, which means that every node should have a Print method. However, there is no reasonable Print method that will work for all nodes. Therefore, this is a time to use abstract methods! Here's the syntax:
abstract class Node { abstract public void Print(); // no body, just the function header } class Condition extends Node { public void Print() { // actual code goes here } }
Notes:
Node n; // OK -- just a pointer to a Node, no attempt to create a Node object n = new Condition(); // OK -- Condition is not an abstract class n = new Node(); // Error! Can't instantiate an abstract class
class Statement extends Node { // Error! Statement does not } // implement Print, so it must // be an abstract class
Motivation: Some objects have more than one "is-a" relationship. For example, consider designing classes to represent the people associated with a university:
Person / | \ Student Staff Faculty | TANote that a TA is certainly a student, but in some ways, a TA is also staff (e.g., a TA gets paid, receives benefits, etc). Some languages (e.g., C++) allow multiple inheritance; for example, the TA class could be a subclass of both the Student class and the Staff class. However, there are problems with multiple inheritance, and so Java forbids it, providing interfaces instead.
An interface is similar to a class, but can only contain:
Here's an example:
public interface Employee { void RaiseSalary( double d ); double GetSalary(); }Note that both methods are implicitly public and abstract (those keywords can be provided, but are not necessary).
A class can implement one or more interfaces (in addition to extending one class). It must provide bodies for all of the methods declared in the interface, or else it must be abstract. For example:
public class TA implements Employee { void RaiseSalary( double d ) { // actual code here } double GetSalary() { // actual code here } }
interface GoodClass { } class C1 implements GoodClass { ... } ... C1 x = new C1(); if (x instanceof GoodClass) ... // this condition evaluates to trueCloneable is a useful marker interface, defined in java.lang (see Cloning above).
Consider the following program:
interface Employee { public void RaiseSalary(); } interface Musician { public Play(); } class Test implements Employee, Musician { public void RaiseSalary() { System.out.println("raising"); } public void Play() { System.out.println("playing"); } public static void main(String[] args) { Test x = new Test(); x.RaiseSalary(); x.Play(); } }
True or false:
Solutions to Self-Study Questions
Assignment Type of lhs | Type of rhs | Error?
| h = L.nextElement(); | Horse | Object | compile
| h = (Horse)L.nextElement(); | Horse | Horse | none
| RaceHorse r = L.nextElement(); | RaceHorse | Object | compile
| RaceHorse r = (RaceHorse)L.nextElement(); | RaceHorse | RaceHorse | runtime
| |
Question 2: For each of the following method invocations, say whether the code causes a compile-time error, a run-time error, or no error. ((Horse)h).Age(); no error ((RaceHorse)h).Age(); runtime error (Note: for some versions of Java, the runtime error msg gives the wrong line number!)
Test Yourself #2
What is printed when this program is executed?
Horse
RaceHorse
RaceHorse