|
CSCI A201/A597 and I210
Lecture Notes Twenty-Two
Second semester 2000-2001
|
Inheritance and the class extension mechanism.
|
You don't realize it, but you're constantly enjoying the benefits of science.
|
For example, when you turn on the the radio, you take it for granted that music will come out;
|
|
But do you ever stop to think that this miracle would not be possible without the work of scientists?
|
That's right: there are tiny scientists inside that radio, playing instruments.
|
|
A similar principle is used in automatic bank-teller machines, which is why they frequently say:
"Sorry, out of service."
|
They're too embarassed to say: "Sorry, tiny scientist going to the bathroom."
|
Speaking of banks,
let's use the BankAccount class to study
|
... the concept of inheritance (in Java).
|
|
Inheritance is a mechanism for enhancing existing, working classes.
|
If you need to implement a new class,
|
|
... and a class representing a more general concept is already available,
|
.. then the new class can inherit from the existing class.
|
For example, suppose you need to define a class SavingsAccount
|
... to model an account that pays a fixed interest rate on deposits.
|
You already have a class BankAccount
|
... and a savings account is a special case of a bank account.
|
|
Ask any tiny scientist!
|
So, in this case, it makes sense to use the language construct of inheritance.
|
|
Here is the syntax for the class definition:
| |
class SavingsAccount extends BankAccount {
<new methods>
<new instance variables>
}
| |
And the set union of features kicks in.
|
|
Exactly.
|
In the SavingsAccount class definition you specify only new methods
and instance variables.
|
All methods and instance variables of the BankAccount class
|
... are automatically inherited
by the SavingsAccount class.
|
|
The more general class that forms the basis for inheritance is called the superclass.
|
That would be BankAccount.
|
|
The more specialized class that inherits from the superclass is called the subclass.
|
Here this is SavingsAccount.
|
|
In Java, every class that does not specifically extend another class,
|
... extends the class Object.
|
The Object class has a small number of methods that make sense for all objects,
|
.. such as the toString method that you can use to obtain a string that describes the state
of an object.
|
|
One important reason for inheritance is code reuse.
|
By inheriting from an existing class, you do not have to replicate
the effort that went into designing and perfecting that class.
|
For example, when implementing the SavingsAccount class, you can rely
on the withdraw, deposit and getBalance methods
of the BankAccount class without touching them.
|
Let us see how our savings account objects are different from BankAccount objects.
|
|
We will set an interest rate in the constructor,
|
... and then we need a method to apply that interest periodically.
|
|
That is, in addition to the three methods that can be applied to every account,
|
... there is an additional method, addInterest.
|
|
These new methods and instance variables must be defined in the subclass.
|
Here's the definition:
|
public class SavingsAccount extends BankAccount {
double interestRate;
public SavingsAccount (double rate) {
this.interestRate = rate;
}
public void addInterest() {
double interest;
interest = this.getBalance() * this.interestRate / 100;
this.deposit(interest);
}
}
Given this definition, what is the structure of a SavingsAccount object?
|
It inherits the balance instance variable from the BankAccount superclass,
and it gains one additional instance variable:
|
... interestRate.
|
Exactly.
|
Next we need to implement the new addInterest method.
|
We have in fact, but we pretend not to, just to discuss it.
|
|
This method computes the interest due on the current balance,
|
... and deposits that interest to the account.
|
Note how the addInterest method calls the
getBalance and deposit methods
of the superclass.
|
Let's draw a picture to illustrate what each type of object has and why:
|

