CHAPTER GOALS
To be able to declare and use interface types
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
G To implement event listeners in graphical applications
In order to increase programming productivity, we want to be able to reuse software components in multiple projects. However, some adaptations are often required to make reuse possible. In this chapter, you will learn an important strategy for separating the reusable part of a computation from the parts that vary in each reuse scenario. The reusable part invokes methods of an interface. It is combined with a class that implements the interface methods. To produce a different application, you simply plug in another class that implements the same interface. The program's behavior varies according to the implementation that is plugged in—this phenomenon is called polymorphism.
It is often possible to make a service available to a wider set of inputs by focusing on the essential operations that the service requires. Interface types are used to express these common operations.
Consider the DataSet
class of Chapter 6. That class provides a service, namely computing the average and maximum of a set of input values. Unfortunately, the class is suitable only for computing the average of a set of numbers. If we wanted to process bank accounts to find the bank account with the highest balance, we could not use the class in its current form. We could modify the class, like this:
public class DataSet // Modified for BankAccount objects { private double sum; private BankAccount maximum; private int count; ... public void add(BankAccount x) { sum = sum + x.getBalance(); if (count == 0 || maximum.getBalance() < x.getBalance()) maximum = x; count++; }
public BankAccount getMaximum() { return maximum; } }
Or suppose we wanted to find the coin with the highest value among a set of coins. We would need to modify the DataSet
class again.
public class DataSet // Modified for Coin objects { private double sum; private Coin maximum; private int count; ... public void add(Coin x) { sum = sum + x.getValue(); if (count == 0 || maximum.getValue() < x.getValue()) maximum = x; count++; } public Coin getMaximum() { return maximum; } }
Clearly, the algorithm for the data analysis service is the same in all cases, but the details of measurement differ. We would like to provide a single class that provides this service to any objects that can be measured.
Suppose that the various classes agree on a method getMeasure
that obtains the measure to be used in the data analysis. For bank accounts, getMeasure
returns the balance. For coins, getMeasure
returns the coin value, and so on. Then we can implement a DataSet
class whose add
method looks like this:
sum = sum + x.getMeasure(); if (count == 0 || maximum.getMeasure() < x.getMeasure()) maximum = x; count++;
What is the type of the variable x
? Ideally, x
should refer to any class that has a getMeasure
method.
In Java, an interface type is used to specify required operations. We will declare an interface type that we call Measurable
:
public interface Measurable { double getMeasure(); }
A Java interface type declares methods but does not provide their implementations.
The interface declaration lists all methods that the interface type requires. The Measurable
interface type requires a single method, but in general, an interface type can require multiple methods.
Note that the Measurable
type is not a type in the standard library—it is a type that was created specifically for this book, in order to make the DataSet
class more reusable.
An interface type is similar to a class, but there are several important differences:
All methods in an interface type are abstract; that is, they have a name, parameters, and a return type, but they don't have an implementation.
All methods in an interface type are automatically public.
An interface type does not have instance variables.
Unlike a class, an interface type provides no implementation.
Now we can use the interface type Measurable
to declare the variables x
and maximum
.
public class DataSet { private double sum; private Measurable maximum; private int count; ... public void add(Measurable x) { sum = sum + x.getMeasure(); if (count == 0 || maximum.getMeasure() < x.getMeasure()) maximum = x; count++; } public Measurable getMaximum() { return maximum; } }
This DataSet
class is usable for analyzing objects of any class that implements the Measurable
interface. A class implements an interface type if it declares the interface in an implements
clause. It should then implement the method or methods that the interface requires.
public class BankAccount implements Measurable { ... public double getMeasure() { return balance; } }
Use the implements
reserved word to indicate that a class implements an interface type.
Note that the class must declare the method as public
, whereas the interface need not—all methods in an interface are public.
Similarly, it is an easy matter to modify the Coin
class to implement the Measurable
interface.
public class Coin implements Measurable { public double getMeasure() { return value; } ... }
In summary, the Measurable
interface expresses what all measurable objects have in common. This commonality makes the flexibility of the improved DataSet
class possible. A data set can analyze objects of any class that implements the Measurable
interface.
This is a typical usage for interface types. A service provider—in this case, the DataSet
—specifies an interface for participating in the service. Any class that conforms to that interface can then be used with the service. This is similar to the way a mixer will provide rotation to any attachment that fits its interface (see Figure 1).
Figure 9.2. UML Diagram of the DataSet
Class and the Classes that Implement the Measurable
Interface
Figure 2 shows the relationships between the DataSet
class, the Measurable
inter-face, and the classes that implement the interface. Note that the DataSet
class depends only on the Measurable
interface. It is decoupled from the BankAccount
and Coin
classes.
In the UML notation, interfaces are tagged with an indicator «interface»
. A dotted arrow with a triangular tip denotes the "is-a" relationship between a class and an interface. You have to look carefully at the arrow tips—a dotted line with an open arrow tip (→) denotes the "uses" relationship or dependency.
ch09/measure1/DataSetTester.java
1
/**2
This program tests the DataSet class.3
*/4
public class DataSetTester5
{6
public static void main(String[] args)7
{8
DataSet bankData = new DataSet();9
10
bankData.add(new BankAccount(0));11
bankData.add(new BankAccount(10000));12
bankData.add(new BankAccount(2000));13
14
System.out.println("Average balance: " + bankData.getAverage());15
System.out.println("Expected: 4000");16
Measurable max = bankData.getMaximum();17
System.out.println("Highest balance: " + max.getMeasure());18
System.out.println("Expected: 10000");19
20
DataSet coinData = new DataSet();21
22
coinData.add(new Coin(0.25, "quarter"));23
coinData.add(new Coin(0.1, "dime"));24
coinData.add(new Coin(0.05, "nickel"));25
26
System.out.println("Average coin value: " + coinData.getAverage());27
System.out.println("Expected: 0.133");28
max = coinData.getMaximum();29
System.out.println("Highest coin value: " + max.getMeasure());30
System.out.println("Expected: 0.25");31
}32
}
Average balance: 4000.0 Expected: 4000 Highest balance: 10000.0 Expected: 10000 Average coin value: 0.13333333333333333 Expected: 0.133 Highest coin value: 0.25 Expected: 0.25
2. Why can't the add
method of the DataSet
class have a parameter of type Object
?
Interfaces are used to express the commonality between classes. In this section, we discuss when it is legal to convert between class and interface types.
Have a close look at the call
bankData.add(new BankAccount(1000));
from the test program of the preceding section. Here we pass an object of type BankAccount
to the add
method of the DataSet
class. However, that method has a parameter of type Measurable
:
public void add(Measurable x)
It it legal to convert from the BankAccount
type to the Measurable
type. In general, you can convert from a class type to the type of any interface that the class implements. For example,
BankAccount account = new BankAccount(1000); Measurable meas = account; // OK
You can convert from a class type to an interface type, provided the class implements the interface.
Alternatively, a Measurable
variable can refer to an object of the Coin
class of the preceding section because that class also implements the Measurable
interface.
Coin dime = new Coin(0.1, "dime"); Measurable meas = dime; // Also OK
However, the Rectangle
class from the standard library doesn't implement the Measurable
interface. Therefore, the following assignment is an error:
Measurable meas = new Rectangle(5, 10, 20, 30); // Error
Occasionally, it happens that you store an object in an interface reference and you need to convert its type back. This happens in the getMaximum
method of the DataSet
class. The DataSet
stores the object with the largest measure, as a Measurable
reference.
DataSet coinData = new DataSet(); coinData.add(new Coin(0.25, "quarter")); coinData.add(new Coin(0.1, "dime")); coinData.add(new Coin(0.05, "nickel")); Measurable max = coinData.getMaximum();
Now what can you do with the max
reference? You know it refers to a Coin
object, but the compiler doesn't. For example, you cannot call the getName
method:
String coinName = max.getName(); // Error
That call is an error, because the Measurable
type has no getName
method.
However, as long as you are absolutely sure that max
refers to a Coin
object, you can use the cast notation to convert its type back:
Coin maxCoin = (Coin) max; String name = maxCoin.getName();
If you are wrong, and the object doesn't actually refer to a coin, a run-time exception will occur.
This cast notation is the same notation that you saw in Chapter 4 to convert between number types. For example, if x
is a floating-point number, then (int) x
is the integer part of the number. The intent is similar—to convert from one type to another. However, there is one big difference between casting of number types and casting of class types. When casting number types, you may lose information, and you use the cast to tell the compiler that you agree to the potential information loss. When casting object types, on the other hand, you take a riskof causing an exception, and you tell the compiler that you agree to that risk.
4. If both BankAccount
and Coin
implement the Measurable
interface, can a Coin
reference be converted to a BankAccount
reference?
When multiple classes implement the same interface, each class can implement the methods of the interface in different ways. How is the correct method executed when the interface method is invoked? We will answer that question in this section.
It is worth emphasizing once again that it is perfectly legal—and in fact very common—to have variables whose type is an interface, such as Measurable meas;
Just remember that the object to which meas
refers doesn't have type Measurable
. In fact, no object has type Measurable
. Instead, the type of the object is some class that implements the Measurable
interface. This might be an object of the BankAccount
or Coin
class, or some other class with a getMeasure
method.
meas = new BankAccount(1000); // OK meas = new Coin(0.1, "dime"); // OK
What can you do with an interface variable, given that you don't know the class of the object that it references? You can invoke the methods of the interface:
double m = meas.getMeasure();
The DataSet
class took advantage of this capability by computing the measure of the added object, without knowing exactly what kind of object was added.
Now let's think through the call to the getMeasure
method more carefully. Which getMeasure
method? The BankAccount
and Coin
classes provide two different implementations of that method. How did the correct method get called if the caller didn't even know the exact class to which meas
belongs?
The Java virtual machine locates the correct method by first looking at the class of the actual object, and then calling the method with the given name in that class. That is, if meas
refers to a BankAccount
object, then the BankAccount.getMeasure
method is called. If meas
refers to a Coin
object, then the Coin.getMeasure
method is called. This means that one method call
double m = meas.getMeasure();
can invoke different methods depending on the momentary contents of meas
. This mechanism for locating the appropriate method is called dynamic method lookup.
When the virtual machine calls an instance method, it locates the method of the implicit parameter's class. This is called dynamic method lookup.
Dynamic method lookup enables a programming technique called polymorphism. The term "polymorphism" comes from the Greek words for "many shapes". The same computation works for objects of many shapes, and adapts itself to the nature of the objects.
Polymorphism denotes the ability to treat objects with differences in behavior in a uniform way.
Figure 9.4. An Interface Reference Can Refer to an Object of Any Class that Implements the Interface
6. Why can you nevertheless declare a variable whose type is Measurable
?
7. What does this code fragment print? Why is this an example of polymorphism?
DataSet data = new DataSet(); data.add(new BankAccount(1000)); data.add(new Coin(0.1, "dime")); System.out.println(data.getAverage());
In this section, we introduce the notion of a callback, show how it leads to a more flexible DataSet
class, and study how a callback can be implemented in Java by using interface types.
To understand why a further improvement to the DataSet
class is desirable, consider these limitations of the Measurable
interface:
You can add the Measurable
interface only to classes under your control. If you want to process a set of Rectangle
objects, you cannot make the Rectangle
class implement another interface—it is a system class, which you cannot change.
You can measure an object in only one way. If you want to analyze a set of savings accounts both by bank balance and by interest rate, you are stuck.
Therefore, let's rethink the DataSet
class. The data set needs to measure the objects that are added. When the objects are required to be of type Measurable
, the responsibility of measuring lies with the added objects themselves, which is the cause of the limitations that we noted.
It would be better if we could give a method for measuring objects to a data set. When collecting rectangles, we might give it a method for computing the area of a rectangle. When collecting savings accounts, we might give it a method for getting the account's interest rate.
Such a method is called a callback. A callback is a mechanism for bundling up a block of code so that it can be invoked at a later time.
In some programming languages, it is possible to specify callbacks directly, as blocks of code or names of methods. But Java is an object-oriented programming language. Therefore, you turn callbacks into objects. This process starts by declaring an interface for the callback:
public interface Measurer { double measure(Object anObject); }
A callback is a mechanism for specifying code that is executed at a later time.
The measure
method measures an object and returns its measurement. Here we use the fact that all objects can be converted to the type Object
, the "lowest common denominator" of all classes in Java. We will discuss the Object
type in greater detail in Chapter 10.
The code that makes the call to the callback receives an object of a class that implements this interface. In our case, the improved DataSet
class is constructed with a Measurer
object (that is, an object of some class that implements the Measurer
interface). That object is saved in a measurer
instance variable.
public DataSet(Measurer aMeasurer) { sum = 0; count = 0; maximum = null; measurer = aMeasurer; }
The measurer
variable is used to carry out the measurements, like this:
public void add(Object x) { sum = sum + measurer.measure(x); if (count == 0 || measurer.measure(maximum) < measurer.measure(x)) maximum = x; count++; }
The DataSet
class simply makes a callback to the measure
method whenever it needs to measure any object.
Finally, a specific callback is obtained by implementing the Measurer
interface. For example, here is how you can measure rectangles by area. Provide a class
public class RectangleMeasurer implements Measurer { public double measure(Object anObject) { Rectangle aRectangle = (Rectangle) anObject; double area = aRectangle.getWidth() * aRectangle.getHeight(); return area; } }
Note that the measure
method must accept a parameter of type Object
, even though this particular measurer just wants to measure rectangles. The method parameter types must match those of the measure
method in the Measurer
interface. Therefore, the Object
parameter is cast to the Rectangle
type:
Rectangle aRectangle = (Rectangle) anObject;
What can you do with a RectangleMeasurer
? You need it for a DataSet
that compares rectangles by area. Construct an object of the RectangleMeasurer
class and pass it to the DataSet
constructor.
Measurer m = new RectangleMeasurer(); DataSet data = new DataSet(m);
Next, add rectangles to the data set.
data.add(new Rectangle(5, 10, 20, 30)); data.add(new Rectangle(10, 20, 30, 40)); ...
The data set will ask the RectangleMeasurer
object to measure the rectangles. In other words, the data set uses the RectangleMeasurer
object to carry out callbacks.
Figure 5 shows the UML diagram of the classes and interfaces of this solution. As in Figure 2, the DataSet
class is decoupled from the Rectangle
class whose objects it processes. However, unlike in Figure 2, the Rectangle
class is no longer coupled with another class. Instead, to process rectangles, you provide a small "helper" class RectangleMeasurer
. This helper class has only one purpose: to tell the DataSet
how to measure its objects.
ch09/measure2/Measurer.java
1
/**2
Describes any class whose objects can measure other objects.3
*/4
public interface Measurer5
{6
/**7
Computes the measure of an object.8
@param anObject the object to be measured9
@return the measure10
*/11
double measure(Object anObject);12
}
ch09/measure2/RectangleMeasurer.java
1
import java.awt.Rectangle;2
3
/**4
Objects of this class measure rectangles by area.5
*/6
public class RectangleMeasurer implements Measurer7
{8
public double measure(Object anObject)9
{10
Rectangle aRectangle = (Rectangle) anObject;11
double area = aRectangle.getWidth() * aRectangle.getHeight();12
return area;13
}14
}
1
/**2
Computes the average of a set of data values.3
*/4
public class DataSet5
{6
private double sum;7
private Object maximum;8
private int count;9
private Measurer measurer;10
11
/**12
Constructs an empty data set with a given measurer.13
@param aMeasurer the measurer that is used to measure data values14
*/15
public DataSet(Measurer aMeasurer)16
{17
sum = 0;18
count = 0;19
maximum = null;20
measurer = aMeasurer;21
}22
23
/**24
Adds a data value to the data set.25
@param x a data value26
*/27
public void add(Object x)28
{29
sum = sum + measurer.measure(x);30
if (count == 0 || measurer.measure(maximum) < measurer.measure(x))31
maximum = x;32
count++;33
}34
35
/**36
Gets the average of the added data.37
@return the average or 0 if no data has been added38
*/39
public double getAverage()40
{41
if (count == 0) return 0;42
else return sum / count;43
}44
45
/**46
Gets the largest of the added data.47
@return the maximum or 0 if no data has been added48
*/49
public Object getMaximum()50
{51
return maximum;52
}53
}
ch09/measure2/DataSetTester2.java
1
import java.awt.Rectangle;2
3
/**4
This program demonstrates the use of a Measurer.5
*/6
public class DataSetTester27
{8
public static void main(String[] args)9
{10
Measurer m = new RectangleMeasurer();11
12
DataSet data = new DataSet(m);13
14
data.add(new Rectangle(5, 10, 20, 30));15
data.add(new Rectangle(10, 20, 30, 40));16
data.add(new Rectangle(20, 30, 5, 15));17
18
System.out.println("Average area: " + data.getAverage());19
System.out.println("Expected: 625");20
21
Rectangle max = (Rectangle) data.getMaximum();22
System.out.println("Maximum area rectangle: " + max);23
System.out.println("Expected: "24
+ "java.awt.Rectangle[x=10,y=20,width=30,height=40]");25
}26
}
Average area: 625 Expected: 625 Maximum area rectangle: java.awt.Rectangle[x=10,y=20,width=30,height=40] Expected: java.awt.Rectangle[x=10,y=20,width=30,height=40]
9. How can you use the DataSet
class of this section to find the longest String
from a set of inputs?
10. Why does the measure
method of the Measurer
interface have one more parameter than the getMeasure
method of the Measurable
interface?
The RectangleMeasurer
class is a very trivial class. We need this class only because the DataSet
class needs an object of some class that implements the Measurer
interface. When you have a class that serves a very limited purpose, such as this one, you can declare the class inside the method that needs it:
public class DataSetTester3 { public static void main(String[] args) { class RectangleMeasurer implements Measurer { ...
} Measurer m = new RectangleMeasurer(); DataSet data = new DataSet(m); ... } }
A class that is declared inside another class, such as the RectangleMeasurer
class in this example, is called an inner class. This arrangement signals to the reader of your program that the RectangleMeasurer
class is not interesting beyond the scope of this method. Since an inner class inside a method is not a publicly accessible feature, you don't need to document it as thoroughly.
An inner class is declared inside another class.
You can also declare an inner class inside an enclosing class, but outside of its methods. Then the inner class is available to all methods of the enclosing class.
Inner classes are commonly used for utility classes that should not be visible elsewhere in a program.
public class DataSetTester3 { class RectangleMeasurer implements Measurer { ... } public static void main(String[] args) { Measurer m = new RectangleMeasurer(); DataSet data = new DataSet(m); ... } }
When you compile the source files for a program that uses inner classes, have a look at the class files in your program directory—you will find that the inner classes are stored in files with curious names, such as DataSetTester3$1RectangleMeasurer.class
. The exact names aren't important. The point is that the compiler turns an inner class into a regular class file.
ch09/measure3/DataSetTester3.java
1
import java.awt.Rectangle;2
3
/**4
This program demonstrates the use of an inner class.5
*/6
public class DataSetTester37
{8
public static void main(String[] args)9
{10
class RectangleMeasurer implements Measurer11
{12
public double measure(Object anObject)13
{14
Rectangle aRectangle = (Rectangle) anObject;15
double area16
= 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, 15));28
29
System.out.println("Average area: " + data.getAverage());30
System.out.println("Expected: 625");31
32
Rectangle max = (Rectangle) data.getMaximum();33
System.out.println("Maximum area rectangle: " + max);34
System.out.println("Expected: "35
+ "java.awt.Rectangle[x=10,y=20,width=30,height=40]");36
}37
}
12. How many class files are produced when you compile the DataSetTester3
program?
When you work on a program that consists of multiple classes, you often want to test some of the classes before the entire program has been completed. A very effective technique for this purpose is the use of mock objects. A mock object provides the same services as another object, but in a simplified manner.
Consider a grade book application that manages quiz scores for students. This calls for a class GradeBook
with methods such as
public void addScore(int studentId, double score) public double getAverageScore(int studentId) public void save(String filename)
A mock object provides the same services as another object, but in a simplified manner.
Now consider the class GradingProgram
that manipulates a GradeBook
object. That class calls the methods of the GradeBook
class. We would like to test the GradingProgram
class without having a fully functional GradeBook
class.
To make this work, declare an interface type with the same methods that the GradeBook
class provides. A common convention is to use the letter I
as the prefix for such an interface:
public interface IGradeBook { void addScore(int studentId, double score); double getAverageScore(int studentId); void save(String filename); ... }
The GradingProgram
class should only use this interface, never the GradeBook
class. Of course, the GradeBook
class implements this interface, but as already mentioned, it may not be ready for some time.
In the meantime, provide a mock implementation that makes some simplifying assumptions. Saving is not actually necessary for testing the user interface. We can temporarily restrict to the case of a single student.
public class MockGradeBook implements IGradeBook { private ArrayList<Double> scores; public void addScore(int studentId, double score) { // Ignore studentId scores.add(score); } double getAverageScore(int studentId) { double total = 0; for (double x : scores) { total = total + x; } return total / scores.size(); } void save(String filename) { // Do nothing } ... }
Both the mock class and the actual class implement the same interface.
Now construct an instance of MockGradeBook
and use it in the GradingProgram
class. You can immediately test the GradingProgram
class. When you are ready to test the actual class, simply use a GradeBook
instance instead. Don't erase the mock class—it will still come in handy for regression testing.
14. Why is the technique of mock objects particularly effective when the GradeBook
and GradingProgram
class are developed by two programmers?
This and the following sections continue the book's graphics track. You will learn how interfaces are used when programming graphical user interfaces.
In the applications that you have written so far, user input was under control of the program. The program asked the user for input in a specific order. For example, a program might ask the user to supply first a name, then a dollar amount. But the programs that you use every day on your computer don't work like that. In a program with a graphical user interface, the user is in control. The user can use both the mouse and the keyboard and can manipulate many parts of the user interface in any desired order. For example, the user can enter information into text fields, pull down menus, click buttons, and drag scroll bars in any order. The program must react to the user commands, in whatever order they arrive. Having to deal with many possible inputs in random order is quite a bit harder than simply forcing the user to supply input in a fixed order.
In the following sections, you will learn how to write Java programs that can react to user-interface events, such as menu selections and mouse clicks. The Java windowing toolkit has a very sophisticated mechanism that allows a program to specify the events in which it is interested and which objects to notify when one of these events occurs.
User-interface events include key presses, mouse moves, button clicks, menu selections, and so on.
Whenever the user of a graphical program types characters or uses the mouse anywhere inside one of the windows of the program, the Java windowing toolkit sends a notification to the program that an event has occurred. The windowing toolkit generates huge numbers of events. For example, whenever the mouse moves a tiny interval over a window, a "mouse move" event is generated. Whenever the mouse button is clicked, a "mouse pressed" and a "mouse released" event are generated. In addition, higher level events are generated when a user selects a menu item or button.
Most programs don't want to be flooded by boring events. For example, consider what happens when selecting a menu item with the mouse. The mouse moves over the menu item, then the mouse button is pressed, and finally the mouse button is released. Rather than receiving lots of irrelevant mouse events, a program can indicate that it only cares about menu selections, not about the underlying mouse events. However, if the mouse input is used for drawing shapes on a virtual canvas, it is necessary to closely track mouse events.
An event listener belongs to a class that is provided by the application programmer. Its methods describe the actions to be taken when an event occurs.
Every program must indicate which events it needs to receive. It does that by installing event listener objects. An event listener object belongs to a class that you provide. The methods of your event listener classes contain the instructions that you want to have executed when the events occur.
To install a listener, you need to know the event source. The event source is the user-interface component that generates a particular event. You add an event listener object to the appropriate event sources. Whenever the event occurs, the event source calls the appropriate methods of all attached event listeners.
Event sources report on events. When an event occurs, the event source notifies all event listeners.
This sounds somewhat abstract, so let's run through an extremely simple program that prints a message whenever a button is clicked (see Figure 6).
Button listeners must belong to a class that implements the ActionListener
interface:
public interface ActionListener { void actionPerformed(ActionEvent event); }
Use JButton
components for buttons. Attach an ActionListener
to each button.
This particular interface has a single method, actionPerformed
. It is your job to supply a class whose actionPerformed
method contains the instructions that you want executed whenever the button is clicked. Here is a very simple example of such a listener class:
ch09/button1/ClickListener.java
1
import java.awt.event.ActionEvent;2
import java.awt.event.ActionListener;3
4
/**5
An action listener that prints a message.6
*/7
public class ClickListener implements ActionListener8
{9
public void actionPerformed(ActionEvent event)10
{11
System.out.println("I was clicked.");12
}13
}
We ignore the event
parameter of the actionPerformed
method—it contains additional details about the event, such as the time at which it occurred.
Once the listener class has been declared, we need to construct an object of the class and add it to the button:
ActionListener listener = new ClickListener(); button.addActionListener(listener);
Whenever the button is clicked, it calls
listener.actionPerformed(event);
As a result, the message is printed.
You can think of the actionPerformed
method as another example of a callback, similar to the measure
method of the Measurer
class. The windowing toolkit calls the actionPerformed
method whenever the button is pressed, whereas the DataSet
calls the measure
method whenever it needs to measure an object.
The ButtonViewer
class, whose source code is provided at the end of this section, constructs a frame with a button and adds a ClickListener
to the button. You can test this program out by opening a console window, starting the ButtonViewer
program from that console window, clicking the button, and watching the messages in the console window.
ch09/button1/ButtonViewer.java
1
import java.awt.event.ActionListener;2
import javax.swing.JButton;3
import javax.swing.JFrame;4
5
/**6
This program demonstrates how to install an action listener.7
*/8
public class ButtonViewer9
{10
private static final int FRAME_WIDTH = 100;11
private static final int FRAME_HEIGHT = 60;12
13
public static void main(String[] args)14
{15
JFrame frame = new JFrame();16
JButton button = new JButton("Click me!");17
frame.add(button);18
19
ActionListener listener = new ClickListener();20
button.addActionListener(listener);21
22
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);23
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);24
frame.setVisible(true);25
}26
}
16. Why is it legal to assign a ClickListener
object to a variable of type ActionListener
?
In the preceding section, you saw how the code that is executed when a button is clicked is placed into a listener class. It is common to implement listener classes as inner classes like this:
JButton button = new JButton("..."); // This inner class is declared in the same method as the button variable class MyListener implements ActionListener { ... }; ActionListener listener = new MyListener(); button.addActionListener(listener);
There are two reasons for this arrangement. The trivial listener class is located exactly where it is needed, without cluttering up the remainder of the project. Moreover, inner classes have a very attractive feature: Their methods can access variables that are declared in surrounding blocks. In this regard, method declarations of inner classes behave similarly to nested blocks.
Recall that a block is a statement group enclosed by braces. If a block is nested inside another, the inner block has access to all variables from the surrounding block:
{// Surrounding block BankAccount account = new BankAccount(); if (...) {// Inner block ... // OK to access variable from surrounding block account.deposit(interest); ... } // End of inner block ... } // End of surrounding block
The same nesting works for inner classes. Except for some technical restrictions, which we will examine later in this section, the methods of an inner class can access the variables from the enclosing scope. This feature is very useful when implementing event handlers. It allows the inner class to access variables without having to pass them as constructor or method parameters.
Let's look at an example. Suppose we want to add interest to a bank account whenever a button is clicked.
Methods of an inner class can access local and instance variables from the surrounding scope.
JButton button = new JButton("Add Interest"); final BankAccount account = new BankAccount(INITIAL_BALANCE); // This inner class is declared in the same method as the account and button variables. class AddInterestListener implements ActionListener { public void actionPerformed(ActionEvent event) { // The listener method accesses the account variable // from the surrounding block double interest = account.getBalance() * INTEREST_RATE / 100; account.deposit(interest); } }; ActionListener listener = new AddInterestListener(); button.addActionListener(listener);
There is a technical wrinkle. An inner class can access surrounding localvariables only if they are declared as final
. That sounds like a restriction, but it is usually not an issue in practice. Keep in mind that an object variable is final
when the variable always refers to the same object. The state of the object can change, but the variable can't refer to a different object. For example, in our program, we never intended to have the account
variable refer to multiple bank accounts, so there was no harm in declaring it as final
.
Local variables that are accessed by an inner class method must be declared as final
.
An inner class can also access instance variables of the surrounding class, again with a restriction. The instance variable must belong to the object that constructed the inner class object. If the inner class object was created inside a static method, it can only access static variables.
Here is the source code for the program.
ch09/button2/InvestmentViewer1.java
1
import java.awt.event.ActionEvent;2
import java.awt.event.ActionListener;3
import javax.swing.JButton;4
import javax.swing.JFrame;5
6
/**7
This program demonstrates how an action listener can access8
a variable from a surrounding block.9
*/10
public class InvestmentViewer111
{12
private static final int FRAME_WIDTH = 120;13
private static final int FRAME_HEIGHT = 60;14
15
private static final double INTEREST_RATE = 10;16
private static final double INITIAL_BALANCE = 1000;17
18
public static void main(String[] args)19
{20
JFrame frame = new JFrame();21
22
// The button to trigger the calculation23
JButton button = new JButton("Add Interest");24
frame.add(button);25
26
// The application adds interest to this bank account27
final BankAccount account = new BankAccount(INITIAL_BALANCE);28
29
class AddInterestListener implements ActionListener30
{31
public void actionPerformed(ActionEvent event)32
{33
// The listener method accesses the account variable34
// from the surrounding block35
double interest = account.getBalance() * INTEREST_RATE / 100;36
account.deposit(interest);37
System.out.println("balance: " + account.getBalance());38
}39
}40
41
ActionListener listener = new AddInterestListener();42
button.addActionListener(listener);43
44
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);45
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);46
frame.setVisible(true);47
}48
}
balance: 1100.0 balance: 1210.0 balance: 1331.0 balance: 1464.1
18. If an inner class accesses a local variable from a surrounding scope, what special rule applies?
In this section, you will learn how to structure a graphical application that contains buttons. We will put a button to work in our simple investment viewer program. Whenever the button is clicked, interest is added to a bank account, and the new balance is displayed (see Figure 7).
First, we construct an object of the JButton
class. Pass the button label to the constructor:
JButton button = new JButton("Add Interest");
We also need a user-interface component that displays a message, namely the current bank balance. Such a component is called a label. You pass the initial message string to the JLabel
constructor, like this:
JLabel label = new JLabel("balance: " + account.getBalance());
The frame of our application contains both the button and the label. However, we cannot simply add both components directly to the frame—they would be placed on top of each other. The solution is to put them into a panel, a container for other user-interface components, and then add the panel to the frame:
JPanel panel = new JPanel(); panel.add(button); panel.add(label); frame.add(panel);
Now we are ready for the hard part—the event listener that handles button clicks. As in the preceding section, it is necessary to provide a class that implements the ActionListener
interface, and to place the button action into the actionPerformed
method. Our listener class adds interest and displays the new balance:
class AddInterestListener implements ActionListener { public void actionPerformed(ActionEvent event) { double interest = account.getBalance() * INTEREST_RATE / 100; account.deposit(interest); label.setText("balance: " + account.getBalance()); } }
You specify button click actions through classes that implement the ActionListener
interface.
There is just a minor technicality. The actionPerformed
method manipulates the account
and label
variables. These are local variables of the main
method of the investment viewer program, not instance variables of the AddInterestListener
class. We therefore need to declare the account
and label
variables as final
so that the action-Performed
method can access them.
Let's put the pieces together.
public static void main(String[] args) { ... JButton button = new JButton("Add Interest"); final BankAccount account = new BankAccount(INITIAL_BALANCE); final JLabel label = new JLabel("balance: " + account.getBalance()); class AddInterestListener implements ActionListener { public void actionPerformed(ActionEvent event) { double interest = account.getBalance() * INTEREST_RATE / 100; account.deposit(interest); label.setText("balance: " + account.getBalance()); } } ActionListener listener = new AddInterestListener(); button.addActionListener(listener); ... }
With a bit of practice, you will learn to glance at this code and translate it into plain English: "When the button is clicked, add interest and set the label text."
Here is the complete program. It demonstrates how to add multiple components to a frame, by using a panel, and how to implement listeners as inner classes.
ch09/button3/InvestmentViewer2.java
1
import java.awt.event.ActionEvent;2
import java.awt.event.ActionListener;3
import javax.swing.JButton;4
import javax.swing.JFrame;5
import javax.swing.JLabel;6
import javax.swing.JPanel;7
import javax.swing.JTextField;8
9
/**10
This program displays the growth of an investment.11
*/12
public class InvestmentViewer213
{14
private static final int FRAME_WIDTH = 400;15
private static final int FRAME_HEIGHT = 100;16
17
private static final double INTEREST_RATE = 10;18
private static final double INITIAL_BALANCE = 1000;19
20
public static void main(String[] args)21
{22
JFrame frame = new JFrame();23
24
// The button to trigger the calculation25
JButton button = new JButton("Add Interest");26
27
// The application adds interest to this bank account28
final BankAccount account = new BankAccount(INITIAL_BALANCE);29
30
// The label for displaying the results31
final JLabel label = new JLabel("balance: " + account.getBalance());32
33
// The panel that holds the user-interface components34
JPanel panel = new JPanel();35
panel.add(button);36
panel.add(label);37
frame.add(panel);38
39
class AddInterestListener implements ActionListener40
{41
public void actionPerformed(ActionEvent event)42
{43
double interest = account.getBalance() * INTEREST_RATE / 100;44
account.deposit(interest);45
label.setText("balance: " + account.getBalance());46
}47
}48
49
ActionListener listener = new AddInterestListener();50
button.addActionListener(listener);51
52
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);53
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);54
frame.setVisible(true);55
}56
}
20. Why was it not necessary to declare the button
variable as final
?
In this section we will study timer events and show how they allow you to implement simple animations.
The Timer
class in the javax.swing
package generates a sequence of action events, spaced apart at even time intervals. (You can think of a timer as an invisible button that is automatically clicked.) This is useful whenever you want to have an object updated in regular intervals. For example, in an animation, you may want to update a scene ten times per second and redisplay the image, to give the illusion of movement.
When you use a timer, you specify the frequency of the events and an object of a class that implements the ActionListener
interface. Place whatever action you want to occur inside the actionPerformed
method. Finally, start the timer.
class MyListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
Action that is executed at each timer event
}
}
MyListener listener = new MyListener();
Timer t = new Timer(interval, listener);
t.start();
A timer generates timer events at fixed intervals.
Then the timer calls the actionPerformed
method of the listener
object every interval
milliseconds.
Our sample program will display a moving rectangle. We first supply a Rectangle-Component
class with a moveBy
method that moves the rectangle by a given amount.
ch09/timer/RectangleComponent.java
1
import java.awt.Graphics;2
import java.awt.Graphics2D;3
import java.awt.Rectangle;4
import javax.swing.JComponent;5
6
/**7
This component displays a rectangle that can be moved.8
*/9
public class RectangleComponent extends JComponent10
{11
private static final int BOX_X = 100;12
private static final int BOX_Y = 100;13
private static final int BOX_WIDTH = 20;14
private static final int BOX_HEIGHT = 30;15
16
private Rectangle box;17
18
public RectangleComponent()19
{20
// The rectangle that the paint method draws21
box = new Rectangle(BOX_X, BOX_Y, BOX_WIDTH, BOX_HEIGHT);22
}23
24
public void paintComponent(Graphics g)25
{26
Graphics2D g2 = (Graphics2D) g;27
28
g2.draw(box);29
}30
31
/**32
Moves the rectangle by a given amount.33
@param x the amount to move in the x-direction34
@param y the amount to move in the y-direction35
*/36
public void moveBy(int dx, int dy)37
{38
box.translate(dx, dy);39
repaint();40
}41
}
Note the call to repaint
in the moveBy
method. This call is necessary to ensure that the component is repainted after the state of the rectangle object has been changed. Keep in mind that the component object does not contain the pixels that show the drawing. The component merely contains a Rectangle
object, which itself contains four coordinate values. Calling translate
updates the rectangle coordinate values. The call to repaint
forces a call to the paintComponent
method. The paintComponent
method redraws the component, causing the rectangle to appear at the updated location.
The repaint
method causes a component to repaint itself. Call repaint
whenever you modify the shapes that the paintComponent
method draws.
The actionPerformed
method of the timer listener simply calls component.moveBy(1
, 1). This moves the rectangle one pixel down and to the right. Since the actionPer-formed
method is called many times per second, the rectangle appears to move smoothly across the frame.
ch09/timer/RectangleMover.java
1
import java.awt.event.ActionEvent;2
import java.awt.event.ActionListener;3
import javax.swing.JFrame;4
import javax.swing.Timer;5
6
/**7
This program moves the rectangle.8
*/9
public class RectangleMover10
{11
private static final int FRAME_WIDTH = 300;12
private static final int FRAME_HEIGHT = 400;13
14
public static void main(String[] args)15
{16
JFrame frame = new JFrame();17
18
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);19
frame.setTitle("An animated rectangle");20
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);21
22
final RectangleComponent component = new RectangleComponent();23
frame.add(component);24
25
frame.setVisible(true);26
27
class TimerListener implements ActionListener28
{29
public void actionPerformed(ActionEvent event)30
{31
component.moveBy(1, 1);32
}33
}34
35
ActionListener listener = new TimerListener();36
37
final int DELAY = 100; // Milliseconds between timer ticks38
Timer t = new Timer(DELAY, listener);39
t.start();40
}41
}
22. What would happen if you omitted the call to repaint
in the moveBy
method?
If you write programs that show drawings, and you want users to manipulate the drawings with a mouse, then you need to process mouse events. Mouse events are more complex than button clicks or timer ticks.
You use a mouse listener to capture mouse events.
A mouse listener must implement the MouseListener
interface, which contains the following five methods:
public interface MouseListener { void mousePressed(MouseEvent event); // Called when a mouse button has been pressed on a component void mouseReleased(MouseEvent event); // Called when a mouse button has been released on a component void mouseClicked(MouseEvent event); // Called when the mouse has been clicked on a component void mouseEntered(MouseEvent event); // Called when the mouse enters a component void mouseExited(MouseEvent event); // Called when the mouse exits a component }
The mousePressed
and mouseReleased
methods are called whenever a mouse button is pressed or released. If a button is pressed and released in quick succession, and the mouse has not moved, then the mouseClicked
method is called as well. The mouseEntered
and mouseExited
methods can be used to paint a user-interface component in a special way whenever the mouse is pointing inside it.
The most commonly used method is mousePressed
. Users generally expect that their actions are processed as soon as the mouse button is pressed.
You add a mouse listener to a component by calling the addMouseListener
method:
public class MyMouseListener implements MouseListener { // Implements five methods } MouseListener listener = new MyMouseListener(); component.addMouseListener(listener);
In our sample program, a user clicks on a component containing a rectangle. Whenever the mouse button is pressed, the rectangle is moved to the mouse location. We first enhance the RectangleComponent
class and add a moveTo
method to move the rectangle to a new position.
ch09/mouse/RectangleComponent.java
1
import java.awt.Graphics;2
import java.awt.Graphics2D;3
import java.awt.Rectangle;4
import javax.swing.JComponent;5
6
/**7
This component displays a rectangle that can be moved.8
*/9
public class RectangleComponent extends JComponent10
{11
private static final int BOX_X = 100;12
private static final int BOX_Y = 100;13
private static final int BOX_WIDTH = 20;14
private static final int BOX_HEIGHT = 30;15
16
private Rectangle box;17
18
public RectangleComponent()19
{20
// The rectangle that the paint method draws21
box = new Rectangle(BOX_X, BOX_Y, BOX_WIDTH, BOX_HEIGHT);22
}23
24
public void paintComponent(Graphics g)25
{26
Graphics2D g2 = (Graphics2D) g;27
28
g2.draw(box);29
}30
31
/**32
Moves the rectangle to the given location.33
@param x the x-position of the new location34
@param y the y-position of the new location35
*/36
public void moveTo(int x, int y)37
{38
box.setLocation(x, y);39
repaint();40
}41
}
Note the call to repaint
in the moveTo
method. As explained in the preceding section, this call causes the component to repaint itself and show the rectangle in the new position.
Now, add a mouse listener to the component. Whenever the mouse is pressed, the listener moves the rectangle to the mouse location.
class MousePressListener implements MouseListener { public void mousePressed(MouseEvent event) { int x = event.getX(); int y = event.getY(); component.moveTo(x, y); } // Do-nothing methods public void mouseReleased(MouseEvent event) {} public void mouseClicked(MouseEvent event) {} public void mouseEntered(MouseEvent event) {} public void mouseExited(MouseEvent event) {} }
It often happens that a particular listener specifies actions only for one or two of the listener methods. Nevertheless, all five methods of the interface must be implemented. The unused methods are simply implemented as do-nothing methods.
Go ahead and run the RectangleComponentViewer
program. Whenever you click the mouse inside the frame, the top-left corner of the rectangle moves to the mouse pointer (see Figure 8).
ch09/mouse/RectangleComponentViewer.java
1
import java.awt.event.MouseListener;2
import java.awt.event.MouseEvent;3
import javax.swing.JFrame;4
5
/**6
This program displays a RectangleComponent.7
*/8
public class RectangleComponentViewer9
{10
private static final int FRAME_WIDTH = 300;11
private static final int FRAME_HEIGHT = 400;12
13
public static void main(String[] args)14
{15
final RectangleComponent component = new RectangleComponent();16
17
// Add mouse press listener18
19
class MousePressListener implements MouseListener20
{21
public void mousePressed(MouseEvent event)22
{23
int x = event.getX();24
int y = event.getY();25
component.moveTo(x, y);26
}27
28
// Do-nothing methods29
public void mouseReleased(MouseEvent event) {}30
public void mouseClicked(MouseEvent event) {}31
public void mouseEntered(MouseEvent event) {}32
public void mouseExited(MouseEvent event) {}33
}34
35
MouseListener listener = new MousePressListener();36
component.addMouseListener(listener);37
38
JFrame frame = new JFrame();39
frame.add(component);40
41
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);42
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);43
frame.setVisible(true);44
}45
}
Why must the MousePressListener
class supply five methods?
Use interfaces for making a service available to multiple classes.
A Java interface type declares methods but does not provide their implementations.
Unlike a class, an interface type provides no implementation.
Use the implements
reserved word to indicate that a class implements an interface type.
Use interface types to make code more reusable.
Describe how to convert between class and interface types.
You can convert from a class type to an interface type, provided the class implements the interface.
You need a cast to convert from an interface type to a class type.
Describe dynamic method lookup and polymorphism.
When the virtual machine calls an instance method, it locates the method of the implicit parameter's class. This is called dynamic method lookup.
Polymorphism denotes the ability to treat objects with differences in behavior in a uniform way.
Describe how to use interface types for providing callbacks.
A callback is a mechanism for specifying code that is executed at a later time.
Use inner classes to limit the scope of a utility class.
An inner class is declared inside another class.
Inner classes are commonly used for utility classes that should not be visible elsewhere in a program.
Use mock objects for supplying test versions of classes.
A mock object provides the same services as another object, but in a simplified manner.
Both the mock class and the actual class implement the same interface.
Recognize the use of events and event listeners in user-interface programming.
User-interface events include key presses, mouse moves, button clicks, menu selections, and so on.
An event listener belongs to a class that is provided by the application programmer. Its methods describe the actions to be taken when an event occurs.
Event sources report on events. When an event occurs, the event source notifies all event listeners.
Use JButton
components for buttons. Attach an ActionListener
to each button.
Implement event listeners as inner classes.
Methods of an inner class can access local and instance variables from the surrounding scope.
Local variables that are accessed by an inner class method must be declared as final
.
Build graphical applications that use buttons.
Use a JPanel
container to group multiple user-interface components together.
You specify button click actions through classes that implement the ActionListener
interface.
Use a timer for drawing animations.
A timer generates timer events at fixed intervals.
The repaint
method causes a component to repaint itself. Call repaint
whenever you modify the shapes that the paintComponent
method draws.
Write programs that process mouse events.
You use a mouse listener to capture mouse events.
java.awt.Component java.awt.event.MouseListener addMouseListener mouseClicked repaint mouseEntered setPreferredSize mouseExited java.awt.Container mousePressed add mouseReleased java.awt.Dimension javax.swing.AbstractButton java.awt.Rectangle addActionListener setLocation javax.swing.JButton java.awt.event.ActionListener javax.swing.JLabel actionPerformed javax.swing.JPanel java.awt.event.MouseEvent javax.swing.Timer getX start getY stop
R9.1 Suppose C
is a class that implements the interfaces I
and J
. Which of the following assignments require a cast?
C c = ...; I i = ...; J j = ...;
c = i;
j = c;
i = j;
R9.2 Suppose C
is a class that implements the interfaces I
and J
, and suppose i
is declared as
I i = new C();
Which of the following statements will throw an exception?
C c = (C) i;
J j = (J) i;
i = (I) null;
R9.3 Suppose the class Sandwich
implements the Edible
interface, and you are given the variable declarations
Sandwich sub = new Sandwich(); Rectangle cerealBox = new Rectangle(5, 10, 20, 30); Edible e = null;
Which of the following assignment statements are legal?
e = sub;
sub = e;
sub = (Sandwich) e;
sub = (Sandwich) cerealBox;
e = cerealBox;
e = (Edible) cerealBox;
e = (Rectangle) cerealBox;
e = (Rectangle) null;
R9.4 How does a cast such as (BankAccount) x
differ from a cast of number values such as (int) x
?
R9.5 The classes Rectangle2D.Double, Ellipse2D.Double
, and Line2D.Double
implement the Shape
interface. The Graphics2D
class depends on the Shape
interface but not on the rectangle, ellipse, and line classes. Draw a UML diagram denoting these facts.
R9.6 Suppose r
contains a reference to a new Rectangle(5, 10, 20, 30)
. Which of the following assignments is legal? (Look inside the API documentation to check which interfaces the Rectangle
class implements.)
Rectangle a = r;
Shape b = r;
String c = r;
ActionListener d = r;
Measurable e = r;
Serializable f = r;
Object g = r;
R9.7 Classes such as Rectangle2D.Double, Ellipse2D.Double
and Line2D.Double
implement the Shape
interface. The Shape
interface has a method
Rectangle getBounds()
that returns a rectangle completely enclosing the shape. Consider the method call:
Shape s = ...; Rectangle r = s.getBounds();
Explain why this is an example of polymorphism.
R9.8 In Java, a method call such as x.f()
uses dynamic method lookup—the exact method to be called depends on the type of the object to which x
refers. Give two kinds of method calls that do not look dynamic method lookup in Java.
R9.9 Suppose you need to process an array of employees to find the average and the highest salaries. Discuss what you need to do to use the implementation of the DataSet
class in Section 9.1 (which processes Measurable
objects). What do you need to do to use the second implementation (in Section 9.4)? Which is easier?
R9.10 What happens if you add a String
object to the implementation of the DataSet
class in Section 9.1? What happens if you add a String
object to a DataSet
object of the implementation in Section 9.4 that uses a RectangleMeasurer
class?
R9.11 How would you reorganize the DataSetTester3
program if you needed to make RectangleMeasurer
into a top-level class (that is, not an inner class)?
R9.12 What is a callback? Can you think of another use for a callback for the DataSet
class? (Hint: Exercise P9.12.)
R9.13 Consider this top-level and inner class. Which variables can the f
method access?
public class T { private int t; public void m(final int x, int y) { int a; final int b; class C implements I { public void f() { ...
} } final int c; ... } }
R9.14 What happens when an inner class tries to access a non-final
local variable? Try it out and explain your findings.
R9.15 How would you reorganize the InvestmentViewer1
program if you needed to make AddInterestListener
into a top-level class (that is, not an inner class)?
R9.16 What is an event object? An event source? An event listener?
R9.17 From a programmer's perspective, what is the most important difference between the user interfaces of a console application and a graphical application?
R9.18 What is the difference between an ActionEvent
and a MouseEvent
?
R9.19 Why does the ActionListener
interface have only one method, whereas the MouseListener
has five methods?
R9.20 Can a class be an event source for multiple event types? If so, give an example.
R9.21 What information does an action event object carry? What additional information does a mouse event object carry?
R9.22 Why are we using inner classes for event listeners? If Java did not have inner classes, could we still implement event listeners? How?
R9.23 What is the difference between the paintComponent
and repaint
methods?
R9.24 What is the difference between a frame and a panel?
P9.1 Have the Die
class of Chapter 6 implement the Measurable
interface. Generate dice, cast them, and add them to the implementation of the DataSet
class in Section 9.1. Display the average.
P9.2 Implement a class Quiz
that implements the Measurable
interface. A quiz has a score and a letter grade (such as B+
). Use the implementation of the DataSet
class in Section 9.1 to process a collection of quizzes. Display the average score and the quiz with the highest score (both letter grade and score).
P9.3 A person has a name and a height in centimeters. Use the implementation of the DataSet
class in Section 9.4 to process a collection of Person
objects. Display the average height and the name of the tallest person.
P9.4 Modify the implementation of the DataSet
class in Section 9.1 (the one processing Measurable
objects) to also compute the minimum data element.
P9.5 Modify the implementation of the DataSet
class in Section 9.4 (the one using a Measurer
object) to also compute the minimum data element.
P9.6 Using a different Measurer
object, process a set of Rectangle
objects to find the rectangle with the largest perimeter.
P9.7 Enhance the DataSet
class so that it can either be used with a Measurer
object or for processing Measurable
objects. Hint: Supply a constructor with no parameters that implements a Measurer
that processes Measurable
objects.
P9.8 Modify the display
method of the LastDigitDistribution
class of Worked Example 9.1 so that it produces a histogram, like this:
0: ************* 1: ****************** 2: *************
Scale the bars so that widest one has length 40.
P9.9 Write a class PrimeSequence
that implements the Sequence
interface of Worked Example 9.1, producing the sequence of prime numbers.
P9.10 Add a method hasNext
to the Sequence
interface of Worked Example 9.1 that returns false
if the sequence has no more values. Implement a class MySequence
producing a sequence of real data of your choice, such as populations of cities or countries, temperatures, or stock prices. Obtain the data from the Internet and reformat the values so that they are placed into an array. Return one value at a time in the next
method, until you reach the end of the data. Your SequenceTester
class should display all data in the sequence and check whether the last digits are randomly distributed.
P9.11 Provide a class FirstDigitDistribution
that works just like the LastDigitDistribution
class of Worked Example 9.1, except that it counts the distribution of the first digit of each value. (It is a well-known fact that the first digits of random values are not uniformly distributed. This fact has been used to detect accounting fraud, when sequences of transaction amounts had an unnatural distribution of their first digits.)
P9.12 Declare an interface Filter
as follows:
public interface Filter { boolean accept(Object x); }
Modify the implementation of the DataSet
class in Section 9.4 to use both a Measurer
and a Filter
object. Only objects that the filter accepts should be processed. Demonstrate your modification by having a data set process a collection of bank accounts, filtering out all accounts with balances less than $1,000.
P9.13 The standard Java library provides a Comparable
interface:
public interface Comparable { /** Compares this object with another. @param other the object to be compared @return a negative integer, zero, or a positive integer if this object is less than, equal to, or greater than, other */ public int compareTo(Object other); }
Modify the DataSet
class of Section 9.1 to accept Comparable
objects. With this interface, it is no longer meaningful to compute the average. The DataSet
class should record the minimum and maximum data values. Test your modified DataSet
class by adding a number of String
objects. (The String
class implements the Comparable
inter-face.)
P9.14 Modify the Coin
class to have it implement the Comparable
interface described in Exercise P9.13.
P9.15 The System.out.printf
method has predefined formats for printing integers, floating-point numbers, and other data types. But it is also extensible. If you use the S
format, you can print any class that implements the Formattable
interface. That interface has a single method:
void formatTo(Formatter formatter, int flags, int width, int precision)
In this exercise, you should make the BankAccount
class implement the Formattable
interface. Ignore the flags and precision and simply format the bank balance, using the given width. In order to achieve this task, you need to get an Appendable
reference like this:
Appendable a = formatter.out();
Appendable
is another interface with a method
void append(CharSequence sequence)
CharSequence
is yet another interface that is implemented by (among others) the String
class. Construct a string by first converting the bank balance into a string and then padding it with spaces so that it has the desired width. Pass that string to the append
method.
P9.16 Enhance the formatTo
method of Exercise P9.15 by taking into account the precision.
P9.17 Consider the task of writing a program that plays TicTacToe against a human opponent. A user interface TicTacToeUI
reads the user's moves and displays the computer's moves and the board. A class TicTacToeStrategy
determines the next move that the computer makes. A class TicTacToeBoard
represents the current state of the board. Complete all classes except for the strategy class. Instead, use a mock class that simply picks the first available empty square.
P9.18 Consider the task of translating a plain text book from Project Gutenberg (http://gutenberg.org
) to HTML. For example, here is the start of the first chapter of Tolstoy's Anna Karenina:
Chapter 1 Happy families are all alike; every unhappy family is unhappy in its own way. Everything was in confusion in the Oblonskys' house. The wife had discovered that the husband was carrying on an intrigue with a French girl, who had been a governess in their family, and she had announced to her husband that she could not go on living in the same house with him ...
The equivalent HTML is:
<h1>Chapter 1</h1> <p>Happy families are all alike; every unhappy family is unhappy in its own way.</p> <p>Everything was in confusion in the Oblonskys' house. The wife had discovered that the husband was carrying on an intrigue with
a French girl, who had been a governess in their family, and she had announced to her husband that she could not go on living in the same house with him ...</p>
The HTML conversion can be carried out in two steps. First, the plain text is assembled into segments, blocks of text of the same kind (heading, paragraph, and so on). Then each segment is converted, by surrounding it with the HTML tags and converting special characters.
Plain Text | HTML |
---|---|
" " |
|
' ' |
|
— |
|
< |
|
> |
|
& |
|
Fetching the text from the Internet and breaking it into segments is a challenging task. Provide an interface and a mock implementation. Combine it with a class that uses the mock implementation to finish the formatting task.
P9.19 Write a method randomShape
that randomly generates objects implementing the Shape
interface: some mixture of rectangles, ellipses, and lines, with random positions. Call it 10 times and draw all of them.
P9.20 Enhance the ButtonViewer
program so that it prints a message "I was clicked n times!" whenever the button is clicked. The value n should be incremented with each click.
P9.21 Enhance the ButtonViewer
program so that it has two buttons, each of which prints a message "I was clicked n times!" whenever the button is clicked. Each button should have a separate click count.
P9.22 Enhance the ButtonViewer
program so that it has two buttons labeled A and B, each of which prints a message "Button x was clicked!", where x is A or B.
P9.23 Implement a ButtonViewer
program as in Exercise P9.22, using only a single listener class.
P9.24 Enhance the ButtonViewer
program so that it prints the time at which the button was clicked.
P9.25 Implement the AddInterestListener
in the InvestmentViewer1
program as a regular class (that is, not an inner class). Hint: Store a reference to the bank account. Add a constructor to the listener class that sets the reference.
P9.26 Implement the AddInterestListener
in the InvestmentViewer2
program as a regular class (that is, not an inner class). Hint: Store references to the bank account and the label in the listener. Add a constructor to the listener class that sets the references.
P9.27 Write a program that demonstrates the growth of a roach population. Start with two roaches and double the number of roaches with each button click.
P9.28 Write a program that uses a timer to print the current time once a second. Hint: The following code prints the current time:
Date now = new Date(); System.out.println(now);
The Date
class is in the java.util
package.
P9.29 Change the RectangleComponent
for the animation program in Section 9.10 so that the rectangle bounces off the edges of the component rather than simply moving outside.
P9.30 Write a program that animates a car so that it moves across a frame.
P9.31 Write a program that animates two cars moving across a frame in opposite directions (but at different heights so that they don't collide.)
P9.32 Change the RectangleComponent
for the mouse listener program in Section 9.11 so that a new rectangle is added to the component whenever the mouse is clicked. Hint: Keep an ArrayList<Rectangle>
and draw all rectangles in the paintComponent
method.
P9.33 Write a program that prompts the user to enter the x- and y-positions of the center and a radius, using JOptionPane
dialogs.When the user clicks a "Draw" button, draw a circle with that center and radius in a component.
P9.34 Write a program that allows the user to specify a circle by typing the radius in a JOptionPane
and then clicking on the center. Note that you don't need a "Draw" button.
P9.35 Write a program that allows the user to specify a circle with two mouse presses, the first one on the center and the second on a point on the periphery. Hint: In the mouse press handler, you must keep track of whether you already received the center point in a previous mouse press.
Project 9.1 Design an interface MoveableShape
that can be used as a generic mechanism for animating a shape. A moveable shape must have two methods: move
and draw
. Write a generic AnimationPanel
that paints and moves any MoveableShape
(or array list of Move-ableShape
objects if you covered Chapter 7). Supply moveable rectangle and car shapes.
Project 9.2 Your task is to design a general program for managing board games with two players. Your program should be flexible enough to handle games such as tic-tac-toe, chess, or the Game of Nim of Project 6.2.
Design an interface Game
that describes a board game. Think about what your program needs to do. It asks the first player to input a move—a string in a game-specific format, such as Be3
in chess. Your program knows nothing about specific games, so the Game
interface must have a method such as
boolean isValidMove(String move)
Once the move is found to be valid, it needs to be executed—the interface needs another method executeMove
. Next, your program needs to check whether the game is over. If not, the other player's move is processed. You should also provide some mechanism for displaying the current state of the board.
Design the Game
interface and provide two implementations of your choice—such as Nim
and Chess
(or TicTacToe
if you are less ambitious). Your GamePlayer
class should manage a Game
reference without knowing which game is played, and process the moves from both players. Supply two programs that differ only in the initialization of the Game
reference.
It must implement the Measurable
interface, and its getMeasure
method must return the population.
The Object
class doesn't have a getMeasure
method, and the add
method invokes the getMeasure
method.
Only if x
actually refers to a BankAccount
object.
No—a Coin
reference can be converted to a Measurable
reference, but if you attempt to cast that reference to a BankAccount
, an exception occurs.
Measurable
is an interface. Interfaces have no instance variables and no method implementations.
That variable never refers to a Measurable
object. It refers to an object of some class—a class that implements the Measurable
interface.
The code fragment prints 500.05. Each call to add
results in a call x.getMeasure()
. In the first call, x
is a BankAccount
. In the second call, x
is a Coin
. A different getMeasure
method is called in each case. The first call returns the account balance, the second one the coin value.
The String
class doesn't implement the Measurable
interface.
Implement a class StringMeasurer
that implements the Measurer
interface.
A measurer measures an object, whereas getMeasure
measures "itself", that is, the implicit parameter.
Inner classes are convenient for insignificant classes. Also, their methods can access local and instance variables from the surrounding scope.
Four: one for the outer class, one for the inner class, and two for the DataSet
and Measurer
classes.
You want to implement the GradingProgram
class in terms of that interface so that it doesn't have to change when you switch between the mock class and the actual class.
Because the developer of GradingProgram
doesn't have to wait for the GradeBook
class to be complete.
The button
object is the event source. The listener
object is the event listener.
The ClickListener
class implements the ActionListener
interface.
Direct access is simpler than the alternative—passing the variable as a parameter to a constructor or method.
The local variable must be declared as final
.
First add label
to the panel
, then add button
.
The actionPerformed
method does not access that variable.
The timer needs to call some method whenever the time interval expires. It calls the actionPerformed
method of the listener object.
The moved rectangles won't be painted, and the rectangle will appear to be stationary until the frame is repainted for an external reason.
Because you know the current mouse position, not the amount by which the mouse has moved.
It implements the MouseListener
interface, which has five methods.
52.15.129.90