Chapter 9
Interfaces and Polymorhism
Chapter Goals
- To learn about interfaces
- To be able to convert between supertype and subtype references
- To understand the concept of polymorphism
- To appreciate how interfaces can be used to decouple classes
- To learn how to implement helper classes as inner classes
- To understand how inner classes access variables from the surrounding
scope
- To implement event listeners for timer events
Modifying DataSet for Bank Accounts
public class DataSet // modified for BankAccount objects
{
. . .
public void add(BankAccount x)
{
sum = sum + x.getBalance();
if (count == 0
|| maximum.getBalance() < x.getBalance())
maximum = x;
count++;
}
public BankAccount getMaximum()
{
return maximum;
}
private double sum;
private BankAccount maximum;
private int count;
}
Modifying DataSet for Coins
public class DataSet // modified for Coin objects
{
. . .
public void add(Coin x)
{
sum = sum + x.getValue();
if (count == 0
|| maximum.getValue() < x.getValue())
maximum = x;
count++;
}
public Coin getMaximum()
{
return maximum;
}
private double sum;
private Coin maximum;
private int count;
}
Measurable Interface
Interfaces vs. Classes
- All methods in an interface are abstract--no implementation
- All methods in an interface are automatically public
- An interface doesn't have instance fields
Generic DataSet for Measurable Objects
public class DataSet // modified for Coin objects
{
. . .
public void add(Measurable x)
{
sum = sum + x.getMeasure();
if (count == 0
|| maximum.getMeasure() < x.getMeasure())
maximum = x;
count++;
}
public Measurable getMaximum()
{
return maximum;
}
private double sum;
private Measurable maximum;
private int count;
}
Realizing an Interface
Making BankAccount and Coin Classes Measurable
class BankAccount implements Measurable
{
public double getMeasure()
{
return balance;
}
additional methods and fields
}
class Coin implements Measurable
{
public double getMeasure()
{
return value;
}
additional methods and fields
}
File DataSetTest.java
1 | /** |
2 | This program tests the DataSet class. |
3 | */ |
4 | public class DataSetTest |
5 | { |
6 | public static void main(String[] args) |
7 | { |
8 | |
9 | DataSet bankData = new DataSet(); |
10 | |
11 | bankData.add(new BankAccount(0)); |
12 | bankData.add(new BankAccount(10000)); |
13 | bankData.add(new BankAccount(2000)); |
14 | |
15 | System.out.println("Average balance = " |
16 | + bankData.getAverage()); |
17 | Measurable max = bankData.getMaximum(); |
18 | System.out.println("Highest balance = " |
19 | + max.getMeasure()); |
20 | |
21 | DataSet coinData = new DataSet(); |
22 | |
23 | coinData.add(new Coin(0.25, "quarter")); |
24 | coinData.add(new Coin(0.1, "dime")); |
25 | coinData.add(new Coin(0.05, "nickel")); |
26 | |
27 | System.out.println("Average coin value = " |
28 | + coinData.getAverage()); |
29 | max = coinData.getMaximum(); |
30 | System.out.println("Highest coin value = " |
31 | + max.getMeasure()); |
32 | } |
33 | } |
UML Diagram of DataSet and Related Classes
- Note that DataSet is decoupled from BankAccount
, Coin
Syntax 9.1: Defining an Interface
|
public interface InterfaceName
{
method signatures
}
|
Example:
|
public interface Measurable
{
double getMeasure();
}
|
Purpose:
To define an interface and its method signatures. The methods are automatically
public. |
Syntax 9.2: Implementing an Interface
|
public class ClassName
implementsInterfaceName, InterfaceName, ...
{
methods
instance variables
}
|
Example:
|
public class BankAccount
implements Measurable
{
// other BankAccount methods
public double getMeasure()
{
// method implementation
}
}
|
Purpose:
To define a new class that implements the methods of an interface
|
Converting Between Types
- Can convert from class type to realized interface type:
BankAccount account = new BankAccount(10000);
Measurable x = account; // OK
- Same interface type variable can hold reference to Coin
x = new Coin(0.1, "dime"); // OK
- Cannot convert between unrelated types
x = new Rectangle(5, 10, 20, 30); // ERROR
Casts
- Add coin objects to DataSet
DataSet coinData = new DataSet();
coinData.add(new Coin(0.25, "quarter"));
coinData.add(new Coin(0.1, "dime"));
...
- Get largest coin with getMaximum method:
Measurable max = coinData.getMaximum();
- What can you do with it? It's not of type Coin
String name = max.getName(); // ERROR
- You know it's a coin, but the compiler doesn't. Apply a cast:
Coin maxCoin = (Coin)max;
String name = maxCoin.getName();
- If you are wrong and max isn't a coin, the compiler throws
an exception
The instanceof Operator
- Use instanceof for safe casts:
if (max instanceof Coin)
{
Coin maxCoin = (Coin)max;
. . .
}
Syntax 9.3: The instanceof Operator
|
object instanceof ClassName
|
Example:
|
if (x instanceof Coin)
{
Coin c = (Coin)x;
}
|
Purpose:
To return true if the object is an instance of ClassName
(or one of its subclasses), false otherwise |
Polymorphism
- Interface variable holds reference to object of a class that realizes
the interface
Measurable x;
x = new BankAccount(10000);
x = new Coin(0.1, "dime");
- You can never construct an interface!
x = new Measurable(); // ERROR
- You can call any of the interface methods:
double m = x.getMeasure();
- Which method is called?
Polymorphism
- Depends on the actual object.
- If x refers to a bank account, calls BankAccount.getMeasure
- If x refers to a coin, calls Coin.getMeasure
- Polymorphism (greek: many shapes): The type of the object determines
the method to call
- Called late binding. Resolved at runtime
- Different from overloading. Overloading is resolved by the compiler.
Using a Strategy Interface
- Drawbacks of Measurable interface:
- must modify class, add interface and method
- can measure a class in only one way
- Remedy: Hand the object to be measured to a method:
public interface Measurer
{
double measure(Object anObject);
}
- Object is the "lowest common denominator" of all classes
Using a Strategy Interface
- add method asks measurer (and not the added object) to do the
measuring
public void add(Object x)
{
sum = sum + measurer.measure(x);
if (count == 0
|| measurer.measure(maximum) < measurer.measure(x))
maximum = x;
count++;
}
Using a Strategy Interface
class RectangleMeasurer implements Measurer
{
public double measure(Object anObject)
{
Rectangle aRectangle = (Rectangle)anObject;
double area = aRectangle.getWidth() * aRectangle.getHeight();
return area;
}
}
- Must cast from Object to Rectangle
- Pass measurer to data set constructor:
Measurer m = new RectangleMeasurer();
DataSet data = new DataSet(m);
UML Diagram of Measurer Interface and Related Classes
- Note that the Rectangle class is decoupled from the Measurer
interface
Inner Classes
- Trivial class can be defined inside a method
public static void main(String[] args)
{
class RectangleMeasurer implements Measurer
{
. . .
}
Measurer m = new RectangleMeasurer();
. . .
// RectangleMeasurer class not used beyond this point
}
File DataSet.java
1 | /** |
2 | Computes the average of a set of data values. |
3 | */ |
4 | public class DataSet |
5 | { |
6 | /** |
7 | Constructs an empty data set with a given measurer |
8 | @param aMeasurer the measurer that is used to measure data values |
9 | */ |
10 | public DataSet(Measurer aMeasurer) |
11 | { |
12 | sum = 0; |
13 | count = 0; |
14 | maximum = null; |
15 | measurer = aMeasurer; |
16 | } |
17 | |
18 | /** |
19 | Adds a data value to the data set |
20 | @param x a data value |
21 | */ |
22 | public void add(Object x) |
23 | { |
24 | sum = sum + measurer.measure(x); |
25 | if (count == 0 |
26 | || measurer.measure(maximum) < measurer.measure(x)) |
27 | maximum = x; |
28 | count++; |
29 | } |
30 | |
31 | /** |
32 | Gets the average of the added data. |
33 | @return the average or 0 if no data has been added |
34 | */ |
35 | public double getAverage() |
36 | { |
37 | if (count == 0) return 0; |
38 | else return sum / count; |
39 | } |
40 | |
41 | /** |
42 | Gets the largest of the added data. |
43 | @return the maximum or 0 if no data has been added |
44 | */ |
45 | public Object getMaximum() |
46 | { |
47 | return maximum; |
48 | } |
49 | |
50 | private double sum; |
51 | private Object maximum; |
52 | private int count; |
53 | private Measurer measurer; |
54 | } |
File DataSetTest.java
1 | import java.awt.Rectangle; |
2 | |
3 | /** |
4 | This program demonstrates the use of a Measurer. |
5 | */ |
6 | public class DataSetTest |
7 | { |
8 | public static void main(String[] args) |
9 | { |
10 | class RectangleMeasurer implements Measurer |
11 | { |
12 | public double measure(Object anObject) |
13 | { |
14 | Rectangle aRectangle = (Rectangle)anObject; |
15 | double area |
16 | = aRectangle.getWidth() * aRectangle.getHeight(); |
17 | return area; |
18 | } |
19 | } |
20 | |
21 | Measurer m = new RectangleMeasurer(); |
22 | |
23 | DataSet data = new DataSet(m); |
24 | |
25 | data.add(new Rectangle(5, 10, 20, 30)); |
26 | data.add(new Rectangle(10, 20, 30, 40)); |
27 | data.add(new Rectangle(20, 30, 5, 10)); |
28 | |
29 | System.out.println("Average area = " + data.getAverage()); |
30 | Rectangle max = (Rectangle)data.getMaximum(); |
31 | System.out.println("Maximum area = " + max); |
32 | } |
33 | } |
File Measurer.java
1 | /** |
2 | Describes any class whose objects can measure other objects. |
3 | */ |
4 | public interface Measurer |
5 | { |
6 | /** |
7 | Computes the measure of an object. |
8 | @param anObject the object to be measured |
9 | @return the measure |
10 | */ |
11 | double measure(Object anObject); |
12 | } |
Syntax 9.4: Inner Classes
|
Declared inside a method
class OuterClassName
{
method signature
{
. . .
class InnerClassName
{
nethods
fields
}
. . .
}
. . .
}
Declared inside a class
class OuterClassName
{
nethods
fields
accessSpecifier class InnerClassName
{
nethods
fields
}
. . .
}
|
Example:
|
public class Test
{
public static void main(String[] args)
{
class RectangleMeasurer implements Measurer
{
. . .
}
}
}
|
Purpose:
To define an inner class whose methods have access to the same variables
and methods as the outer class methods |
Processing Timer Events
- javax.swing.Timer generates equally spaced timer events
- Sends events to action listener
public interface ActionListener
{
void actionPerformed(ActionEvent event);
}
- Realize the interface
class MyListener implements ActionListener
{
void actionPerformed(ActionEvent event);
{
// this action will be executed at each timer event
place listener action here
}
}
- Add listener to timer
MyListener listener = new MyListener();
Timer t = new Timer(interval, listener);
t.start();
Example: Countdown
- Program prints
10
9
. . .
2
1
0
Liftoff!
- One second delay between printouts
File TimerTest.java
1 | import java.awt.event.ActionEvent; |
2 | import java.awt.event.ActionListener; |
3 | import javax.swing.JOptionPane; |
4 | import javax.swing.Timer; |
5 | |
6 | /** |
7 | This program tests the Timer class. |
8 | */ |
9 | public class TimerTest |
10 | { |
11 | public static void main(String[] args) |
12 | { |
13 | class CountDown implements ActionListener |
14 | { |
15 | public CountDown(int initialCount) |
16 | { |
17 | count = initialCount; |
18 | } |
19 | |
20 | public void actionPerformed(ActionEvent event) |
21 | { |
22 | if (count >= 0) |
23 | System.out.println(count); |
24 | if (count == 0) |
25 | System.out.println("Liftoff!"); |
26 | count--; |
27 | } |
28 | |
29 | private int count; |
30 | } |
31 | |
32 | CountDown listener = new CountDown(10); |
33 | |
34 | final int DELAY = 1000; // milliseconds between timer ticks |
35 | Timer t = new Timer(DELAY, listener); |
36 | t.start(); |
37 | |
38 | JOptionPane.showMessageDialog(null, "Quit?"); |
39 | System.exit(0); |
40 | } |
41 | } |
Example: Add Interest
Inner Class Can Access Outer Variables
class InterestAdder implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
double interest = account.getBalance() * RATE / 100;
account.deposit(interest);
System.out.println("Balance = " + account.getBalance());
}
}
- Inner class can access all fields and methods of outer class
- Inner class can access all final variables of enclosing method
File TimerTest.java
1 | import java.awt.event.ActionEvent; |
2 | import java.awt.event.ActionListener; |
3 | import javax.swing.JOptionPane; |
4 | import javax.swing.Timer; |
5 | |
6 | /** |
7 | This program uses a timer to add interest to a bank |
8 | account once per second. |
9 | */ |
10 | public class TimerTest |
11 | { |
12 | public static void main(String[] args) |
13 | { |
14 | final BankAccount account = new BankAccount(1000); |
15 | |
16 | class InterestAdder implements ActionListener |
17 | { |
18 | public void actionPerformed(ActionEvent event) |
19 | { |
20 | double interest = account.getBalance() * RATE / 100; |
21 | account.deposit(interest); |
22 | System.out.println("Balance = " |
23 | + account.getBalance()); |
24 | } |
25 | } |
26 | |
27 | InterestAdder listener = new InterestAdder(); |
28 | |
29 | final int DELAY = 1000; // milliseconds between timer ticks |
30 | Timer t = new Timer(DELAY, listener); |
31 | t.start(); |
32 | |
33 | JOptionPane.showMessageDialog(null, "Quit?"); |
34 | System.exit(0); |
35 | } |
36 | |
37 | private static final double RATE = 5; |
38 | } |
UML Diagram of Timer Classes