The class SavingsAccount extends the class BankAccount.
|
In other words, a SavingsAccount object is a special case of a BankAccount object.
|
|
A special case has more features.
|
When I define a variable collegeFund of type SavingsAccount
how do you anticipate using it?
|
|
Here are all possible uses:
|
Very good.
|
collegeFund.deposit(___);
collegeFund.withdraw(___);
collegeFund.getBalance();
collegeFund.addInterest();
When I define a variable anAccount of type BankAccount how do you
anticipate using it?
|
|
|
|
Here are all possible uses:
|
anAccount.deposit(___);
anAccount.withdraw(___);
anAccount.getBalance();
|
Very good.
|
Can you then store a reference to a SavingsAccount object into an object variable of type BankAccount?
|
BankAccount b = new SavingsAccount(10);
You would never utilize b fully, but perhaps you don't need that.
|
So the answer is: yes. You can store the reference to a SavingsAccount
object into an object variable of type BankAccount.
|
Indeed, because you just say: "I will not need the extra features
that the class SavingsAccount is defining,
|
... I will just attempt to work with the features defined in BankAccount
-- that's all I need in this particular case".
|
|
Can you do the opposite?
|
You mean this?
|
SavingsAccount a = new BankAccount(___);
|
Why?
|
Well, what's the intended use of a?
|
|
I see, if I try to compute the added interest the object won't
have an adequate instance variable, nor the capability to do that.
|
a.addInterest()
does not make sense.
|
|
So we can't let that happen.
|
And the compiler will complain.
|
|
Why doesn't it complain in the previous situation?
|
Because in that situation we are only giving up on some amenities,
|
| ... which is fine with the
compiler for as long as it's fine with us,
|
... whereas here we might ask for the impossible,
|
|
.. which the compiler can't accept,
|
... even if it's fine with us.
|
a.addInterest()
is what it wants to guard as against.
|
So, going back to our original example,
|
SavingsAccount collegeFund = new SavingsAccount(10);
BankAccount anAccount = collegeFund;
Object anObject = collegeFund;
Now the three object references stored in collegeFund,
anAccount, and anObject,
|
... all refer to the same object, of type SavingsAccount.
|
However, the object variable anAccount knows
less than the full story aboutm the object to which it refers.
|
Because anAccount is an object of type BankAccount, you
can use the deposit and withdraw methods to change the balance
of the savings account.
|
You cannot use the addInterest method, though -- it is not a method of the
BankAccount superclass.
|
And, of course, the variable anObject knows even less.
|
You can't even apply the deposit method to it.
|
deposit is not a method of the Object class.
|
|
Why would anyone want to know less about an object
and store a reference to it in a variable of the superclass's type?
|
For generality and uniformity.
|
|
Have any example?
|
I have two of them.
|
|
Let's see the first one.
|
Consider the transfer method which transfers money from one account into another.
|
void transfer (BankAccount other, double amount) {
this.withdraw(amount);
other.deposit(amount);
}
| |
You can use this method to transfer money from one BankAccount to another,
|
... and you can also use the method to transfer money
into a SavingsAccount.
|
The transfer method expects a reference to a BankAccount, which it will
use to deposit.
|
Any SavingsAccount object can do that too, so it can be passed as the first explicit argument
to transfer.
|
The transfer method doesn't actually know that, in this case, other refers to a
SavingsAccount.
|
It knows only that other is a BankAccount,
|
... that is, that it can
-
deposit
-
withdraw, and
-
getBalance
|
|
... and it doesn't need to know anything else.
|
Precisely.
|
|
What's the second example?
|
It involves arrays, but we need to discuss inheritance hierarchies first.
|
|
Very good, let's do that.
|
Occasionally, it happens that you convert an object to a superclass
reference and you need to convert it back.
|
Suppose you captured a reference to a savings account in a variable of type Object:
|
A variable reference is like a pair of binoculars.
|
Object myObj = new SavingsAccount(10);
|
Or a pair of blinkers
|
... of the type that's used on skittish racehorses.
|
|
If you put it on,
|
... you can only see what it lets you see.
|
|
Much later, if you want to add interest or deposit to the account,
|
... you can do that, with care.
|
|
The object still has all the features,
|
... you just need to put the right pair of binoculars on to see them.
|
|
That's called casting.
|
As long as you are absolutely sure that myObj
really refers to a SavingsAccount object,
|
|
... you can use the cast notation to convert it back:
| What if you're sure but wrong? |
SavingsAccount x = (SavingsAccount)myObj;
|
If you are wrong, and the object doesn't actually refer to a savings account, your program will throw an
exception, and terminate.
|
You saw one example of using casts in graphics programs, when we had to
cast the Graphics object to a Graphics2D object.
|
|
In real world, we often categorize concepts into hierarchies.
|
Hierarchies are frequently represented as trees, with the most general concepts
at the root of the hierarchy,
|
|
... and the more specialized ones towards the branches.
|
Let's see an example in Java.
|
Suppose that we have more than just one extension to BankAccount.
|
Consider a CheckingAccount class that describes accounts with no interest,
|
|
... gives you a small number of free transactions per month,
|
... and charges a transaction fee for each additional transaction.
|
|
All accounts have something in common.
|
They are all bank accounts with a balance and the ability to deposit money,
|
|
... and (within limits) to withdraw money.
|
This leads us to the following inheritance hierarchy:
|

|
|
Now suppose that you have 100 bank account objects, and half of them are checking accounts and the
other half are
|
|
... savings accounts.
|
Can you keep them all in an array?
|
|
Only if the array is declared as having an interest in only their most general, common features.
|
|
BankAccount[] a = new BankAccount[100];
This strategy, in its most general form, is used by the Vector class.
|
Which makes use of arrays of Objects.
|
To store something in a Vector it must be of type Object.
|
So you can't store an int?
|
|
Not directly.
|
But you can store a Rectangle
|
If you do, it will get stored as an Object.
|
And when you retrieve it,
|
Vector v = new Vector();
v.addElement(new Rectangle(_,_,_,_));
(v.elementAt(0)).translate(_,_);
| |
... it comes back as an Object not as a Rectangle.
|
Whatever you want to do with it as a Rectangle,
|
... you need to cast it to a Rectangle from
the Object that it is.
|
Not casting will give you an error first time you try to use it as a Rectangle.
|
In most situations of this kind, though, it is better to play it safe and test whether a cast will succeed,
|
|
... before carrying out the cast.
|
For that purpose you use the instanceof operator.
|
|
That's right, it tests whether an object belongs to a particular class.
|
For example, when retrieving one of our accounts from the array we could
take the appropriate type of action depending on the type of account:
|
if (anObject instanceOf SavingsAccount) {
// ... do savings account type of work
} else if (anObject instanceOf CheckingAccount) {
// ... do checking account type of work
} else {
// ... in which case the tiny scientist reports an error
}
Last updated: Mar 9, 2001 by Adrian German for A201