95.105/145 - Introduction to Programming |
Fall 2001
|
9 Inheritance, Type-Casting and Interfaces |
9.1 Class Hierarchies and Inheritance |
A subclass (child) inherits this from all of its superclasses (ancestors):
Inheritance:
Here, Snake, and Lizard are subclasses of Reptile (which merely means that they are specializations). Also Whale and Dog are subclasses of Mammal. All of the classes are subclasses of Animal (except Animal itself). Animal is a superclass of all the classes below it, and Mammal is a superclass of Whale and Dog. As we can see, we can go even deeper in the hierarchy by creating subclasses of Lizard.
A subclass:
We often make subclasses of our own objects:
Here are a few more hierarchies:
Be careful not to use a “has a” relationship !!!
To create a subclass of another class, use the extends keyword
in the class definition.
public class A extends
B
{
... } |
If the extends keyword is not used, it is assumed that the class extends Object.
How do we know how deep to make the tree ? Most of the time, any “is a” relationship should certainly result in subclassing. OO code usually involves a lot of small classes as opposed to a few large ones. Often, the code (i.e., hierarchies) ends up being rearranged over time. So, we often make mistakes the first time around :).
It is not always easy to chose a hierarchy ... it depends on the application.
Students in a university (may be represented many different ways...here are just 3 possibilities):
How do we know which one to use ? It will depend on the state and behaviour. If we find that the main differences in behaviour are between full time and part time students, then we may choose the top hierarchy (e.g., fee payments). If however the main differences are between grad/undergrad, (e.g., privledges, requirements, exam styles etc..) then we may choose the middle hierarchy. The bottom hierarchy further distinguishes between full/part time grads/undergrads. So...the answer is...we often don't know which hierahcy to pick until after we get started with the code :).
Consider making an object to represent Employees in a company which maintains: name, address, phoneNumber and employeeNumber
We may make a single class for this:
Now what if we want to distinguish between regular Employees and Managers ?
We must think of what is different between these classes with respect to state and behaviour.
Managers may have
What if we wanted to represent Customers as well ? Customers probably have state like this: name, address and phoneNumber. This state information is also used by Employees
We could just make Employee objects and set their employeeNumber
to something like -1 to indicate that it is not an Employee,
but in fact a Customer.
Employee jim = new Employee(“Jim”, “13 Elm St.”, “555-2394”, 762389);That’s a HORRIBLE idea! It actually undoes the whole concept of OOP !
Employee bill = new Employee(“Jim”, “13 Elm St.”, “555-2394”, -1);
We MUST have a
Perhaps we can name the Customer class Person. So, Customers are just Person objects:
This is a good solution as long as:
So in the end, the state stored in each class is:
9.2 Abstraction: Abstract Classes & Methods |
An Abstract Class:
public abstract class
BankAccount {
} |
This class must have subclasses in order to be useful.
How do we know which classes to make abstract ?
Maybe later we need to add more shapes. What about a Cube or a Sphere or even a Tetrahedron ? Well, we can merely add it as a subclass of Shape, but maybe we need to make a distinction between 2D and 3D objects. We may want to abstract out even more (this is called abstraction):
Perhaps we need to abstract out more later with our objects as we add more shapes. We may end up with something like this:
Abstraction is the process we have just been applying to our hierarchy. It allows us to extract the common features out of a class's subclasses. For instance, a polygon may keep a bunch of vertices and line segments. All Triangles, Rectangles and Squares will make use of these features and perhaps share the same drawing method.
Recall the Person/Employee/Manager example:
If the only people being used in this application are Customers, Employees and Managers, then we can make the Person class abstract.
If however, we had used the hierarchy in which Customers were just Person objects, then we CANNOT make the Person class abstract since Person objects represent Customers.
For BankAccounts, we may wish to make BankAccount, SavingsAccount and CheckingAccount all be abstract:
This forces the users of these classes to specify the most specific
type of bank account required.
Abstract Methods:
In addition to having abstract classes, Java allows us to make abstract methods. Only abstract classes can have abstract methods. However, an abstract class DOES NOT have to have only abstract methods.
All subclasses inherit all of the visible methods from their superclasses, even if the superclasses are abstract, so inheritance works the same.
An abstract method is a method with no code. It is
defined using this format:
public abstract <returnType> <methodName>(...); |
Notice the ; used at the end of the definition ... there are no { } characters.
Abstract methods have no code !!!! Why would we want a method that has no code ?
Note that abstract classes SavingsAccount and ChequingAccount do NOT NEED TO implement the abstract methods in BankAccount.
Alternatively, we could write non-abstract methods deposit(float amount) and withdraw(float amount) in the BankAccount class and have the subclasses either use or override them.
Abstract classes are often used to specify some “standard” behaviour for use by its subclasses.
E.g. we can make a Loan class abstract and then specify behaviour that all Loan objects should have:
public abstract class Loan {Note that all concrete subclasses of Loan MUST implement the 3 abstract methods.
public abstract float calculateMonthlyPayment(); //abstract method
public abstract void makePayment(float amount); //abstract method
public abstract void renew(); //abstract method
public Client getClientInfo() { //non-abstract method
...
}
....
}
Here's another example. Consider the Animal class as shown at the top of these notes as being an abstract class with some abstract methods and some non-abstract methods as follows:
public abstract class Animal {
public void eat(Food someFood)
{
putInMouth(someFood);
chew(someFood);
swallow(someFood);
}
public abstract
void
putInMouth(Food someFood);
public abstract
void
chew(Food
someFood);
public abstract
void
swallow(Food
someFood);
public void sleep(int minutes)
{
....
}
public abstract
void
poop();
...
}
In this code, the putInMouth(), chew(), swallow() and poop() methods are all declared as abstract. Note that the code is not given. By doing this, we are FORCING ALL subclasses of Animal to implement these methods. The compiler will complain if the methods are not implemented.
That means, the Reptile and Mammal classes must implement these methods. Note that some methods (such as eat()) may not be abstract.
So by writing an abstract method, we force all subclasses to adhere to some "standard". As we can see, the eat method requires the putInMouth(), chew() and swallow() methods to be written in the subclasses. That is, we provide a guarantee that the subclasses will implement all of the required methods.
9.3 Method Lookup and Overriding |
Since Object is at the top of the hierarchy, every class inherits
from Object.
What kind of neat behaviours do we inherit from Object ?
toString(), equals(), hashcode(), clone()
To cause inheritance in our own classes, we merely make our class to be a subclass of the class we want to inherit from.
Consider the inheritance in this example:
Employee inherits name and getName() from Person.
public class Person {
public String name;
public String getName(){
return name; }
}
public class Employee extends Person {
public int employeeNumber;
public int getEmployeeNumber(){
return employeeNumber; }
}
public class Manager extends Employee {
public Vector responsibilities;
public Vector getResponsibilities(){
return responsibilities; }
}
Both Employee and Manager objects can use the fields and methods of their superclass as if it was defined in their own class:
Employee jim = new Employee();A subclass cannot, however, access private methods or private fields from its superclasses:
jim.name = “Jim”;
jim.employeeNumber = 123456;
System.out.println(jim.getName());Manager betty = new Manager();
betty.name = “Betty”;
betty.employeeNumber = 543469;
betty.responsibilites.add(“Internet project”);
System.out.println(betty.getName());
System.out.println(betty.getEmployeeNumber());
public class B {
public int a = 10;
private int b = 20;
protected int c = 30;
public int getB() { return b; }
}
public class A extends B {
public int d;
public void tryVariables() {
System.out.println(a); //access allowed
System.out.println(b); //access NOT allowed
System.out.println(getB()); //can get private variable value through this public method
System.out.println(c); //access allowed
}
}
Note:
|
Overriding:
Through inheritance, a class inherits all the methods of its superclasses. That means, a subclass can use any methods of its superclasses as if they were its own. It may be the case that a subclass does not want to inherit some methods from its super class. If this is the case, then the subclass can choose to ignore the superclass method by making its own method with the same name (and same parameter list) that does something else (perhaps nothing).
A method in a class is overridden by one of its subclasses if the subclass implements the method using the exact same signature.
Overriding is used when:
(e.g., assume that the SuperSavings account cannot be withdrawn from)
- Write withdraw(float amount) in BankAccount class as usual.
- Don’t write withdraw(float amount) in SavingsAccount
- allow it to be inherited from BankAccount
- Override the method in SuperSavingsAccount:
public void withdraw(float amount) {
// Do Nothing
}
(e.g., Assume that withdraws from a checking account have a
surcharge fee of $0.75).
|
If a method is declared as final, it CANNOT be overridden, the compiler will stop you:
public final void withdraw(float amount) {Why would we want to do this ? Well...perhaps the behaviour defined in the method is very critical and overriding this behaviour improperly may cause problems with the rest of the program.
...
}
If a class is declared as final, it CANNOT have subclasses:
public final class Manager {Why would we want to do this ? Well...perhaps the class has very weird code that the author does not want you to inherit...maybe because it is too complicated and may easily be miss-used. Many of the java classes (e.g., Vector) are declared final which means that we cannot make a subclass of them....its a kind of security issue to prevent us from "messing up" the design and intentional usage of those classes.
...
}
If a method is declared as protected, it can be accessed, from this class or its subclasses:
protected void withdraw(float amount) {
...
}
Method Look-Up:
When a message (method) is sent to an instance, Java must look up the method in the class hierarchy and then evaluate it. It is important to understand how Java “looks up” methods when we send messages to objects. Assume that we send a message M() to an instance of class D:
Method lookup for all instance methods works as follows:
If not found at all during this search up to the Object class, the compiler will catch this and inform you that it can't find the method M() for the object you are trying to sending it to:
The super keyword is also used in constructors to call a constructor in the superclass. Java automatically calls the superclass’ default constructor if you do not specify a call yourself. If you do want to call a superclass constructor, it MUST be the first line of your constructor code.
public BankAccount(String own, float bal) {Java does lookup for static methods differently
owner = own;
balance = bal;
}
public SavingsAccount(String own){
super(own, 0.0f); //Calls above constructor, otherwise the default
//constructor in BankAccount would have been called.}
If we would like to implement a university system we might come up with
the following hierarchy showing the different types of people:
Person
EmployeeProfessorStudent |
(name, phone#,
address)
(employee#, workPhone#) (courses,
office #)
(student#, courses, gpa) (major)
|
From this example we can see that there are different specializations of people but each of these special people share some common attributes (name, phone# and address). This does not mean that they all have the same name, address or phone#; it merely means that they all have this type of information associated with them. A specialization of the Person object is the Employee class in which all employees have information pertaining to their employee number and work telephone number. Because the employees are specializations of people, Employee is made to be a subclass of Person. This allows Employee instances to inherit the properties (attributes) of Person objects. The same argument applies to all the other subclasses above.
Thus, we only need to make get and set methods for name, phone# and address in the Person class. All other subclasses will inherit these methods which means that they will all automatically understand the messages getName(), setName(), getPhoneNumber(), setPhoneNumber() etc... This is very useful since it saves us from having to repeatedly type duplicate methods.
In our university example above, we would have declarations (each in a separate file of course) that look something like this:
"Person
NAME: Jim Class
ADDRESS: 1445 Porter St.
PHONE #: 845-3232"
"Employee
NAME: Rob Banks
ADDRESS: 789 ScotiaBank Road.
PHONE #: 899-2332
EMPLOYEE #: 88765
WORK #: 555-2433"
"Professor
NAME: Guy Smart
EMPLOYEE #: 65445
WORK #: 232-3415
OFFICE #: 5240 PA"
"Secretary
NAME: Earl E. Bird
ADDRESS: 12 Knowhere Cres.
PHONE #: 443-7854
EMPLOYEE #: 76845
WORK #: 444-3243"
"Student
NAME: May I. Passplease
ADDRESS: 5567 Java Drive
PHONE #: 732-8923
STUDENT #: 156753"
Also note that not all information is shown using this method. For example, the courses were not displayed for Professors or Students since there may be too many and a toString() method should never return a big string. We can clearly write a toString() method for each class that does what we want. However, this would be wasteful. Let's try to save ourselves from writing a lot of code. We'll try to "share" code among classes by making use of inheritance and the super keyword.
First, we notice that all classes show the name. This should definitely be done in the Person class since all others are subclasses and can inherit the code. Next, we see that all classes (except Professor) also show the phone number and address. I guess Professors do not want to show everybody this information. Thus, we'll allow all subclasses to display this information and we'll have to do something different for the Professor class. Lets write the toString() method for the Person class:
Therefore, we can do this one without inheritance by completely overriding the Employee method:
We know that sometimes (i.e., for professors) we don't want to see the address and phone number, whereas other times (i.e., for employees and persons) we do need to see it. We can make a boolean version of the Person class's toString() method as follows:
public String toString(boolean showAddresses) {But wait! This is no longer overriding the toString() method in Object, so printing out a Person does not work properly anymore! We can fix this by having the toString() method call this one:
String s = getClass().getName() + "\n" +
" NAME: " + getName() + "\n";
if (showAddresses)
s += " ADDRESS: " + getAddress() + "\n" +
" PHONE #: " + getPhoneNumer());
return s;
}
public String toString() {Now, we can have the Employee toString() method call the boolean version in Person with true and professors call it with false:
return toString(true);
}
//This is the toString method for the Employee classThe Professor method calls the toString(boolean) method all the way up in Person. In fact, java looks for it in the Employee class, does not find it and then looks in Person....and there it finds it. The code works, but we see that there is duplication in the Employee and Professor methods. Can we fix this ? Sure! Just have another boolean version of the toString() method in the Employee class:
public String toString() {
return(super.toString(true) + "\n" +
" EMPLOYEE #: " + getEmployeeNumber() + "\n" +
" WORK #: " + getWorkNumer());
}//This is the toString method for the Professor class
public String toString() {
return(super.toString(false) + "\n" +
" EMPLOYEE #: " + getEmployeeNumber() + "\n" +
" WORK #: " + getWorkNumer() + "\n" +
" OFFICE# : " + getOfficeNumber() + "\n");
}
//This is the toString method for the Employee class
public String toString(boolean showAddresses) {
return(super.toString(showAddresses) + "\n" +
" EMPLOYEE #: " + getEmployeeNumber() + "\n" +
" WORK #: " + getWorkNumer());
}
//This is the toString method for the Employee classNote that the Professor class does not have a special toString(boolean) method and so we don't need to write super since java will automatically go and look in the superclass when it does not find this method:
public String toString() {
return(toString(true));
}//This is the toString method for the Professor class
public String toString() {
return(super.toString(false) + "\n" +
" OFFICE# : " + getOfficeNumber());
}
//This is the toString method for the Professor classNow the Student class merely inherits from Person and also displays the student number.
public String toString() {
return(toString(false) + "\n" +
" OFFICE# : " + getOfficeNumber());
}
public Person() {
public class Employee extends Person {
public Employee() {
public class Professor extends Employee {
public Professor() {
9.4 Type-Casting, Polymorphism and Double-Dispatching |
Many classes in Java make use of automatic type-casting, so we must understand:
(int)871.34354; // results in 871For objects, type casting does not convert, but merely causes the object to be “treated” more generally.
(char)65; // results in ‘A’
(long)453; // results in 453L
Consider the following hierarchy:
If we are given one of these shapes, we may wish to draw it or perhaps ask how many sides it has. However, we may not know exactly what shape is given to us, so we would have to make a check. Perhaps a bunch of if statements could be used:
aString = aShape.getClass().getName();or even better:
if (aString.equals(“Circle”))
aShape.drawCircle();
if (aString.equals(“Triangle”))
aShape.drawTriangle();
if (aString.equals(“Rectangle”))
aShape.drawRectangle();
if (aShape instanceof Circle)However, looking at the code, it is clear that all we want to do is draw the shape. The shape must be drawn differently according to the type of shape it actually is. In the code above, we have three different methods for drawing the shape and we are calling the appropriate one according to the type of shape.
aShape.drawCircle();
if (aShape instanceof Triangle)
aShape.drawTriangle();
if (aShape instanceof Rectangle)
aShape.drawRectangle();
There is a better way to write the code. We could have a draw() method for each of the three shape classes. Having the same method name (and signature) for different methods of multiple classes is called Polymorphism.
Why do we want this ? Well, replace the method names in the code:
There are many advantages to using polymorphism:
There are certain rules for type-casting objects. Objects may ONLY be type-casted to:
Managers may be type-casted to Employee, Person or Object but not to Customer, Company or Car. Some of these restrictions make sense, after all, why would we treat an Manager as a Company or a Car ?
Manager man;Explicit type-casting is not always required. An object may be automatically type-casted when it:
Employee emp;
man = new Manager();
man.getName();
man.getEmployeeNumber();
man.getSalary();
emp = (Employee)man; // emp is actually pointing to a Manager object
emp.getName(); //Can use this methods since it is also defined in Person
emp.getEmployeeNumber(); //Can use this methods since it is also defined for Employee
emp.getSalary(); // emp cannot respond to Manager methods anymore
Manager man = new Manager();
Employee emp;
emp = man; // automatic ... ame as saying emp = (Employee)man;is sent as a parameter to a method
REMEMBER: Java ALWAYS knows the “original” type of every object...and this NEVER changes.Manager man = new Manager();
aCompany.doStandardHiringProcess(man); //Passed in as a Manager objectpublic void doStandardHiringProcess(Employee emp) {
... //emp is type-casted to Employee upon entering this method.}
IMPORTANT: You are only allowed to use methods which are defined in the class being type-casted to. However, when methods are called, lookup begins in the original class. |
Here is another example:
Professor prof;Note that we have type-casted the Professor to be an Employee object. From that point onwards, we may only send Employee methods to the object. Make sure to keep in mind that type-casting does NOT do any kind of conversion. In fact, the anEmployee variable in the above example actually points to a Professor object. That is, the type casting does not change the fact that the object is a Professor object. The type-casting process for objects merely informs the compiler that from now on, the object is to be treated as an Employee. In fact, we can always type-cast back again:
Employee anEmployee;prof = new Professor();
anEmployee = (Employee)prof; //From now on, treat the Professor as an EmployeeanEmployee.computeSalary(); //This will work
anEmployee.hasTenure(); //This won't work since hasTenure() is only defined for Professors
Professor aProf;So...an object may be type-casted back again at any time. Look again at the Manager example:
aProf = (Professor)anEmployee; //From now on, treat the object again as a professoraProf.computeSalary(); //This will still work
aProf.hasTenure(); //Works now
Manager man = new Manager();Type-casting may occur up and down the hierarchy along the path from the original type of the object to the Object class:
Employee emp = (Employee)man;
emp.getName();
emp.getEmployeeNumber();
((Manager)emp).getSalary();
How does method lookup occur for type-caseted objects ?
For instance methods, method lookup ALWAYS begins
in the class of the original object. Lookup then proceeds as normal up the hierarchy. Manager man = new Manager(); |
|
For static methods, lookup is done at compile time,
and so
Java always begins in the class of the defined variable. Manager man = new Manager(); |
Many Java methods work with arbitrary objects: e.g., Collections can hold any kind of objects
Many of these methods take Objects as parameters:
public boolean equals(Object obj) {...}In these cases, any object can be passed into the method and the object is automatically type-casted to type Object. Thus, inside the method, only messages that Object understands can be sent to the incoming obj.
public void add(Object obj) {...}
As we have already seen, type-casting is also necessary when extracting items from a Collection:
Vector employees = getEmployees();We must type-cast here because both of these are true:
for (int i=0; i<employees.size(); i++) {
Employee emp = (Employee)employees.get(i);
emp.doSomething();
}
public Class getClass();Every object inherits these methods from object. The last 4 of these are often overridden.
public String toString();
public boolean equals(Object obj);
public void clone();
public int hashCode();
Double-Dispatching:
Lets look back again at our shape-drawing example. Consider now a Pen object which is capable of drawing shapes. We would like to use code that looks something like this:
aPen.draw(aCircle);However, this is not so straight forward. We would have to define a draw method in the Pen class for each kind of shape in order to satisfy the compiler with regards to the type of the parameter:
aPen.draw(aTriangle);
aPen.draw(aRectangle);
public void draw(Circle aCircle) {Well, since the drawing code is likely different for all 3 shapes...then don't we need 3 different methods anyway ? YES. However, the way the code is arranged, we have to write all the draw methods in the Pen class. So, if we later want to add some more Shapes (such as polygons, ellipses, parallelograms etc...), we would have to go into the Pen class and add a draw method for each Shape. This is BAD...because the Pen class is highly dependent on the different kinds of Shape objects. We would like a way of separating the Pen class completely, so that we don;t have to come back to it later and add more draw methods.
// Do the drawing
}
public void draw(Triangle aTriangle) {
// Do the drawing
}
public void draw(Rectangle aRectangle) {
// Do the drawing
}
Well, in fact, we can indeed declare a single method as long as all the Shapes inherit from a common superclass (or implement a common interface as we'll see later). We are lucky here since all of our shapes are subclasses of the Shape class. So, we can write one method that takes a Shape parameter:
public void draw(Shape anyShape) {This is actually a VERY powerful concept. It prevents us from having to write a method for every kind of parameter object!! Now we simply write one draw method that handles all shapes :).
// Do the drawing
}
Wait a minute!!! We still have a minor problem though. What does the code look like inside the method ?
public void draw(Shape anyShape) {That is, we seem to still have to decide how to draw the different Shapes. So then when new Shapes are added, we still need to come into the Pen class and make changes :(. We can correct this problem by shifting the drawing responsibility to the Shapes themselves, as opposed to being a Pen responsibility. This "shifting" (or flipping) of responsibility is called double dispatching (the name implies that a message is "dispatched" twice in order to be accomplished)if (anyShape instanceof Circle)}
// Do the drawing for circles
if (anyShape instanceof Triangle)
// Do the drawing for triangles
if (anyShape instanceof Rectangle)
// Do the drawing for rectangles
We perform double-dispatching by making a method in each of the specific Shape classes that allows the shape to draw itself using a given Pen object:
//This method should be in the Circle classNow, we reduce the code in the Pen class to the following:
public void drawWith(Pen aPen) {
// Do the drawing with the given pen
}
//This method should be in the Triangle class
public void drawWith(Pen aPen) {
// Do the drawing with the given pen
}
//This method should be in the Rectangle class
public void drawWith(Pen aPen) {
// Do the drawing with the given pen
}
public void draw(Shape anyShape) {So ... when we ask the Pen to draw the Shape, it kinda says: "No way! let the shape draw itself using me". It represents a notion similar to the phrase "passing the buck". This is a very common technique which is used in OO programming and it helps to reduce/simplify the code. Polymorphism makes it all work.
anyShape.drawWith(this);
}
NOTE: In order for this to compile, you must have a drawWith(Pen p) method declared in class Shape since it is declared as abstract.
Here is a more in-depth example of inheritance with type-casting.
Consider the following classes:
public class Person
{
static int lifeSpan
= 60;
public Person() {
public Person(String aName) {
public String getName() { return name; } public void setName(String aName) { name = aName; } public String toString() {
public String talk() {
public String walk() {
public static double lifeSpan() {
|
|
Now try to determine the output of the following code:
aPerson = new Person("Fred");
jimmy = new Boy();
betty = new Girl("Betty");
System.out.println(aPerson);
System.out.println(aPerson.talk());
System.out.println(aPerson.walk());
System.out.println();
System.out.println(jimmy);
System.out.println(jimmy.talk());
System.out.println(jimmy.walk());
System.out.println();
System.out.println(betty);
System.out.println(betty.talk());
System.out.println(betty.walk());
System.out.println();
System.out.println((Person)jimmy);
System.out.println(((Person)jimmy).talk());
System.out.println(((Person)jimmy).walk());
System.out.println();
System.out.println((Person)betty);
System.out.println(((Person)betty).talk());
System.out.println(((Person)betty).walk());
System.out.println();
System.out.println(Person.lifeSpan());
System.out.println(Boy.lifeSpan());
System.out.println(Girl.lifeSpan());
System.out.println(((Boy)aPerson).talk());
Hello, my name is Fred
I have nothing to say. I have nowhere to go. Hello, my name is
Hello, my name is Ms.Betty
Hello, my name is
Hello, my name is Ms.Betty
60.0
java.lang.ClassCastException: Person
|
Hey! The lifespan() method did not work the way that we
expected. That's because for class methods, method look-up occurs
at compile time. The lifeSpan() method in the Person
class is used by both the Boy and Person classes.
In this case, since the method is static and declared in the Person
class, the ageFactor from the Person class is used.
However, the Girl class has its own lifeSpan() method, so
the ageFactor within the Girl class is used in that case.
Weird eh ?
9.5 Interfaces |
An Interface represents
Interfaces are like abstract classes in that they define common
behaviour between a set of classes.
For example, an insurance company may define an Insurable interface
and have different kinds of objects implement the interface.
Defining the interface, involves determining the common behaviour for all implementors of the interface.
Unlike abstract classes however, interfaces allow us to specify common behaviour between seemingly unrelated objects:
We use the interface keyword instead of the class keyword
when defining an interface.
public interface
interfaceName
{
// Method specifications } |
Defining it is similar to defining an abstract class with no state and only abstract methods:
public interface Insurable {Once this is saved and compiled, classes may then implement and use the interface using the implements keyword in their definition:
public int getPolicyNumber();
public int getCoverageAmount();
public double calculatePremium();
public Date getExpiryDate();
}
public class className
implements
interfaceName
{
/* Bodies for the interface methods */ /* Own data and methods. */ } |
Classes that implement the interface must implement ALL of the interface methods:
public class Car implements Insurable {Classes may implement more than one interface.
public int getPolicyNumber() {
// write code here
}
public double calculatePremium() {
// write code here
}
public Date getExpiryDate() {
// write code here
}
public int getCoverageAmount() {
// write code here
}
}
To do this, just specify each in the class definition:
public class Car implements Insurable, Drivable, Sellable {Here, Car would have to implement ALL of the methods defined in each of the three interfaces.
....
}
public class Vegetable extends Food implements EdibleObject, ThrowableObject, CookableObject{Like classes, interfaces can be organized in a hierarchy.
...
}
Classes implementing an interface must implement its “super” interfaces as well.
public interface DepreciatingInsurable extendsInsurable {Objects can also be type-casted to an interface type, provided that the class implements that interface.
public double computeFairMarketValue();
}
public interface FixedInsurable extends Insurable {
public int getEvaluationPeriod();
}
Car jetta = new Car();
Insurable item = (Insurable)jetta;// We can now only send Insurable messages to item
item.getPolicyNumber();
item.calculatePremium();jetta.getMileage(); // This is OK
item.getMileage(); // This is NOT OK
((Car)item).getMileage(); // This is OK too
Consider typical operations on machines that move. The interface on such objects may look as follows:
Now, consider a Plane. It is a MovableObject and so it should implement this interface. We can implement the interface for planes as follows:
public boolean start() {
//
There will likely also be some other methods which are plane-specific
public
Date
getLastRepairDate() {
.....
Suppose we'd like to set up a handheld remote control for MovableObjects. We can then treat all of the objects (Planes, Cars, Trains, etc...) as a single type of object ... a MovableObject.
RemoteControl(MovableObject
m) {
machine = m;
}
...
//When
the start button is pressed on the remote
Boolean
okay = machine.start();
if
(!okay) display("No Response on start");
...
}
Guess what ? We can ALSO type-cast objects into their implemented interface:
Plane aPlane = new Plane();So, everything works as it did in the Shape example. Once we type-cast to an interface type, we can only send interface methods to the Plane. Although it is still indeed a plane object, we cannot send Plane-specific methods to the object anymore (unless it is type-casted back to Plane).
MovableObject m = (MovableObject)aPlane;
m.start();
m.stop();
m.getLastRepairDate(); //This won't work now
Interfaces vs. Abstract Classes:
The use of abstract methods is very much like using interfaces.
Recall the abstract methods in the Animal class:
public abstract class Animal {We could have defined the following interface:
public abstract void putInMouth(Food someFood);
public abstract void chew(Food someFood);
public abstract void swallow(Food someFood);
public abstract void poop();public void sleep(int minutes) { ... }
public void eat(Food someFood) { ... }
...
}
public interface AnimalInterface {If we make the subclasses implement this interface, then this is another way of guaranteeing that they will have the required abstract behaviour.
public void putInMouth(Food someFood);
public void chew(Food someFood);
public void swallow(Food someFood);
public void poop();
}
How are the two strategies similar ? Both provide a guarantee
that the methods will be implemented !
So which way should we do it ? Abstract classes with abstract
methods or interfaces ?
Interfaces are more flexible in that any class can implement
the behaviour wheras with abstract classes, we only get to specify
this common behaviour for classes that lie beneath Animal in the
hierarchy. Hence, the abstract class solution is more
restrictive on the classes that will get this guarantee. But
Abstract classes DO allow non-abstract methods...