Objectives
To understand the concept, Inheritance
To be able to differentiate between subclass and superclass
To know what an abstract class is.
To be able to create concrete class from abstract class
To understand the concept, Polymorphism
To know the four forms of polymorphism
To know the what visibility modifiers are
Introduction
In Object Oriented Programming the concept of inheritance means defining a new class from an existing one. That is, the new class is an extension of the existing one. Another way to say this is that the new class is derived or inherited from an existing class. This concept is a key feature of Object Oriented
Programming. The existing class is called the direct superclass and the new or derived class is called the direct subclass. A direct superclass does not have any intervening class between itself and its direct subclass; conversely, a direct subclass does not have any class between itself and its direct superclass. With this understanding, whenever we say subclass, we mean direct subclass; and whenever we say, superclass, we mean direct superclass. Figure 1 shows a conceptual drawing of the concept of inheritance. In the diagram
A
represents the superclass and B represents the subclass.
A
B
Figure 1 A the direct superclass and B the direct subclass
Java allows one and only one direct superclass for any direct subclass. A superclass on the other hand can have any number of subclasses. In addition, a subclass can serve as a superclass for other subclasses.
Figure.2
shows that class A has two subclasses – B and C ; and subclass C is the superclass for class D , making D an indirect subclass of A .
1
B
A
C
D
Figure 2 Classes B and C are direct subclasses of class A and class D is a subclass of C
Conceptually two or more classes can be combined to form a new one. Although this is true with some programming paradigm, Java does not support this idea. Figure 3 shows that class
R
is derived from class
P
and class
Q
. As we said before, Java does not supported this form of inheritance.
P Q
R
Figure 3 Class R inherits classes P and Q. (NOT SUPPORTED BY JAVA )
The format for inheriting a class is as follows:
<modifier> class NewClass extends ExistingClass
{
// new instance fields unique to NewClass
// new constructors unique to NewClass
// new member methods unique to NewClass
}
Where NewClass is the derived class and ExistingClass is the superclass. The subclass can add its own members, as shown in the format.
Example.1
Consider the following situation.
A person is represented by the name and the age.
A student is represented by the name, age, and major.
A graduate student is represented by the name, age, major, and thesis option.
2
An instructor is represented by a name, age, and a salary.
Figure.4
shows how these entities can be represented in a hierarchy of classes. Instructor and
Undegraduate are both direct subclasses of the class Person; and Graduate is a direct subclass of
Undergraduate.
Person
Instructor Undergraduate
Graduate
Figure.4 Hierarchy of classes.
Listing 10.1a
shows a listing of the class Person.
1.
/**
2.
A person is represented by the name and the age
3.
*/
4.
public class Person
5.
{
6.
private String name;
7.
private int age;
8.
/**
9.
Construct a Person object
10.
@param n the name of the person
11.
@param age , the age of the individual
12.
*/
13.
public Person(String n, int a)
14.
{
15.
name = n;
16.
age = a;
17.
}
18.
19.
/**
20.
Print the string represention of the object
21.
@return a string representation of the object
22.
*/
23.
public String toString()
24.
{
25.
return "Person[name = " + name + "\nage = " + age + "]";
26.
}
27.
}
Listing.1a The class Person
3
The class Person is defined the usual way that we are accustomed to writing a class. It has two parameters, a constructor, and a single accessor method that returns the name and age of the individual.
Listing.1b
shows the definition for the class Instructor; i.e. an Instructor has a name, age and get some salary.
1.
/**
2.
An instructor is represented by a name, age, and a salary
3.
*/
4.
public class Instructor extends Person
5.
{
6.
private double salary;
7.
8.
/**
9.
Construct an Instructor object
10.
@param n the name of the instructor
11.
@param age the age of the individual
12.
@param s the salary
13.
*/
14.
public Instructor(String n, int age, double s)
15.
{
16.
super(n, age);
17.
salary = s;
18.
}
19.
20.
/**
21.
Print the string represention of the object
22.
@return a string representation of the object
23.
*/
24.
public String toString()
25.
{
26.
return "Instructor[super=" + super.toString() + " salary = " + salary + "]";
27.
}
28.
}
Listing 10.1b The class Instructor - it inherits the class Person
The class Instructor is defined a little different from the regular way to which we have been accustomed.
Line 4 shows the heading for the class Instructor ; in particular, notice the phrase Instructor extends
Person . By definition, Person is the superclass, and Instructor is the subclass. This means that the class
Instructor may add to what it inherits.
Line 6 shows a member variable – salary . This variable is unique to Instructor and is not a member of the class Person . This means that Instructor has variables name , age , and salary .
Lines 14 thru 18 show the constructor definition for the class Instructor . Line 14 has parameters name , age , and salary . Since the superclass Person contains the codes for initializing name and age , we can use that constructor to carry out the initialization of these two variables. Line 16 shows how this is achieved.
4
The code super( n, age) calls the superclass a constructor by passing it these variables. Notice that the variable salary is initialized locally.
The class has a single method - public String toString() . Line 26 returns the name , age , and salary of the
Instructor . Notice that the variables name and age are private members of the class Person , therefore they cannot be accessed directly from within the class Instructor . In order to access these variables you will have to access public method in the superclass. In the current situation the statement super.toString() returns these values through the toString() method. In particular, notice the use of the qualifier super to refer to the toString() method in the superclass.
The class Undergraduate is defined in a similar way to the class Instructor. See Listing1c . Notice that whereas the class Instructor has added field salary , Undergraduate has added field called major .
1.
/**
2.
A student is represented by the name, age, and major
3.
*/
4.
public class Undergraduate extends Person
5.
{
6.
private String major;
7.
8.
/**
9.
Construct a Student object
10.
@param n the name of the student
11.
@param age the age of the student
12.
@param m the major
13.
*/
14.
public Undergraduate(String n, int age, String m)
15.
{
16.
super(n, age);
17.
major = m;
18.
}
19.
20.
/**
21.
Print the string represention of the object
22.
@return a string representation of the object
23.
*/
24.
public String toString()
25.
{
26.
return "Undergraduate[super= " + super.toString() + "\nmajor=" + major + "]";
27.
}
28.
}
Listing 1c The class Undergraduate – it inherits the class Person.
Finally, the class Graduate inherits the class Undergraduate , which indirectly inherits the class Person .
See Listing 1d .
5
1.
/**
2.
A graduate student is represented by the name, age, major, and thesis option
3.
*/
4.
public class Graduate extends Undergraduate
5.
{
6.
private String thesis;
7.
8.
/**
9.
Construct a Undergraduate object
10.
@param n the name of the student
11.
@param age the age of person
12.
@param m the major
13.
@param thesis
14.
*/
15.
public Graduate(String n, int age, String m, String thesis)
16.
{
17.
super(n, age, m);
18.
this.thesis = thesis;
19.
}
20.
21.
/**
22.
Print the string represention of the object
23.
@return a string representation of the object
24.
*/
25.
public String toString()
26.
{
27.
return "Graduate[super = " + super.toString() + "\nThesis = " + thesis + "]";
28.
}
29.
}
Listing 10.1d The class Graduate - it inherits the class Undergraduate .
Although the class Graduate indirectly inherits the class Person , we do not extends twice. That is, class Graduate extends Undergraduate extends Person is syntactically incorrect. The word extends can only be used once.
Listing 10.1e
shows a possible driver class that makes objects of the different types.
1.
import java.util.ArrayList;
2.
3.
/**
4.
This class tests primarily the Person, Undergraduate, Graduate, and
5.
Instructor classes
6.
*/
7.
public class Driver
8.
{
9.
public static void main(String[] args)
10.
{
11.
ArrayList<Object>list = new ArrayList<Object>();
12.
list.add(new Person("Perry", 35));
13.
list.add(new Undergraduate("Sylvia", 24, "Computer Science"));
14.
list.add(new Graduate("James", 30, "Computer Science", "Graphical User Interface"));
15.
list.add(new Instructor("Edgar", 45, 65000));
16.
list.add(new Graduate("Barry", 32, "Computer Science", "Compiler Construction"));
6
17.
System.out.println(print(list));
18.
}
19.
20.
21.
static void print(ArrayList list)
22.
{
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
}
String s = "Individual is a(n):\n"; int length = list.size(); for (int i = 0; i < length; i++)
{
}
Object o = list.get(i); // 5 th Graduate if ( o instanceof Graduate)
System.out.println(s + (Graduate)o.toString() + "\n"); else if (o instanceof Undergraduate) else
System.out.println(s + (Undergraduate)o + "\n"); else if (o instanceof Instructor)
System.out.println(s + (Instructor)o + "\n"); else if (o instanceof Person)
System.out.println(s + (Person)o + "\n");
System.out.println((Object)o + " ...Unknown individual\n");
40.
}
41.
}
Listing 10.1e The class Driver .
Rules Governing Constructors
Constructors are not inherited. They are included as part of the definition of the subclass so that they can be used to initialize variables in the superclass.
You may use constructors in the superclass to assist with the initialization of variables in the derived class. To do so, you must use the Java method super() . Its format is as follows: super(<argument>);
When used, the statement must be the first executable statement in the current constructor.
Rules Governing Data Members and Methods
The object representing an instance of a derived class contains all the variables and methods of the super class, along with the variables and methods that were added to the new class.
Private members of the superclass cannot be accessed directly by its subclasses.
Instead, you must provide public accessor methods to return their values.
7
You can use the Java modifier super to reference the method in the superclass.
The format of using it is as follows: super.nameOfMethod();
Non-private variables in the superclass can be referenced directly by their names in the subclass. Usually, we use accessor methods to return their values.
A subclass class can override the definition of its inherited method. That is, a subclass can redefine a method it inherits from a superclass. The new method will undoubtedly have the same signature as the super class, but usually they have different code in the body. At runtime the object type determines which method is invoked.
1.
Define the following terms: inheritance , superclass , and subclass .
2.
What are the two ways that the keyword super can be applied.
3.
Show the output from the following program.
class A extends B
{
A(int x)
{
System.out.println("In construct A - x is: " + x);
}
} class B
{
B()
{
System.out.println("In construct B");
}
} class Test
{
public static void main(String[] arg)
{
}
A a = new A(10);
}
4.
What problem arises when you attempt to compile the following program?
} class A extends B
{
A(int x)
{
System.out.println("In construct A - x is: " + x);
} super(x);
8
} class B
{
B(int y)
{
}
System.out.println("In construct B - y is: " + y);
} class Test
{ public static void main(String[] arg)
{
}
A a = new A(10);
5.
Consider the following description. Every employee and every student is a person. Every instructor and every administrator is an employee. Every associate and every undergraduate is a student; furthermore, every graduate has attribute of an undergraduate, and every post graduate has attributes of a graduate.
6.
Draw an inheritance diagram showing the relationship among the entities. Mark clearly the name of each entity.
7.
How many times can we have the extends clause in the class header when creating subclasses?
8.
Where should the following Java statement appear in a subclass? super();
(a) It can be anywhere, as long as it is in the constructor.
(b) It should be the last statement in the constructor.
(c) It should be the first executable statement in the constructor.
(d) It should come before the this() statement in the constructor.
(e) It should be the first executable statement in a method.
An abstract class is a special type of Java class that has zero or more abstract methods. An abstract method is one that is declared but its body is not implemented; only a prototype of the method is specified. The classes that we have been accustomed to had all of their methods defined. These classes are called concrete classes.
Abstract classes are used to declare common characteristics of subclasses. An abstract class cannot be instantiated. It can only be used as a superclass for other classes that extend the abstract class.
9
Like concrete class, an abstract class can contain fields, constructors and concrete methods. An abstract class can include methods that contain no implementation. These are called abstract methods. The abstract method declaration must end with a semicolon rather than a block. If a subclass has any abstract methods, whether declared or inherited, then the class must be declared abstract. Abstract methods are used to provide a template for the classes that inherit the abstract methods. The general format of an abstract class is depicted in Figure 5 .
}
<modifier> abstract class A
{
// member variables
// constructors
// concrete methods
<modifier> abstract return_type methodName(<parameter>) ;
Figure 10.5 The general format of an abstract class
In Figure 10.5, notice in the class declaration the keyword abstract . Also, notice the keyword abstract appears in the method prototype; in addition, instead of specifying the body of the method, it is terminated with the semi-colon.
As mentioned earlier, abstract classes cannot be instantiated; they must be subclassed, and the subclasses must provide the actual implementations for the abstract methods. Any implementation specified can, of course, be overridden by additional subclasses. An object must have an implementation for all of its methods. If the subclasses do not provide implementation for all of the abstract methods that it inherits, then this subclass is also an abstract class, as such you must preface the word class with the keyword abstract .
Example 10.2
Write a Java program to calculate the wages for employees of a company. There are two types of employees – managers and hourly workers. A manager is paid a fixed salary. An hourly worker is not paid a fixed amount. If an hourly paid employee works
40 hours or less the gross pay is calculated as the number of hours worked times the rate of pay. If an hourly paid employee works more than 40 hours for the week, the gross pay is calculated as forty hours times the rate of pay, plus an overtime payment calculated at 1½ the pay rate times the number of excess hours. The hourly rate is fixed at $25.00.
The program must calculate the gross pay and the net pay for each employee.
10
The deduction for a manager is 1/5 th gross pay.
The deduction for anyone who works regular forty hours or less is 1/6 th the
gross pay, and
The deduction for anyone who works over forty hours is 1/6 th the gross pay regular pay, plus 1/8 th of the overtime amount.
Solution
You should convince yourself that technically there are three categories of employees – manager, regular hourly worker who works forty hours or less, and overtime worker who works more than forty hours per week. In each case, the worker gets a gross pay; similarly each worker has some form of deduction, and each gets a net pay after the deduction has been taken out of the gross. Against this background we can see that all three share these commonalities. The method of carrying out these calculations is different however.
With this in mind we can define:
(a) A class for the manager
(b) A class for the regular worker, and
(c) A class for the overtime worker.
These three classes will return the gross pay, the deduction, and the net pay per individual. In addition, they will carry out calculations to determine these values. The method of carrying out these calculations will differ however. Against this background we could define an abstract class to handle all of the commonalities – getting the gross pay, getting the deduction, and getting the net pay. In addition, we can specify the abstract method to indicate that all three classes will carry out some calculations. Let us call these classes Wages , Manager , RegularPay , and OvertimePay ; the class Wages being the abstract class.
Figure 6 show a hierarchy of these classes.
Abstract class Wages
Manager Regular
Overtime
Figure 10.6 A hierarchy of classes
As you read, assume that the following constants exist - MANAGER_TAX , REGULAR_TAX , RATE ,
OVERTIME_RATE , OVERTIME_TAX , and FORTY .
11
Listing 10.2a
shows the abstract class Wages.
1.
abstract class Wages
2.
{
3.
double amount, gross, deduction, netPay;
4.
5.
Wages(double a)
6.
{
7.
amount = a;
8.
}
9.
10.
double getGross()
11.
{
12.
return gross;
13.
}
14.
15.
double getDeduction()
16.
{
17.
return deduction;
18.
}
19.
20.
double getNet()
21.
{
22.
return netPay;
23.
}
24.
25.
abstract void calculate();
26.
}
Listing 10.2a the abstract class Wages.
Line 1 shows the declaration of the abstract class – you must preface the class with the keyword abstract .
Line 25 shows the declaration of the abstract method calculate() . All other features of the class are similar to the concrete classes to which we have been accustomed. All three classes that inherit this abstract class will report some gross pay, calculate some deductions, and get some net pay - hence the need for the three concrete methods. Each of the three classes however, will no doubt carry out these calculations differently; hence the need for the abstract method.
The class Manager is a concrete class, it defines the abstract method calculate. See Lines 8 thru 13 . This method calculates the three values – gross , deduction , and netPay - based on the value that was passed to the superclass. Hence the three accessor methods in the superclass can return these values for a Manager object.
The gross pay for the manager class is any amount that the constructor gets as argument. Listing 10.2b
shows the definition of the concrete class Manager . The method calculate() – as shown in Line 10 thru 13
- simply assigns to the variable gross the value in the variable amount ; the deduction is carried out per the rule as shown on Line 11 ; and lastly, the deduction is subtracted from the gross pay to give the net pay – standard calculations.
1.
class Manager extends Wages
2.
{
12
3.
Manager(double amount)
4.
{
5.
super(amount);
6.
}
7.
8.
void calculate()
9.
{
10.
gross = amount;
11.
deduction = gross * MANAGER_TAX;
12.
netPay = gross - deduction;
13.
}
14.
}
Listing 10.2b The concrete class Manager derived for Wages
The class RegularPay is a concrete class, as shown in Listing 10.2c
. It defines the abstract method that it inherits from the abstract class Wages .
1.
class RegularPay extends Wages
2.
{
3.
RegularPay(double amount)
4.
{
5.
super(amount);
6.
}
7.
8.
void calculate()
9.
{
10.
gross = amount * RATE;
11.
deduction = gross * REGULAR_TAX;
12.
netPay = gross - deduction;
13.
}
14.
}
Listing 10.2c concrete class RegularPay
This class is similar to the Manager class, except that the amount variable represents the number of hours worked for a person who works forty hours or less. The calculate() method, though similar to the one in
Manager class, does calculate the gross pay differently than the manager class’s definition. Nevertheless, all three field variables are assigned their respective values.
The class OvertimePay , a concrete class is somewhat different from the other two, in that it inherits all calculations from the RegularPay class, and adds to each quantity the extra amounts. See Listing 10.2d
.
1.
class OvertimePay extends RegularPay
2.
{
3.
double overtimeHours;
4.
5.
OvertimePay(double amount)
6.
{
7.
super(FORTY);
8.
overtimeHours = amount - FORTY;
9.
}
10.
11.
void calculate()
13
12.
{
13.
super.calculate();
14.
gross = super.getGross() + overtimePay();
15.
deduction = super.getDeduction() + overtimePay() * OVERTIME_TAX;
16.
netPay = gross - deduction;
17.
}
18.
19.
double overtimePay()
20.
{
21.
return overtimeHours * OVERTIME_RATE * RATE;
22.
}
23.
}
Listing 10.2d the concrete class OvertimePay, inherited from RegularPay
In this listing the amount parameter in the constructor represents the number of hours worked. Because this number exceeds forty, we must send forty to the RegularyPay class to carryout the calculations based on regular pay. The excess hours is now assigned to the variable overtimeHours . This value is used to carryout the overtime gross pay as shown in method overtimePay() .
The method calculate() is different from the previous two, in the sense that this method calls the superclass calculate() method to carryout the calculations for the forty hours regular pay. See Line 13 . The gross pay is now based on the amount calculated from the superclass plus the extra from the overtimePay() method.
See Line 14 . The deduction is calculated in likewise manner. See Line 15 .
The term polymorphism simply means that a substance or an object can exist in multiple forms. When applied to Object Oriented Programming, it means a reference variable can be used to create objects from different classes, and you can use this reference variable to call on methods from the different objects. Of course we could use different variable declarations for each object, but our intention is to understand the concept of polymorphism as it relates to Object Oriented programming. The programmer does not have to know the exact type of object in advance, so its behavior is implemented at runtime. This concept is called late binding or dynamic binding.
In terms of Java, polymorphism exhibits itself in four ways – overloading, inclusion, parametric, and coercion or casting.
O
14
Overriding polymorphism occurs when a subclass redefines the body of the method implementation of the superclass. This type of polymorphism is used when different subclasses have different behaviors based on some intrinsic characteristic of the subclass. As an example, the calculate() method in subclass
OvertimePay overrides the calculate() method that it inherits from its superclass RegularPay . Figure
10.7
. class RegularPay extends Wages void calculate()
{
} gross = amount * RATE; deduction = gross * REGULAR_TAX; netPay = gross - deduction; class OvertimePay extends RegularPay void calculate()
{ super.calculate(); gross = super.getGross() + overtimePay(); deduction = super.getDeduction() + overtimePay() * OVERTIME_TAX; netPay = gross - deduction;
}
Figure 10.7 A comparison between the definition of the methods calculate()
Inclusion polymorphism occurs if a subclass class inherits its methods from its superclass, whether direct or indirect superclass, and do not carryout any modification to these methods. In our current example there are three methods that were inherited from the superclass Wages – getGross(), getDeduction(), and getNet() .
All of the subclasses made use of these methods without any changes to any of the methods. This is an example of inclusion or subclassing polymorphism.
Overloading and inclusion are the most common types of polymorphism in Java. Overloading polymorphism exists when each subclass defines its own method of action, either because the superclass has declared the method abstract, or because it simply wishes to provide special processing. On the other hand, inclusion polymorphism exists when subclasses use the method of the superclass to perform a required task.
15
Parametric polymorphism occurs when a class implements methods that are the same in signature except for the parameters passed to the method. This is useful, as one class can handle several different types of arguments to a specific method.
Coercion polymorphism occurs when an object type is cast or coerced into being another object type. Care must be taken when coercing classes. You must understand that in Java, the type of variable does not completely determine the type of object to which it refers. It could hold the reference of to an object of itself if it is a concrete class, or a reference to any of its subclass object. Using our example, consider the following declaration:
Wages w;
This declaration simply says that the variable w
is of type Wages . To what object it will refer, we do not know in advance. For instance, let us consider the following test class TestWages . See Listing 10.2e
.
1.
class TestWages
2.
{
3.
public static void main(String[] arg)
4.
{
5.
String s = "Employee\tGross\n";
6.
Wages w;
7.
8.
w = new Manager(5000);
9.
w.calculate();
10.
s = output(s, w);
11.
12.
w = new OvertimePay(50);
13.
w.calculate();
14.
s = output(s, w);
15.
16.
w = new RegularPay(40);
17.
w.calculate();
18.
s = output(s, w);
19.
20.
System.out.println(s);
21.
}
22.
23.
static String output(String s, Wages w)
24.
{
25.
if (w instanceof OvertimePay)
26.
{
16
27.
28.
29.
}
OvertimePay ot = (OvertimePay)w; s += "Overtime: " + "\t" + ot.getGross() + "\n";
30.
else if( w instanceof RegularPay)
31.
{
32.
33.
RegularPay rp = (RegularPay)w; s += "Regular: " + "\t" + rp.getGross() + "\n";
34.
}
35.
else if (w instanceof Manager)
36.
{
37.
38.
39.
}
40.
else
41.
Manager mgr = (Manager)w; s += "Manager: " + "\t" + mgr.getGross() + "\n"; s += "Unknown person - no payment is due\n";
42.
return s;
43.
}
44.
}
Listing 10.2e A possible test class for the set of classes.
Line 6 shows the declaration of the variable w
. Presently it references no object. However, on Line 8 it is used to create a Manager object. Similarly, on Line 12 and Line 16 it is used to create two other objects -
OvertimePay and RegularPay – respectively.
Line 10 , Line 14 , and Line 16 show that this reference
w,
is passed as argument to the method called output . See Line 23 . Because this reference is changing, we cannot be sure what object it contains at any given point in time. Hence we have to determine its type by using the instanceof operator. See Lines 25 ,
30 , and 35 . Once the type of object is determined, we cast it to its relevant type. See Lines 27 , 32 , and 37 .
Each reference is then used to call the right getGross() method. Figure 10.8
shows the output generated by the test class, TestWages.java
.
Figure 10.8 Output from the program
Listing 10.2f
shows a fully version of the abstract class Wages .
17
1.
abstract class Wages
2.
{
3.
static final double MANAGER_TAX = 1.0/5;
4.
static final double REGULAR_TAX = 1.0/6;
5.
static final double RATE = 25;
6.
static final double OVERTIME_RATE = 1.5;
7.
static final double OVERTIME_TAX = 1.0/8;
8.
static final double FORTY = 40;
9.
10.
double amount, gross, deduction, netPay;
11.
12.
Wages(double a)
13.
{
14.
amount = a;
15.
}
16.
17.
double getGross()
18.
{
19.
return gross;
20.
}
21.
22.
double getDeduction()
23.
{
24.
return deduction;
25.
}
26.
27.
double getNet()
28.
{
29.
return netPay;
30.
}
31.
32.
abstract void calculate();
33.
}
Listing 10.2g Abstract class Wages.java
You cannot cast siblings. For instance, in our example, the class Manager and the class RegularPay are siblings. The following two statements create two sibling objects.
Manager mgr = new Manager(5000);
RegularPay rPay = new RegularPay(30);
Figure 10.9
shows the pictorial representation of the situation. mgr reg
Manager amount = 5000
RegularPay amount = 30
18
Figure 10.9
Two sibling objects mgr and reg
The statement : mgr = (RegularPay) reg; is an attempt to cast object mgr from being of type Manager to be of type RegularPay . This does not work! Figure 10.10
shows the situation when an attempt is made to coerce the object of mgr to the object of reg . This situation gives rise to syntax error. mgr reg
Manager amount = 5000
RegularPay amount = 30
Figure 10.10 Attempting to cast sibling objects
Here is another situation. You cannot cast from a superclass object to a subclass object. Using our example, consider the following statements:
RegularPay reg = new RegularPay(30);
OvertimePay ot = new OvertimePay(50);
Figure 10.11
shows the pictorial representation of these two statements. reg ot regPay
RegularPay amount = 30
OvertimePay amount = 50
19
Figure 10.11 A RegularPay object and a OvertimePay object created.
The statement: reg = (OvertimePay) ot; is an attempt to cast object reg from being of type RegularPay to be of type OvertimePay . This statement compiles perfectly. Figure 10.12
shows the situation when an attempt is made to coerce the reference to the object reg to the reference to the object ot .
reg ot
RegularPay amount = 30
OvertimePay amount = 50
Figure 10.12 Attempting to cast a superclass object to a subclass object.
We have just noticed that the following three statements compile successfully.
RegularPay reg = new RegularPay(30);
OvertimePay ot = new OvertimePay(50); reg = (OvertimePay) ot;
Now add the following statement:
reg.overtimePay();
This statement causes an execution error. You may wonder why. The reason is that the class RegularPay does not know in advance that the class OvertimePay had included in its definition a method called overtimePay() . Hence, a runtime error occurs.
So when do we cast objects? We can only cast subclass objects to their superclass type, because the subclass objects know about the characteristics of the superclass. In our example, all OvertimePay objects are also RegularPay objects, but RegularPay objects are not OvertimePay objects. Therefore we cannot cast a RegularPay object to an OvertimePay object, but we can cast an OvertimePay object to a
RegularPay object. That is, the following statement is legitimate: ot = ( OvertimePay) reg;
20
Finally, we can only cast to concrete class, since we cannot create objects from an abstract class.
We have seen that polymorphism can occur in four forms. At what point will Java know which method it must call? The Java system will never know until when the program is executing. That is, during compilation the compiler cannot attach a reference to any particular instance method. However, the Java
Virtual Machine (JVM) during runtime determines the appropriate method to call based on the current type of object.
Listing 10.2f
shows a full version of the test class. Here again, in the method output() we apply the instanceof operator to determine what type the object is. When it is determined, the reference is cast to the right object. It is at this point that the JVM determines whose method it will call.
For a better presentation the money values are formatted to dollar currency.
1.
import java.text.NumberFormat;
2.
3.
class TestWages
4.
{
5.
public static void main(String[] arg)
6.
{
7.
NumberFormat cf = NumberFormat.getCurrencyInstance();
8.
String s = "Employee\tGross\tTax\tNet\n";
9.
Wages w;
10.
11.
w = new Manager(5000);
12.
w.calculate();
13.
s = output(s, w, cf);
14.
15.
w = new OvertimePay(50);
16.
w.calculate();
17.
s = output(s, w, cf);
18.
19.
w = new RegularPay(40);
20.
w.calculate();
21.
s = output(s, w, cf);
22.
23.
System.out.println(s);
24.
}
25.
26.
static String output(String s, Wages w, NumberFormat cf)
27.
{
28.
if (w instanceof OvertimePay)
29.
{
30.
OvertimePay ot = (OvertimePay)w;
31.
s += "Overtime: " + cf.format(w.getGross()) + " " + cf.format(ot.getDeduction()) +
" " + cf.format(ot. getNet()) + "\n\n";
32.
}
33.
else if( w instanceof RegularPay)
21
34.
{
35.
RegularPay rp = (RegularPay)w; s += "Regular : " + cf.format(rp.getGross()) + " " + cf.format(rp.getDeduction())
+ " " + cf.format(rp. getNet()) + "\n\n";
36.
}
37.
else if (w instanceof Manager)
38.
{
39.
Manager mgr = (Manager)w; s += "Manager : " + cf.format(mgr.getGross()) + " " +
cf.format(mgr.getDeduction()) + " " + cf.format(mgr. getNet()) + "\n\n";
40.
}
41.
else
42.
s += "Unknown person - no payment is due\n";
43.
44.
return s;
45.
}
46.
}
Listing 10.2f Test class
Figure 10.8 Output from the wages program
The uppermost superclass in Java is called Object . Every class that is defined without an explicit extends
Object clause automatically extends the class Object . That is, when you define a class it implicitly extends the class Object . It means therefore, that you are permitted to redefine the methods that your class inherits.
The two most frequently methods that are used and overridden are the toString() method and the equals() method.
The toString() method if not redefined returns a representation of the object to which it refers. By default it specifies the name of the class from which the object is created followed by the address at which the reference to the object is stored. For example, consider the following two statements:
Wages w = new Manager(5000);
System.out.println(w.toString());
22
When these statements were executed the toString() method yielded the output as shown in Figure 10.9
.
Figure 10.9 Default output value returned by the toString() method
This output reflects the name of the class from which the object was created – Manager – and the address at which the reference to the object was stored.
Let us remodel the abstract class Wages to include a redefinition of the toString() method that it automatically inherited from the class Object . In redefining the method let us format the three values and return then as a string of information. See Listing 10.2g
.
1.
import java.text.NumberFormat;
2.
3.
abstract class Wages
4.
{
5.
static final double MANAGER_TAX
6.
static final double REGULAR_TAX
7.
static final double RATE
8.
static final double OVERTIME_RATE
9.
static final double OVERTIME_TAX
10.
static final double FORTY = 40;
= 1.0/5;
= 1.0/6;
= 25;
= 1.5;
= 1.0/8;
11.
12.
double amount, gross, deduction, netPay;
13.
static NumberFormat cf = NumberFormat.getCurrencyInstance();
14.
15.
Wages(double a)
16.
{
17.
amount = a;
18.
}
19.
20.
abstract void calculate();
21.
22.
public String toString()
23.
{
24.
return cf.format(gross) + " "+ cf.format(deduction) +" " + cf.format(netPay) + "\n";
25.
}
26.
}
Listing 10.g Redefinition of the toString() method
23
Lines 22 thru 25 shows the new definition of the toString() method. Notice that we have abandoned the accessor methods since the toString() method can deliver the same information. Note though, if you think that you may want individual values then retain them.
We have also modified the method output() in the test class to accommodate the change in the Wages class. See Listing 10.2h
.
1.
class TestWages
2.
{
3.
public static void main(String[] arg)
4.
{
5.
String s = "Employee Gross\tDeduction\tNetPay\n";
6.
Wages w;
7.
8.
w = new Manager(5000);
9.
w.calculate();
10.
s = output(s, w);
11.
12.
w = new OvertimePay(50);
13.
w.calculate();
14.
s = output(s, w);
15.
16.
w = new RegularPay(40);
17.
w.calculate();
18.
s = output(s, w);
19.
System.out.println(s);
20.
}
21.
22.
static String output(String s, Wages w)
23.
{
24.
if (w instanceof OvertimePay)
25.
{
26.
OvertimePay ot = (OvertimePay)w;
27.
s += "Overtime: " + ot.toString();
28.
}
29.
else if( w instanceof RegularPay)
30.
{
31.
RegularPay rp = (RegularPay)w;
32.
s += "Regular : " + rp.toString();
33.
}
34.
else if (w instanceof Manager)
35.
{
36.
Manager mgr = (Manager)w;
37.
s += "Manager : " + mgr.toString();
38.
}
39.
else
40.
s += "Unknown person - no payment is due\n";
41.
42.
return s;
43.
}
44.
}
Listing 10.2h Each reference calls its own toString() method .
24
Lines 27 , 32 , and 37 shows how the toString() method gets called for each type of object. The result we get is the same as the previous version.
The equals() method is seldom redefined. However, the String class has redefined it. It is this method that is commonly used to test String objects.
We have seen the modifier final used in conjunction with identifiers to define constants. We can also use it to prevent a class from being inherited. If it does not make sense for a class to be inherited, then you can use the modifier final to prevent the inheritance to occur. For example, in our example, there is no practicality in extending this class. In this regard we can bar this class from inheriting it. Line 1 of Listing
10.2i
shows how this is done.
1.
final class OvertimePay extends RegularPay
2.
{
3.
double overtimeHours;
4.
5.
OvertimePay(double amount)
6.
{
7.
super(FORTY);
8.
overtimeHours = amount - FORTY;
9.
}
10.
11.
void calculate()
12.
{
13.
super.calculate();
14.
gross = super.getGross() + overtimePay();
15.
deduction = super.getDeduction() + overtimePay() * OVERTIME_TAX;
16.
netPay = gross - deduction;
17.
}
18.
19.
double overtimePay()
20.
{
21.
return overtimeHours * OVERTIME_RATE * RATE;
22.
}
23.
}
Listing 10.2i Use of the modifier final to prevent inheritance.
Two classes that we have been using that cannot be inherited are the classes Math.java
and String.java
.
A third use of the modifier final is to prevent a method from being redefined. For this to work the method must a concrete method. It cannot be an abstract method. The format is shown in Listing 10.2j.
1.
final return_type methodName()
25
2.
{
3.
4.
}
// method definition
Listing 10.2j Using final to avoid method from being redefined
Java has four ways of controlling access to fields, methods, and classes. These modifiers, taken in increasing order of protection or visibility are:
private – the highest level of protection.
No modifier at all – has weak level of protection with respect to the modifier private.
protected – a weaker protection than no modifier.
public – the weakest level of protection.
You have already encountered private modifier and public modifier. You use the private modifier to hide members of a class. This means that these members can only be accessed from inside the class, and cannot be accessed directly from outside the class.
You use no modifier to allow direct access to classes and members of classes that are inside the same package, but not from outside packages. This is also known as package level modifier.
You use the protected modifier to allow the members of a class to be accessed by the subclasses or classes in the same package.
You use the public modifier to allow access to class and class members from anywhere, hence the word public.
A subclass can override a protected method in its superclass and change its visibility from protected to public . However, a subclass cannot change a public modifier to a protected modifier. In other words, a subclass cannot weaken the accessibility of a method defined in its superclass. It is for this reason why when you redefine the toString() method you must write the method signature in full. That is, you must write as: public String toString() { … }
The method cannot be prefaced with either the modifier private or protected ; neither can it be left at package level since these have narrower scope than public .
Figure 10.10 summarizes the accessibility of the members in a class.
26
Visibility Rules
Modifier on members in a class public
Accessed from the same class protected
(default)
Accessed from the same package
Accessed from a subclass
-
-
private
Figure 10.10
Accessed from a different package
-
-
-
27
p1;
C1 {
x;
y;
z;
u;
m() {
}
}
C3
C1 {
can access x;
can access y;
can access z;
cannot access u;
can invoke m();
}
C2 {
C1 o =
C1();
can access o.x;
can access o.y;
can access o.z;
cannot access o.u;
can invoke o.m();
}
p2; p2;
C4
C1 {
can access x;
can access y;
cannot access z;
cannot access u;
can invoke m();
}
C5 {
C1 o =
C1();
can access o.x;
cannot access o.y;
cannot access o.z;
cannot access o.u;
cannot invoke o.m();
}
Figure 10.10
Visibility modifiers are used to control how data and methods are accessed.
private modifier does not allow visibility outside of the class. Hence you should use methods with public modifier to provide access.
No specified modifier allows the members of the class to be accessed directly from any class within the same package but not from other packages.
The protected modifier enables the members of the class to be accessed by the subclasses in any package or classes in the same package.
The public modifier to enable the members of the class to be accessed by any class.
A subclass may override a protected method in its superclass and change its visibility to public.
However, a subclass cannot weaken the accessibility of a method defined in the superclass.
28
You have learned about inheritance, and have noticed that it is a powerful concept in Object Oriented
Programming. You learned how to define subclasses from concrete classes and to override methods inherited from the superclass.
You also learned about abstract classes, their purpose and how they factor in the inheritance scheme.
29
30