Chapter 9,

advertisement
Chapter 9,
Inheritance, Abstract Classes, Interfaces and Generics
Earlier we identified some of the objects in the description of a payroll system for the
Crunchy Crème Cupcake Factory. We identified that we would need to calculate pay for
Salaried Employees, Hourly Employees, and Commission-based Employees. We
identified some basic features that all types of employees share in common: first name,
last name, title, hire date, address, phone # etc. , and features specific to each subtype:
Such as only Hourly employees will have an hourly rate, and hours worked
Our program will need to create instances of each of our employee types to represent the
employees our factory employees, and to calculate their pay. So each of our employee
types will become object classes. Now, we can define our employee classes to contain all
data members, both those specific to their subtype, and general to all employees (for
simplicity we’ll focus on:
Salaried Employee
- FirstName: String
- LastName: String
- Title: String
- HireDate: String
Salary: float
+ Salaried() : [constructor]
+ Salaried(in first: String, in
last: String, in date: String, in
position: String, in Salary:
float): [constructor]
+ Print(): void [query]
+ getFirst() : String [query]
+ setFirst(in first: String): void
Hourly Employee
- FirstName: String
- LastName: String
- Title: String
- HireDate: String
Hourly Rate: float
Hours Worked: float
Commission Employee
- FirstName: String
- LastName: String
- Title: String
- HireDate: String
Salary: float
comissionSales: float
commissionRate: float
+ Hourly() : [constructor]
+ Commission() : [constructor]
+ Hourly(in first: String, in
last: String, in date: String, in
position: String, in rate:float,
in hrsWorked: float):
[constructor]
+ Comission(in first: String, in
last: String, in date: String, in
position: String, in base: float,
in sales: float, in rate: float ):
[constructor]
+ Print(): void [query]
+ Print(): void [query]
+ getFirst() : String [query]
+ getFirst() : String [query]
+ setFirst(in first: String): void
+ setFirst(in first: String): void
There is a problem with this design choice, it duplicates code and decreases reliability
and reusability. Because whenever the same code resides in more than one physical
location and a change needs to be made to the algorithm the code contains, there will
always be the possibility that the code will be modified in one location but not all.
Whenever possible we want to avoid duplication of code. A different design choice
would be to create one generic Employee object with contained all the possible data
members needed by any subtype of employee.
Employee
- FirstName: String
- LastName: String
- Title: String
- HireDate: String
Salary: float
comissionSales: float
commissionRate: float
Hourly Rate: float
Hours Worked: float
+ Employee() : [constructor]
+ Employee(in first: String, in
last: String, in date: String, in
position: String, in salary:
float, in sales: float, in rate:
float, in rate: float, in
hrsWorked: float ):
[constructor]
+ Print(): void [query]
+ getFirst() : String [query]
+ setFirst(in first: String): void
Again this is a poor design choice, when we design a class EVERY data member must be
applicable to EVERY instance of the class, and every method should appropriate for
every instance. For example if we have a method + calculateComission() : float
which calculates a commission based employee’s commission based on sales, it
SHOULD not be applied to an instance of another type of employee, but it could in this
design. This design is not cohesive.
So a better design choice is to define a “generic” Employee class that contains ONLY
those data members that all subtypes of employees share in common.
Employee
- FirstName: String
- LastName: String
- Title: String
- HireDate: String
+ Employee() : [constructor]
+ Employee(in first: String, in last:
String, in date: String, in position:
String): [constructor]
+ Print(): void [query]
+ getFirst() : String [query]
+ setFirst(in first: String): void
Then we can use inheritance to define subclasses of class Employee that automatically
obtain the shared features from employee, but then implement type specific features that
represent only the individual subclass.
For simplicity we will focus on only the subclasses Salaried Employee and hourly
employee.
Employee
Salaried Employee
Hourly Employee
When we create a subclass, via the “extends” keyword, it automatically inherits or gets a
copy of all of the public and private members of the parent class. So when class Salaried
employee is defined, it will inherit the data members firstName, and lastName etc,
however it will only be able to directly access the public members inherited from class
Employee. Let’s work through an example. A partial implementation of class Employee
is:
public class
private
private
private
private
Employee{
String firstName;
String lastName;
String title;
String hireDate;
public Employee(){
firstName = new String("unknown");
lastName = new String("unknown");
title = new String("unknown");
hireDate = new String("unknown");
}// end default constructor
public Employee(String first, String last, String
position, String date) {
firstName = first;
lastName = last;
title = position;
hireDate = date;
}// end non default constructor
public void setFirst(String name) {
firstName = new String(name);
}
public String getFirst() {
return firstName;
}
public void print() {
// this method prints out the data stored in all
// data members for a specific instance
System.out.println("The employee is " + firstName + " " +
lastName + " , and was hired on " + hireDate +
" for the position of " + title);
}// end print
}// end employee
We can write a test program to create employees and print out their information
such as:
public class testEmployee{
public static void main(String[] args){
Employee john = new Employee("John", "Adams",
"Janitor", “2/18/2008”);
Employee unknown = new Employee();
john.print(); // print the contents of John
unknown.print(); // print the contents of unknown
// change unknown’s first name, and print again
unknown.setFirst("Sandy");
unknown.print();
}// main
}// end testemployee
When the test program is executed it produces the results
The employee is John Adams , and was hired on 2/18/2008 for the position of
Janitor
The employee is unknown unknown , and was hired on unknown for the position of
unknown
The employee is Sandy unknown , and was hired on unknown for the position of
unknown
Overloaded methods:
A class can contain more than one method by the same name. When this occurs the
method is said to be overloaded. Each overloaded method must be distinguishable
through the number and type of their parameters. When we have multiple constructors in
a class, the constructors are overloaded. As an example of overloading a method, we can
add a second “print” method to class Employee, which receives an additional string from
the client program that is to be added to the end of the information printed.
public void print(String xtra) {
System.out.println("The employee is " + firstName + " " +
lastName + " , and was hired on " + hireDate +
" for the position of " + title +
" \n The extra information to be printed is " + xtra);
}// end print
The java interpreter decides which version of print to execute based on parameters, return
type, and the TYPE of the object to which the method is applied. We can test this in our
test program as:
public class testEmployee{
public static void main(String[] args){
Employee john = new Employee("John", "Adams","2/18/2008","Janitor");
Employee unknown = new Employee();
john.print();
unknown.print();
unknown.setFirst("Sandy");
unknown.print();
john.print("Camille");
}// main
}// end testemployee
Which produces the output: (differences are highlighted in red)
The employee is John Adams , and was hired on 2/18/2008 for the position of
Janitor
The employee is unknown unknown , and was hired on unknown for the position of
unknown
The employee is Sandy unknown , and was hired on unknown for the position of
unknown
The employee is John Adams , and was hired on 2/18/2008 for the position of
Janitor
The extra information to be printed is Camille
Subclasses:
To define a subclass of an existing class, on the title line of the class we add the
“extends” keyword and the name of the parent class:
public class Salaried extends Employee{
private float salary;
public Salaried() {
salary = 0.0f;
}
public Salaried(String first, String last, String date,
String position, float sal) {
salary =sal;
}
}// end class
Although class Salaried “inherits” the private data members of class Employee, they are
still private, and thus hidden. This means that the constructors and methods inside
Salaried can not directly reference these data members by name using the dot (.) operator.
For example the following would be illegal in the default constructor for class Salaried:
public Salaried() {
firstName = new String("unknown");
lastName = new String("unknown");
title = new String("unknown");
hireDate = new String("unknown");
salary =sal;
}
When compiled this would produce the result:
ÏSalaried.java:13: firstName has private access in Employee
firstName = new String("unknown");
^
Salaried.java:14: lastName has private access in Employee
lastName = new String("unknown");
^
Salaried.java:15: title has private access in Employee
title = new String("unknown");
^
Salaried.java:16: hireDate has private access in Employee
hireDate = new String("unknown");
^
4 errors
Private members of parent classes are still private, period.
To correctly initialize the private data members of the parent class, you need to invoke a
constructor from the parent class, and this is done via the Super keyword. The parent
class of subclass is also referred to as the subclass’ “Super” class. To invoke the
constructor you use the keyword super followed by parameters. IE:
public Salaried() {
super(); // calls the default constructor of employee
salary = 0.0f;
}
public Salaried(String first, String last, String date,
String position, float sal) {
// calls the non-default constructor
super(first, last, position, date);
salary =sal;
}
The invocation of the parent class’s constructor must be the first line in
the child class’s constructor.
Once a child class is created, all public methods inherited from a parent class can be
applied to an object of the child class in either a client program, or inside the body of a
method in the child class. This is because inheritance in java is an example of pure
subtype or “isa” inheritance. This means that in instance of the child class can be treated
in all ways that an instance of the parent class can, a SalariedEmplyee isa Employee
public class testEmployee{
public static void main(String[] args){
Employee john = new Employee("John", "Adams","Janitor",
"2/18/2008");
Employee unknown = new Employee();
Salaried Pam = new Salaried("Pam", "Jones","7/1/2009”,
“Surgeon”, 30000f);
john.print();
unknown.print();
Pam.print();
}// main
}// end testemployee
This displays:
The employee is John Adams , and was hired on 2/18/2008 for the position of Janitor
The employee is unknown unknown , and was hired on unknown for the position of unknown
The employee is Pam Jones , and was hired on 7/1/2009 for the position of Surgeon
When the print() method was applied to object Pam, the type of Pam was
determined, which is “Salaried”. Class Salaried is checked for an instance method
named print(). If it exists it is executed, if not the super class of Salaried is checked,
and the print() method from class Employee is executed, which does not print any of
the data members specific to a salaried employee, such as Salary.
Overridden method in child classes:
A child class may re-implement or override any public method implemented in the
parent class. For example, we can re-implement print() inside Salaried as:
public void print() {
System.out.println("Print method in Salaried");
System.out.println("The employee earns " + salary +
" per year.");
}
This time when the print() method was applied to object Pam in our test program, the
print() from class Salaried would be used and would display:
The employee is John Adams , and was hired on 2/18/2008 for the position of Janitor
The employee is unknown unknown , and was hired on unknown for the position of unknown
Print method in Salaried
The employee earns 30000.0 per year.
This time the print method from Salaried is called, but only prints the individual’s salary.
Again we can not directly access the data members inherited from Employee. If we tried
the following:
public void print() {
System.out.println("Print method in Salaried");
System.out.println("The employee's name is " +
this.firstName + " " + this.lastName +
" and the employee earns " + salary + " per year.");
}
this generates the errors:
Salaried.java:20: firstName has private access in Employee
this.firstName + " " + this.lastName +
^
Salaried.java:20: lastName has private access in Employee
this.firstName + " " + this.lastName +
^
To access the private members, we must either use accessors (getters) provided by the
parent class, IE:
public void print() {
System.out.println("Print method in Salaried");
System.out.println("The employee's name is " +
this.getFirst() +
" and the employee earns " + salary + " per year.");
}
which would cause our test program to produce the output:
The employee is John Adams , and was hired on 2/18/2008 for the position of Janitor
The employee is unknown unknown , and was hired on unknown for the position of unknown
Print method in Salaried
The employee's name is Pam and the employee earns 30000.0 per year.
Another alternative is that from inside the body of a method in a child class, an
overridden method from the parent class can be invoked via the “super” keyword. IE:
public void print() {
System.out.println("Print method in Salaried");
super.print();
System.out.println("The employee's name is " +
this.getFirst() +
" and the employee earns " + salary + " per year.");
}
super.print(); Invokes the print() method from class Employee and after that method
completes, execution resumes inside the print() method of class Salaried. Our test
program will now produce:
The employee is John Adams , and was hired on 2/18/2008 for the position of Janitor
The employee is unknown unknown , and was hired on unknown for the position of unknown
Print method in Salaried
The employee is Pam Jones, and was hired on 7/1/2009 for the position of Surgeon
The employee's name is Pam and the employee earns 30000.0 per year.
Abstract Classes:
Sometimes in the design of a hierarchy of related classes, it doesn’t make sense from the
application standpoint to declare instances of the parent class. In our example, would
there ever be just a generic “employee” who isn’t either salaried, hourly, or commissionbased? We can define our parent class so that is can only serve as a parent for subclasses,
allowing no instances of it to be declared in a client program. The abstract keyword
does this.
public abstract class Employee{
private String firstName;
private String lastName;
private String title;
private String hireDate;
public Employee(){
firstName = new String("unknown");
lastName = new String("unknown");
title = new String("unknown");
hireDate = new String("unknown");
}// end default constructor
public Employee(String first, String last, String position,
String date) {
firstName = first;
lastName = last;
title = position;
hireDate = date;
}// end non default constructor
public void setFirst(String name) {
firstName = new String(name);
}
public String getFirst() {
return firstName;
}
public void print() {
// this method prints out the data stored in all
// data members for a specific instance
System.out.println("The employee is " + firstName + " " +
lastName + " , and was hired on " + hireDate +
" for the position of " + title);
}// end print
}// end employee
By making a class abstract, we are indicating that it can only serve as a parent for other
classes. So in our test program:
public class testEmployee{
public static void main(String[] args){
Employee john = new Employee("John", "Adams","Janitor", "2/18/2008");
Salaried Pam = new Salaried("Pam", "Jones", "7/1/2009","Surgeon",
30000f);
john.print();
Pam.print();
}// main
}// end testemployee
This would generate the error:
testEmployee.java:5: Employee is abstract; cannot be instantiated
Employee john = new Employee("John", "Adams","Janitor", "2/18/2008");
An abstract class, can include the definition of both static and instance data members,
and the implementation of public and private methods. Sometimes an abstract class
resemble an interface by defining abstract methods. An abstract method defined within
an abstract class, specifies an interface for a method, but does NOT implement the body.
Any child class of the abstract class is then REQUIRED to implement the method. For
example, the following abstract method might be implemented inside Employee.
public abstract float calculatePay();
Any class, ie: Salaried, that extends Employee must implement a method with the same
name, parameters, and return type. So now the implementation of Salaried must include:
public float calculatePay() {
// there are 24 pay periods in the year
float pay = this.salary/24.0f;
System.out.println("The employee will earn " +
pay + " each pay period. ");
return pay;
}
Our test program can be modified to:
public class testEmployee{
public static void main(String[] args){
Salaried Pam = new Salaried("Pam", "Jones",
"7/1/2009","Surgeon", 30000f);
Pam.print();
Pam.calculatePay();
}// main
}// end testemployee
and will produce the result:
Print method in Salaried
The employee is Pam Jones , and was hired on 7/1/2009 for the position of Surgeon
The employee's name is Pam and the employee earns 30000.0 per year.
The employee will earn 1250.0 each pay period.
Polymorphism
Polymorphism is the definition of operations that apply to more than one type:
Polymorphic means having many forms. In object oriented languages polymorphism
refers to the late binding of a call to a method to the actual implementation of a specific
method.. Polymorphic operations are defined for “classes” or “families” of types that
have common properties… These common properties make it meaningful to define an
operation applying to all sub-types in the class.
For example it is possible to define an operation for a parent class which can be used or
redefined by all subclasses of the parent. In our employee example method print() would
be a polymorphic operation.
Basically polymorphism works like this: as a program executes, an object will have an
apparent and an actual data type. The apparent data type of an object is the data type
of the variable to which it is assigned, which must be the same as or an ancestor class
of the actual type of the object. When a method is applied to an instance of the object,
dynamically at runtime the actual data type of the object is obtained, and the correct
version of the overridden method is executed.
As an example: if you have an object of a declared (or apparent) type T whose actual
type may vary at run time.. T objA; A method call:
objA.m();
the actual instance of the method m that is called depends on the actual or dynamic
type of the object at runtime.
Consider an application that implements a drawing program, such that in a program there
are many different kinds of graphical objects such as circles, rectangles ..etc.. Each
capable of displaying themselves in a window, using a “paint” method. Each type of
graphic object belongs to a family of graphic objects called “graphicalObj
Graphic
Object:
Color
Pattern
Paint
Square:
Color
Pattern
Length
paint
Circle:
Color
Pattern
Radius
paint
Text:
Color
Pattern
Font
paint
Instances of child classes, can be referred to as an instance of the parent class, if we have
a collection of random graphic objects stored in a linked list:
List of
graphical
Obj
Text:
Square:
Circle:
Text:
If we iterate through this list:
foreach (graphicalObj obj in list) {
obj.paint();
..
}
Each graphical object paints itself according to its own logic.. squares differently than
circles.. so the actual paint method being called varies according to the actual type of the
graphical object. Yet each is merely asked to paint itself in turn.. Each instance of the
paint method must uphold the principle of substitutability.. each instance of the paint
method performs the same abstract function, with only the code being particularized for
the individual type or subtype. The profile and the function for each variation of paint
must be identical
In terms of our application, we can assign an object of type Salaried, in a variable of type
Employee:
public class testEmployee{
public static void main(String[] args){
Employee unknown = new Salaried("Larry", "Johnson", "6/1/2003",
"Baker", 15000f);
Salaried Pam = new Salaried("Pam", "Jones", "7/1/2009","Surgeon",
30000f);
Pam.print();
Pam.calculatePay();
unknown.print();
}// main
}// end testemployee
This would produce the result:
Print method in Salaried
The employee is Pam Jones , and was hired on 7/1/2009 for the position of Surgeon
The employee's name is Pam and the employee earns 30000.0 per year.
The employee will earn 1250.0 each pay period.
Print method in Salaried
The employee is Larry Johnson , and was hired on 6/1/2003 for the position of Baker
The employee's name is Larry and the employee earns 15000.0 per year.
When an object of a child type is stored in a variable of the base (parent type) this is
called upcasting. The object can be converted back to an object of the child type by
casting it as you would a float, int or double. However, before casting you should ideally
check to see if it is an object of the correct type though the instanceof operator
public class testEmployee{
public static void main(String[] args){
Employee unknown;
Salaried Pam = new Salaried("Pam", "Jones", "7/1/2009","Surgeon",
30000f);
Pam.print();
Pam.calculatePay();
// the following statements would store a reference
// the object contained in variable Pam into the variable
// unknown. Applying the print method to object unknown
// would invoke the print method for class Salaried. However
// the apparent type of the object is “Employee”
unknown = Pam;
unknown.print();
// To store the object contained in “unknown” into a variable
// of the correct type we must first validate that it is
// of the correct type, before casting
Pam = null; //destroys the address stored in Pam
if (unknown instanceof
Salaried) Pam = (Salaried) unknown;
// this changes the apparent type of the object, and stores the
// reference into pam
}// main
}// end testemployee
Doing this type of a conversion is downcasting.
Generics
Beginning with Java 5.0, we can define special parameterized classes that contain a
“parameter” for representing an unknown type. This is one way to write generalized
ADT’s that can be easily reused in applications without the overhead of upcasting and
downcasting. Previously when we wrote queues, stacks, and lists, we created them for
integers, if we wanted to used them for some other data type we had to “rewrite” the code
changing int to whatever… this violates the whole idea of reuse…ideally we should be
able to use the class as is with minimal modification OR we defined their type to be
object…
Instead of coding the actual type of a data member, classes and methods can use a type
parameter <T> to stand for some unknown type, and may then have any reference type
(class type) plugged in for that parameter, when an instance of the class is created.
(Note: you can use any non-keyword name for the parameter, not just T)
The class definition is stored in a java file and compiled like any other class, but when it
is used to declare a variable in a program the actual type to be plugged in for the generic
parameter must be supplied:
The type plugged in for a type parameter must be a reference type, it can not be a
primitive type such as int or float, but we can use the wrapper classes Integer etc.
public class gClass<T>
{
private T data;
public void setData(T newData){
data = newData;
}
public T getData() {
return data;
}
}
A sample test program for this class would be:
public class testgClass{
public static void main(String[] args) {
gClass<String> name = new gClass<String>();
gClass<Integer> anInt = new gClass<Integer>();
name.setData("Camille Hayhurst");
System.out.println("the contents of name is " +
name.getData());
anInt.setData(55);
System.out.println("The value of anInt minus 20 is
(anInt.getData() -20));
}
}
" +
Interfaces
Interfaces in Java, are NOT classes. If you go to the Java API, they appear in the
“classes” window in italics. Since they are not classes, they can not be used to declare
variables and we can not create instances of them. They exist to enhance interoperability
between the existing classes in the Java API, and user defined ADTs. They are
templates, that specify behaviors in terms of methods to implement in user defined
ADTs. These behaviors help user defined classes to work with the classes in the Java
API. A commonly used interface is the “Comparable” interface. This interface specifies
a “compareTo” method that a class can implement so that it can work with any API class
that orders objects according to their natural ordering. For example classes String,
Integer, Float, and Character all implement the Comparable interface, and implement a
compareTo method. You are already familiar with the compareTo method for class
String.
Assuming that there exists two strings wordA and wordB
wordA.compareTo(wordB)
returns 0 if they contain the same string, a value <0 if wordA is lexicographically less
than wordB, and a value >0 if wordA is greater than wordB.
Access the java API, and open the Comparable
interface
This interfaces specifies the heading for a compareTo method in the following form
public int compareTo( Object B)
The type of the parameter must be Object. Assume that the following class exists:
To indicate that YOUR class adheres to the behaviors specified in an interface, you add
the keyword implements to the first line in your class, and then name the interface(s) you
will implement. Then in the body of your class you must include a method for each
specified in the interface.
public class MyInteger implements java.lang.Comparable{
private int number;
public MyInteger( int value){
number = value;
}// end constructor
}
To implement a compareTo method for this class I would add the following code:
public int compareTo( Object B) {
// to compare two objects, they must be of the same type, so we must cast the
// parameter to the actual type needed, which is the type of the class
MyInteger objB = (MyInteger) B;
if ( this.number == objB.number) return 0;
else if (this.number > objB.number) return 1;
else return -1;
}// end compareTo
The following is a main program that uses class MyInteger:
public class testMyInteger{
public static void main(String[] args){
MyInteger X = new MyInteger(5);
MyInteger Y = new MyInteger(5);
MyInteger Z = new MyInteger(9);
System.out.println("X.compareTo(Y) equals " + X.compareTo(Y));
System.out.println("Y.compareTo(Z) equals "+ Y.compareTo(Z));
System.out.println("Z.compareTo(X) equals " + Z.compareTo(X));
}
}// end class
Displays the output:
X.compareTo(Y) equals 0
Y.compareTo(Z) equals -1
Z.compareTo(X) equals 1
But this still doesn’t solve our problem, because class Object does not implement the
Comparable interface. SOOOO.. Instead of using Object, we can specify “Comparable”
as the data type of our elements. This will ENFORCE that any object inserted into our
list by the client program MUST be of a class type that implements the Comparable
interface.
Download