|
8. Inheritance and Interfaces
To learn about inheritances
To be able to convert between supertype and subtype references
To understand how to override superclass methods
To understand the concept of polymorphism
To design and use abstract classes and interfaces
To learn about packages and Java access control
Introduction to Inheritance
Inheritance is a mechanism for enhancing existing, working class.
For example, suppose we need to define a class SavingsAccount to model an account that pays a fixed interest rate on deposits.
class SavingsAccount extends BankAccount
{ new instance variables
new methods
}
class SubclassName extends SuperclassName
{ new instance variables
new methods
}
Inheritance
The more general class that forms the basis for inheritance is called the superclass.
The more specialized class that inherits from the superclass is called the subclass.
One important reason for inhertance is code reuse.
Inheritance Diagram
Object <--- BankAccount <---- SavingsAccount
Inheritance Diagram
Which class is a superclass and which class is a subclass?
BankAccount is the superclass and SavingsAccount is the subclass.
Suppose we want to set an interest rate in the constructor for SavingsAccount class and also a method to apply that interest rate periodically.
public class SavingsAccount extends BankAccount
{ private double interestRate;
public SavingsAccont(double rate)
{ constructor implementation }
public void addInterest( )
{ method implementation }
}
public class BankAccount
{ private double balance;
public BankAccount( )
{ balance = 0; }
public BankAccount(double init )
{ balance = init; }
public void deposit(double amount)
{ balance = balance + amount; }
public void withdraw(double amount)
{ balance = balance - amount; }
public double getBalance( )
{ return balance; }
}
public class SavingsAccount extends BankAccount
{ private double interestRate; // instance variable
public SavingsAccont(double rate) // constructor
{ interestRate = rate; }
public void addInterest( ) // instance method
{ double interest = getBalance() * interestRate / 100;
deposit(interest);
}
}
// Example Code
SavingsAccount collegeFund = new SavingsAccount(10);
collegeFund.deposit(500); // OK to use superclass’s method
collegeFund.addInterest( );
Converting Between Class Types
A SavingsAccount object is a special case of a BankAccount object. So, you can store a reference to a SavingsAccount object into a BankAccount object.
SavingsAccount collegeFund = new SavingsAccount(10);
BankAccount anAccount = collegeFund;
Object anObject = collegeFund;
The three object references stored in collegeFund, anAccount, and anObject refer to the same object of type SavingsAccount.
However, the anAccount knows less than the full story about the object to which it refers
anAccount.deposit(1000); // OK anAccount.addInterest( ); // Not OK
Because anAccount is an object of type BankAccount, we cannot use the addInterest method.
anObject.deposit(1000); // Not OK
The variable anObject knows even less, because it is an object of type Object and deposit is not a method of the object class.
Why would anyone want to know less about an object and store a reference in an object variable of a superclass? Reuse.
Consider the transfer method
void transfer(BankAccount other, double amount)
{ withdraw(amount);
other.deposit(amount);
}
We can use this method to transfer money from one bank account to another:
BankAccount momsChecking = … ;
BankAccount harrysChecking = … ;
momsChecking.transfer(harrysChecking, 1000);
We can also use this method to transfer money into a SavingAccount:
SavingsAccount CollegeFund = … ;
momsChecking.transfer(collegeFund, 1000);
However, we cannot convert between unrelated classes:
Rectangle r = CollegeFund; // Error
Subclass reference can be assigned to superclass reference
Object anObject = collegeFund;
But, superclass reference must be casted to subclass reference.
SavingsAccount x = (SavingsAcount)anObject;
as long as we absolutely sure that anObject really refers to a SavingsAccount object.
To play it safe, we can use the instanceof operator to test whether an object belongs to a particular class (or one of its subclasses).
Object instanceof ClasssName
Rectangle r;
if (x instanceof Rectangle)
r = (Rectangle) x;
SavingsAccount x;
if (anObject instanceof SavingsAccount)
x = (SavingsAccount)anObject;
else
x = null;
Hierarchies
Hierarchies are frequently represented as trees, with the most general concepts at the root.
In Java, it is common to group classes in inheritance hierarchies.
Inheritance Hierarchies
The classes representing the most general concepts are near the root, more specialized classes towards the branches.
When designing a hierarchy of classes, those common properties are collected in a superclass. More specialized properties can be found in subclasses.
Consider a bank that offers its customers three kinds of accounts:
CheckingAccount
SavingsAccount
TimeDepositAccount
The checking account has no interest, gives you a small number of free transactions per month, and charges a transaction fee for each additional transactions.
The savings account compounds interest monthly.
The time deposit account has a penalty for early withdrawal.
The inheritance hierarchy for these account classes is:
protected Members
A superclass’s public members are accessible anywhere the program has a reference to that superclass type or one of its subclasses types.
A superclass’s private members are accessible only in methods of that superclasses.
A superclass’s protected members serve as an intermediate level of protection between public and private access.
protected Members
A superclass’s protected members can be accessed by methods of the superclass, by methods of subclasses and by methods of other classes in the same package (protected members have package access).
Subclass methods can normally refer to public and protected members of the superclass simply by using the member names.
Inheritance
// Definition of a class Point
public class Point {
protected int x, y;
public Point()
{ setPoint(0,0); }
public Point(int a, int b)
{ setPoint(a, b); }
public void setPoint(int a, int b)
{ x = a;
y = b;
}
public int getX()
{ return x; }
public int getY()
{ return y; }
public String toString()
{ return “[“ + x + “, “ + y + “]”; } }
// Definition of class circle
public class Circle extends Point {
protected double radius;
public Circle()
{ // implicit call to superclass constructor
setRadius(0); }
public Circle(double r, int a, int b)
{ super(a, b);
setRadius(r);
}
public void setRadius(double r)
{ radius = (r >= 0.0 ? r : 0.0); }
pubic double getRadius()
{ return radius; }
public double area()
{ return Math.PI * radius * radius; }
public String toString()
{ return “Center = “ + “[“ + x + “,” + y + “]” +
“; Radius = “ + radius;
}
}
// Main program
import java.text.DecimalFormat;
import javax.swing.JOptionPane;
public class InheritanceTest {
public static void main(String[] args)
{ Point pointRef, p;
Circle circleRef, c;
String output;
p = new Point(30,50);
c = new Circle(2.7, 120, 89);
output = “Point p: “+ p.toString() +
“\nCircle c: “ + c.toString() ;
pointRef = c;
output += “\n\nCircle c (via pointRef): “ +
pointRef.toString() ;
circleRef = (Circle) pointRef;
output += “\n\nCircle c (via circleRef): “ +
circleRef.toString() ;
DecimalFormat precision2 = new DecimalFormat(“0.00”);
output += “\n\nArea of c (via circleRef): “ +
precision2.format(circleRef.area());
if (p instanceof Circle) {
circleRef = (Circle) p;
output += “\n\ncast successful”;
}
else
output += “\n\n p does not refer to a circle”;
JOptionPane.showMessageDialog(null, output,
“Inheritance”,
JOptionPane.INFORMATION_MESSAGE);
System.exit(0);
}
}
Inheriting Instance Variables and Methods
When defining additional methods for a subclass, there are 3 options:
1. We can override methods from the superclass by specifying a method with the same signature (i.e., the same name and the same parameters).
2. We can inherit methods from the superclass. If we do not explicitly override a superclass method, we automatically inherit it.
3. We can define new methods that does not exist in the superclass.
When defining additional instance variables for a subclass, there are 2 options:
1. We can inherit variables from the superclass.
2. We can define new variables. These new variables are only presented in subclass objects.
Remember! We can never override instance variables.
Shadowing
The newly defined subclass variable shadows the superclass variable.
The superclass variable is still present, but it cannot be accessed.
When you refer to balance in a SavingAccount method, you access the new instance variable.
Shadowing instance variables is harmless.
Shadowing Instance Variables
A subclass has no access to the private instance of the superclass
public class CheckingAccount extends BankAccount
{ public void deposit(double amount)
{ transactionCount++;
balance = balance + amount; // ERROR
}
}
To solve the problem
public class CheckingAccount extends BankAccount
{ private double balance; // shadow variable
public void deposit(double amount)
{ transactionCount++;
balance = balance + amount; // OK but different balance
}
}
Example
public class BankAccount
{ private double balance;
public double getBalance() {…}
public void deposit(double amount) {…}
public void withdraw(double amount) {…}
}
public class CheckingAccount extends BankAccount
{ private int transactionCount;
public void deposit(double amount) {…}
public void withdraw(double amount) {…}
public void deductFees() {…}
}
Subclass methods have no access rights to the private data of the superclass.
If we want to modify a private superclass variable, we must use a public method of the superclass.
Can we write the deposit method as
public void deposit(double amount)
{ transactionCount++;
deposit(amount); // Is it OK?
}
If we define the method like that, it will call itself infinitely.
We need to be more specific that we want to invoke the superclass’s deposit method by using a special keyword super for this purpose:
public void deposit(double amount)
{ transactionCount++;
super.deposit(amount);
}
Calling a Superclass Methods
public class CheckingAccount extends BankAccount
{ private static final int FREE_TRANSANCTIONS = 3;
private static final double TRANSACTION_FEE = 2.0;
public void withdraw(double amount) // override method
{ transactionCount++;
super.withdraw(amount); // calling a superclass method.
// what if we call withdraw(amount);
}
public void deductFees( )
{ if (transactionCount > FREE_TRANSACTIONS)
{ double fees = TRANSACTION_FEE
* (transactionCount - FREE_TRANSACTIONS);
super.withdraw(fees);
}
}
Now, let consider the TimeDepositAccount class, a subclass of the SavingsAccount:
public class TimeDepositAccount extends SavingsAccount
{ private int periodsToMaturity;
private static final double EARLY_PENALTY= 20.0;
public void addInterest( )
{ periodsToMaturity- - ;
super.addInterest( );
}
public void withdraw(double amount)
{ if (periodsToMaturity > 0)
super.withdraw(EARLY_PENALTY);
super.withdraw(amount);
}
}
The TimeDepositAccount is two levels away from the BankAccount.
The BankAccount is a superclass, but not the immediate superclass.
The TimeDepositAccount inherits two methods from the BankAccount class, namely getBalance, and deposit.
Methods can be inherited from an indirect superclass as long as none of the intermediate superclasses overrides them.
The method call super.addInterest in addInterest method of the TimeDepositAccount refers to the addInterest method in SavingsAccount class.
The super.withdraw in the withdraw method of the TimeDepositAccount refers to the withdraw method in BankAccount.
Subclass Construction
Calling a Superclass Constructor
ClassName (parameters)
{ super(parameters);
…
}
Call the superclass constructor to set the balance to the initial balance
public class CheckingAccount extends BankAccount
{ public CheckingAccount(double init)
{ super(init);
transactionCount = 0;
}
}
The constructor call must be the first statement of the subclass constructor.
Subclass Constructor
When the keyword super is followed by a parenthesis, it indicates a call to the superclass constructor.
If super is followed by a period and a method name, it indicates a call to a superclass method.
Or, we can implement the CheckingAccount constructor without calling the superclass constructor as can be seen in the following example:
public class CheckingAccount extends BankAccount
{ public CheckingAccount(double init)
{ // use superclass’s default constructor
super.deposit(init);
transactionCount = 0;
}
}
public class TimeDepositAccount extends SavingsAccount
{ public TimeDepositAccount(double rate, int maturity )
{ super(rate);
periodsToMaturity = maturity ;
}
}
Subclass Construction
If a subclass constructor does not call the superclass constructor, the superclass is constructed with its default constructor.
If a superclass does not have a default constructor, then the complier reports an error.
For example, CheckingAccount can be implemented without calling the superclass constructor.
The BankAccount class is constructed with its default constructor, which set the balance to zero.
Then the CheckingAccount constructor must explicitly deposit the initial balance.
public CheckingAccount(double init)
{ // use superclass’s default constructor
super.deposit(init);
transactionCount = 0;
However, in the case of TimeDepositAccount, the superclass, SavingsAccount, has no default constructor.
Thus, we must call the superclass constructor explicitly.
public TimeDepositAccount(double rate, int maturity )
{ super(rate);
periodsToMaturity = maturity ;
}
Polymorphism
“is-a” relationship: Every object of the subclass is also a superclass object, but with special properties.
For example, every CheckingAccount is a BankAccount.
It is possible to use a subclass object in place of a superclass object.
What is polymorphism?
Let’s consider the transfer method that can be used to transfer money from another account:
public void transfer(BankAccount other, double amount)
{ withdraw(amount);
other.deposit(amount);
}
BankAccount collegeFund = … ;
CheckingAccount harrysChecking = … ;
collegeFund.transfer(harrysChecking, 1000);
Which deposit method?
BankAccount.deposit( ) or CheckingAccount.deposit( )
Consider the call to the deposit method, the other parameter has type BankAccount.
On the other hand, the CheckingAccount class provides its own deposit method that updates the transaction count.
Since the other variable actually refers to an object of the subclass CheckingAccount, it would be appropriate if the CheckingAccount.deposit method was called instead.
The principle that the actual type of the object determines the method to be called is called polymorphism.
In Java, method calls are always determined by the type of the actual object, not the type of the object reference.
In Java, all instance methods are polymophic.
The term “polymorphism” comes from the Greek words for “many shapes”.
Program AccountTest.java
public class AccountTest
{ public static void main(String[] args)
{ SavingsAccount momsSavings = new SavingsAccount(0.5);
TimeDepositAccount collegeFund =
new TimeDepositAccount(1, 3);
CheckingAccount harrysChekcing = new CheckingAccount(0);
momsSavings.deposit(10000);
collegeFund.deposit(10000);
momsSavings.transfer(harryschecking, 2000);
collegeFund.transfer(harryschecking, 980);
harrysChecking.withdraw(500);
harrysChecking.withdraw(80);
harrysChecking.withdraw(400);
endOfMonth(momsSavings);
endOfMonth(collegeFund);
endOfMonth(harrysChecking);
printBalance(“mom’s savings”, momsSavings);
printBalance(“the college Fund”, collegeFund);
printBalance(“Harry’s checking”, harrysChecking);
}
public static void endOfMonth(SavingsAccount savings)
{ savings.addInterest( ); }
public static void endOfMonth(CheckingAccount checking)
{ checking.deductFees( ); }
public static void printBalance(String name, BankAccount account)
{ System.out.println(“The balance of “ + name + “ account is $” +
account.getBalance( ) );
}
}
Polymorphism vs. Overload
Let’s take a look at a polymorphic method call such as other.deposit(amount), there are several deposit methods that can be called.
Both polymorphic deposit and withdraw methods are called.
What happens when the method name is overloaded;i.e., when a single class has several methods with the same name but different parameter types?
For example, we can have two constructors BankAccount() and BankAccount(double).
The compiler selects the appropriate method when compiling a program.
Another example of overloading, the static endOfMonth methods in the AccountTest class. The compiler uses the type of the explicit parameter to pick the appropriate method.
The main difference between polymorphism and overloading is the binding method.
Polymorphism uses a selection method called late binding, i.e., the compiler doesn’t make any decision when translating the method, it is the virtual machine that selects the appropriate method.
Overloading uses early binding technique which compiler picks an overloaded method when translating the program.
Interfaces
Many object-oriented programming languages have multiple inheritance, i.e.,more than one superclass.
In Java, a class cannot have two direct superclasses.
To support this feature, we use interfaces instead of superclasses.
The purpose of interfaces is to support the concept of writing a single method for any data type.
Interfaces
An interface is similar to a class, but
An interface does not have instance variables
All methods in an interface are abstract; they have a name, parameters, and a return type, but they don’t have an implementation.
All methods in an interface are automatically public.
The interface declaration lists all methods that the interface requires.
For example, the java.lang package defines a Comparable interface as:
public interface Comparable
{ int compareTo(Object other);
// no implementation
}
We don’t extend interfaces; we implement them, using the special implements keyword:
public class SavingAccount extends BankAccount
implements Comparable
{ …
}
Any class that implements the Comparable interface must supply the compareTo method
public class SavingsAccount extends BankAccount
implements Comparable
{ …
public int compareTo(Object other)
{ // supply implementation …
SavingsAccount otherAcc = (SavingsAccount)other;
if (interestRate < otherAcc.interestRate) return -1;
if (interestRate > otherAcc.interestRate) return 1;
return 0;
} ...
}
Note that the class must declare the method as public, whereas the interface does not -- all methods in an interface are public.
Once a class implements an interface, you can convert a class reference to an interface reference:
SavingsAccount harrysSavings = new SavingAccount();
Comparable first = harrysSavings;
However, we can never construct an interface:
Comparable second; // OK
second = new Comparable( ); // ERROR
All interface references refer to objects of other classes-- classes that implement the interface.
first.compareTo(second);
A class can have only one superclass, but it can implement any number of interfaces
public class SavingsAccount extends BankAccount
implements Comparable, Cloneable
Constants in Interfaces
public interface SwingConstants
{ int NORTH = 1;
int NORTH_EAST = 2;
int EAST = 3;
…
}
// SwingConstants.NORTH;
all variables in an interface are automatically public static final
Abstract Classes
Abstract Classes : A class which we cannot create objects
public abstract class BankAccount
{ public abstract void deductFees( );
…
}
The reason for using abstract classes is to force programmers to create subclasses.
BankAccount anAccount; // OK
anAccount = new BankAccount(); // Error
anAccount = new SavingsAccount(); // OK
anAccount = null; // OK
Note that we can still have an object reference whose type is an abstract class.
Abstarct classes differ from interfaces -- they can have instance variables and concrete methods.
Final Methods and Classes: To prevent others from creating subclasses or from overriding certain methods
public final class String { …. }
public class MyApplet extends Applet
{ public final boolean checkPasswd(String passwd)
{ … }
}
Access Control
Java has four levels of controlling access to variables, methods, and classes:
public access
private access
protected access
Package access (the default, when no access modifier is given)
Proteced data can be accessed by the methods of a class and all its subclasses.
public class BankAccount
{ protected double balance; }
If a superclass declares a method to be publicly accesible, we cannot override it to be more private.
public class BankAccount
{ public void withdraw(double amount) { … } }
public class TimeDepositAccount extends BankAccount
{ private void withdraw(double amount) { … } // ERROR
}
Overriding Object Superclass Methods
The methods of the Object class are very general:
- String toString( ) // Returns a string representation of the object
- boolean equals(Object other) // Test whether the object equals
// another object
- Object clone( ) // Makes a full copy of an object
Overriding the toString Method
The toString method returns a string representation for each object.
Rectangle x = new Rectangle(5, 10, 20, 30);
String s = x.toString( ); // set s to “java.awt.Rectangle[x=5,
// y=10, width=20,height=30]”
The toString method is called whenever we concatenate a string with an object.
System.out.println(“x means ” + x); // will print “x means
// java.awt.Rectangle[x=5,y=10,width=20,height=30]”
Let’s try the toString method for the BankAccount class:
BankAccount x = new BankAccount(5000);
String s = x.toString( ); // sets s to “BankAccount@d246bf”
// print the name of the class and the memory address of the object
To work better, overriding toString method:
public class BankAccount
{ public String toString()
{ return “BankAccount[balance=” + balance + “]”; }
}
BankAccount x = new BankAccount(5000);
String s = x.toString( );
// sets s to “BankAccount[balance=5000]”
Overriding the equals Method
The equals method is called whenever we want to compare two objects:
if (account1.equals(account2)) …
// contents are the same
Compare to:
if (account1 = = account2) …
// test two references are the same object
public class BankAccount
{ public boolean equals(Object otherObj)
{ if (otherObj instanceof BankAccount)
{ BankAccount other = (BankAccount)otherObj;
return balance = = other.balance;
}
else
return false;
}
}
Overriding the clone method
To make a copy of an object:
public class BankAccount
{ public Object clone( )
{ BankAccount clonedAcc = new BankAccount(balance);
return clonedAcc;
}
}
When we call the method:
BankAccount acc1 = new BankAccount(1000);
BankAccount acc2 = (BankAccount)acc1.clone();
Compared to:
BankAccount acc1 = new BankAccount(1000);
BankAccount acc2 = acc1;
Clone Mutable Instance Variables
Consider the following class:
public class Customer
{ private String name;
private BankAccount account;
public Customer(String aName)
{ name = aName;
account = new BankAccount();
}
public String getName()
{ return name; }
public BankAccount getAccount()
{ return account; }
getAccount method breaks encapsulation because anyone can modify the object state without going through the public interface:
Customer harry = new Customer(“Harry Handsome”);
BankAccount account = harry.getAccount();
// anyone can withdraw money!
account.withdraw(100000);
So, we should clone the object reference:
public BankAccount getAccount()
{ return (BankAccount)account.clone(); }
The rule of thumb is that a class should clone all references to mutable objects that it gives out.
The converse is true as well-- a class should clone references that it receives:
public Customer(String aName, BankAccount anAccount)
{ name = aName;
account = (BankAccount)anAccount.clone();
}
Using Object.clone()
If we have an object with many instance variables, it can be tedious to copy them all into a new object.
The Object.clone() simply creates a new object whose instance variables are copies of the original.
public class BankAccount
{ public Object clone()
{ // not complete
Object clonedAccount = super.clone();
return clonedAccount;
}
}
Object.clone() checks that the object being cloned implements the Cloneable interface and do exception handling.
public class BankAccount implements Cloneable
{ public Object clone()
{ try
{ // clones all instance variables
Object clonedAccount = super.clone();
return clonedAccount;
}
catch (CloneNotSupportedException e)
{ return null; }
}
}
If an object contains a reference to another mutable object, then we need to call clone for that reference, in order to prevent shallow copy problem (see Figure 11).
public class Customer implements Cloneable
{ private String name;
private BankAcount account;
public Object clone()
{ try
{ Object clonedCustomer = (Customer)super.clone();
clonedCustomer.account = (BankAccount)account.clone();
return clonedCustomer;
}
catch (CloneNotSupportedException e)
{ return null; }
}
}
Packages
A Java package is a set of related classes.
Table1: Important Packages in the Java Library
java.lang Langauge supoort Math
java.util Utilites Random
java.io Input/Output PrintStream
java.awt Abstract Windowing Color
Toolkit
java.applet Applets Applet
java.net Networking Socket
java.sql Database access ResultSet
javax.swing Swing UI JButton
To put classes in a package:
package packagename;
package com.horstmann.ccj;
import java.io.InputStream;
import java.io.InputStreamReader;
public class ConsoleReader { … }
….
Importing Packages
import com.horstmann.ccj.ConsoleReader;
|
|