Chapter 5
IN THIS CHAPTER
Understanding abstract methods and classes
Using basic interfaces
Using interfaces as types
Adding constants to an interface
Inheriting interfaces
Working with callbacks
Using default methods
In this chapter, you find out how to use two similar but subtly distinct features: abstract classes and interfaces. Both let you declare the signatures of the methods and fields that a class implements separately from the class itself. Abstract classes accomplish this by way of inheritance. Interfaces do it without using inheritance, but the effect is similar. This chapter also covers a feature called default methods, which are designed to make interfaces easier to work with.
Java lets you declare that a method or an entire class is abstract, which means that the method has no body. An abstract method is just a prototype for a method: a return type, a name, a list of parameters, and (optionally) a throws
clause.
To create an abstract method, you specify the modifier abstract
and replace the method body with a semicolon:
public abstract int hit(int batSpeed);
Here the method named hit
is declared as an abstract method that returns an int
value and accepts an int
parameter.
A class that contains at least one abstract method is called an abstract class and must be declared with the abstract
modifier on the class declaration. For example:
public abstract class Ball
{
public abstract int hit(int batSpeed);
}
An abstract class can’t be instantiated. Thus, given the preceding declaration, the following line doesn’t compile:
Ball b = new Ball(); // error: Ball is abstract
The problem here isn’t with declaring the variable b
as a Ball
; it’s using the new
keyword with the Ball
class in an attempt to create a Ball
object. Because Ball
is an abstract class, you can use it to create an object instance.
You can create a subclass from an abstract class like this:
public class BaseBall extends Ball
{
public int hit(int batSpeed)
{
// code that implements the hit method goes here
}
}
When you subclass an abstract class, the subclass must provide an implementation for each abstract method in the abstract class. In other words, it must override each abstract method with a nonabstract method. (If it doesn’t, the subclass is also abstract, so it can’t be instantiated either.)
Here are a few additional points to ponder concerning abstract classes:
Not all the methods in an abstract class have to be abstract. A class can provide an implementation for some of its methods but not others. In fact, even if a class doesn’t have any abstract methods, you can still declare it as abstract. (In that case, the class can’t be instantiated.)
A private method can’t be abstract. That makes sense, because a subclass can’t override a private method, and abstract methods must be overridden.
A class can’t specify both abstract
and final
. That would cause one of those logical paradoxes that result in the annihilation of the universe. Well, ideally, the effect would be localized. But the point is that because an abstract
class can be used only if you subclass it, and because a final
class can’t be subclassed, letting you specify both abstract
and final
for the same class doesn’t make sense.
Abstract classes are used extensively in the Java API. Many of the abstract classes have names that begin with Abstract
— such as AbstractBorder
, AbstractCollection
, and AbstractMap
— but most of the abstract classes don’t. The InputStream
class (used by System.in
) is abstract, for example.
An interface is similar to an abstract class, but an interface can include only abstract methods and final fields (constants), and an interface can’t be used as a base class. A class implements an interface by providing code for each method declared by the interface.
The following sections describe the details of creating and using interfaces.
Here’s a basic interface that defines a single method, named Playable
, that includes a single method named play
:
public interface Playable
{
void play();
}
This interface declares that any class that implements the Playable
interface must provide an implementation for a method named play
that accepts no parameters and doesn’t return a value.
This interface has a few interesting details:
public
so that it can be used by other classes. Like a public class, a public interface must be declared in a file with the same name. Thus this interface must be in a file named Playable.java
.The name of the interface (Playable
) is an adjective. Most interfaces are named with adjectives rather than nouns because they describe some additional capability or quality of the classes that implement the interface. Thus classes that implement the Playable
interface represent objects that can be played.
In case you haven’t been to English class in a while, an adjective is a word that modifies a noun. You can convert many verbs to adjectives by adding -able to the end of the word — playable, readable, drivable, and stoppable, for example. This type of adjective is commonly used for interface names.
CardGame
, and this interface might have methods such as deal
, shuffle
, and getHand
. All the methods in an interface are assumed to be public and abstract. If you want, you can code the public
and abstract
keywords on interface methods. That kind of coding is considered to be bad form, however, because it might indicate that you think the default is private and not abstract.
To implement an interface, a class must do two things:
implements
clause on its class declaration.Here’s a class that implements the Playable
interface:
public class TicTacToe implements Playable
{
// additional fields and methods go here
public void play()
{
// code that plays the game goes here
}
// additional fields and methods go here
}
Here the declaration for the TicTacToe
class specifies implements Playable
. Then the body of the class includes an implementation of the play
method.
public class Hearts implements Playable, CardGame
{
// must implement methods of the Playable
// and CardGame interfaces
}
Here, the Hearts
class implements two interfaces: Playable
and CardGame
.
public class Poker extends Game
implements Playable, CardGame
{
// inherits all members of the Game class
// must implement methods of the Playable
// and CardGame interfaces
}
In Java, an interface is a kind of type, just like a class. As a result, you can use an interface as the type for a variable, parameter, or method return value.
Consider this snippet of code:
Playable game = getGame();
game.play();
Here I assume that the getGame
method returns an object that implements the Playable
interface. This object is assigned to a variable of type Playable
in the first statement. Then the second statement calls the object’s play
method.
For another (slightly more complex) example, suppose that you have an interface named Dealable
defining a method named deal
that accepts the number of cards to deal as a parameter:
public interface Dealable
{
void deal(int cards);
}
Now suppose that you have a method called startGame
that accepts two parameters: a Dealable
object and a String
that indicates what game to play. This method might look something like this:
private void startGame(Dealable deck, String game)
{
if (game.equals("Poker"))
deck.deal(5);
else if (game.equals("Hearts"))
deck.deal(13);
else if (game.equals("Gin"))
deck.deal(10);
}
Assuming that you also have a class named CardDeck
that implements the Dealable
interface, you might use a statement like this example to start a game of Hearts:
Dealable d = new CardDeck();
startGame(d, "Hearts");
Notice that the variable d
is declared as a Dealable
. You could just as easily declare it as a CardDeck
:
CardDeck d = new CardDeck();
startGame(d, "Hearts");
Because the CardDeck
class implements the Dealable
interface, it can be passed as a parameter to the startGame
method.
There’s more to interfaces than just creating abstract methods. The following sections describe some additional interesting things you can do with interfaces. Read on….
Besides abstract methods, an interface can include final fields — that is, constants. Interface fields are used to provide constant values that are related to the interface. For example:
public interface GolfClub
{
int DRIVER = 1;
int SPOON = 2;
int NIBLICK = 3;
int MASHIE = 4;
}
Here any class that implements the GolfClub
interface has these four fields (that is, constants) available.
You can extend interfaces by using the extends
keyword. An interface that extends an existing interface is called a subinterface, and the interface being extended is called the superinterface.
When you use the extends
keyword with interfaces, all the fields and methods of the superinterface are effectively copied into the subinterface. Thus the subinterface consists of a combination of the fields and methods in the superinterface plus the fields and methods defined for the subinterface.
Here’s an example:
public interface ThrowableBall
{
void throwBall();
void catchBall();
}
public interface KickableBall
{
void kickBall();
void catchBall();
}
public interface PlayableBall
extends ThrowableBall, KickableBall
{
void dropBall();
}
Here three interfaces are declared. The first, named ThrowableBall
, defines two methods: throwBall
and catchBall
. The second, named KickableBall
, also defines two methods: kickBall
and catchBall
. The third, named PlayableBall
, extends ThrowableBall
and KickableBall
, and adds a method of its own named dropBall
.
Thus any class that implements the PlayableBall
interface must provide an implementation for four methods: throwBall
, catchBall
, kickBall
, and dropBall
. Note that because the catchBall
methods defined by the ThrowableBall
and KickableBall
interfaces have the same signature, only one version of the catchBall
method is included in the PlayableBall
interface.
In the theater, a callback is when you show up for an initial audition, they like what they see, and they tell you that they want you to come back so they can have another look.
In Java, a callback is sort of like that. It’s a programming technique in which an object lets another object know that the second object should call one of the first object’s methods whenever a certain event happens. The first object is called an event listener because it waits patiently until the other object calls it. The second object is called the event source because it’s the source of events that result in calls to the listener.
Okay, my theater analogy was a bit of a stretch. Callbacks in Java aren’t really that much like callbacks when you’re auditioning for a big part. A callback is more like when you need to get hold of someone on the phone, and you call him when you know he isn’t there and leave your phone number on his voicemail so he can call you back.
Callbacks are handled in Java by a set of interfaces designed for this purpose. The most common use of callbacks is in graphical applications built with Swing, where you create event listeners that handle user-interface events, such as mouse clicks.
You find out all about Swing in Book 6. For now, I look at callbacks by using the Timer
class, which is part of the javax.Swing
package. This class implements a basic timer that generates events at regular intervals — and lets you set up a listener object to handle these events. The listener object must implement the ActionListener
interface, which defines a method named actionPerformed
that’s called for each timer event.
The Timer
class constructor accepts two parameters:
int
value that represents how often the timer events occur.ActionListener
interface. This object’s actionPerformed
method is called when each timer event occurs.The ActionListener
interface is defined in the java.awt.event
package. It includes the following code:
public interface ActionListener extends EventListener {
/**
* Invoked when an action occurs.
*/
public void actionPerformed(ActionEvent e);
}
As you can see, the ActionListener
interface consists of a single method named actionPerformed
. It receives a parameter of type ActionEvent
, but you don’t use this parameter here. (You do use the ActionEvent
class in Book 6.)
The Timer
class has about 20 methods, but I talk about only one of them here: start
, which sets the timer in motion. This method doesn’t require any parameters and doesn’t return a value.
Listing 5-1 shows a program that uses the Timer
class to alternately display the messages Tick
… and Tock
… on the console at 1-second intervals. The JOptionPane
class is used to display a dialog box; the program runs until the user clicks the OK button in this box. Figure 5-1 shows the Tick Tock program in action. (Actually it takes a while for the JOptionPane
class to shut down the timer, so you’ll see a few extra tick-tocks after clicking OK.)
LISTING 5-1 The Tick Tock Program
import java.awt.event.*; →1
import javax.swing.*; →2
public class TickTock
{
public static void main(String[] args)
{
// create a timer that calls the Ticker class
// at one second intervals
Timer t = new Timer(1000, new Ticker()); →10
t.start(); →11
// display a message box to prevent the
// program from ending immediately
JOptionPane.showMessageDialog(null, →15
"Click OK to exit program");
}
}
class Ticker implements ActionListener →20
{
private boolean tick = true; →22
public void actionPerformed(ActionEvent event) →24
{
if (tick)
{
System.out.println("Tick…"); →28
}
else
{
System.out.println("Tock…"); →32
}
tick = !tick; →34
}
}
The following paragraphs describe the important details of this program’s operation:
ActionListener
interface is part of the java.awt.event
package, so this import
statement is required.Timer
class is part of the javax.swing
package, so this import
statement is required.Timer
object. The timer’s interval is set to 1,000 milliseconds — which is equivalent to 1 second. A new instance of the Ticker
class is passed as the second parameter. The timer calls this object’s actionPerformed
method at each timer tick — in other words, once per second.start
method to kick the timer into action.JOptionPane
class is used to display a dialog box that tells the user to click the OK button to stop the application. You might think I include this dialog box to give the user a way to end the program. In reality, I use it to give the timer some time to run. If I just end the main
method after starting the timer, the application ends, which kills the timer. Because I use JOptionPane
here, the application continues to run as long as the dialog box is displayed. (For more information about JOptionPane
, see Book 2, Chapter 2.)Ticker
class, which implements the ActionListener
interface.Ticker
displays Tick…
or Tock…
Each time the actionPerformed
method is called, this field is toggled.actionPerformed
method, which is called at each timer interval.Tick…
on the console if tick
is true.Tock…
on the console if tick
is false.tick
variable. In other words, if tick
is true, it’s set to false
. If tick
is false, it’s set to true
.Although interfaces are an incredibly useful feature of Java, they have an inherent limitation: After you define an interface and then build classes that implement the interface, there’s no easy way to modify the interface by adding additional methods to it.
For example, suppose you have created the following interface:
public interface Playable
{
void play();
}
You then build several classes that implement this interface. Here’s a simple example:
class Game implements Playable
{
public void play()
{
System.out.println("Good luck!");
}
}
This is a pretty pointless game, of course; it simply prints the message “Good luck!” whenever the play
method is called.
Now suppose that you decide that the Playable
interface should have an additional feature — specifically, you want to add the ability to end the game by calling a method named quit
.
You’d be tempted to just add the new method to the existing interface, like this:
public interface Playable
{
void play();
void quit();
}
Unfortunately, however, doing so will break the Game
class because it doesn’t provide an implementation of the quit
method.
You could, of course, modify the Game
class by adding an implementation of the quit
method. But what if you have written dozens, or even hundreds, of classes that implement Playable
? As you can imagine, once an interface has become popular, it becomes nearly impossible to modify.
To alleviate this problem, Java 1.8 introduces a new type of interface method called a default method, which supplies code that will be used as the implementation of the method for any classes that implement the interface but do not themselves provide an implementation for the default method.
Thus, Java 8 allows you to safely add the quit
method to the Playable
interface by specifying it as a default method, like this:
interface Playable
{
void play();
default void quit()
{
System.out.println("Sorry, quitting is not allowed.");
}
}
Here the Playable
interface specifies that if an implementing class does not provide an implementation of the quit
method, the default method will be used. In this case, the default method simply prints the message "Sorry, quitting is not allowed."
Note that the preceding example won’t compile on versions of Java prior to 1.8. If you get an error message when you try to compile an interface that uses the default
keyword, check your Java version to make sure you’re running version 1.8 or later.
Here’s a complete example that uses the Playable
interface and its default method:
public class TestLambdaCollection
{
public static void main(String[] args)
{
Game g = new Game();
g.play();
g.quit();
}
}
interface Playable
{
void play();
default void quit()
{
System.out.println("Sorry, quitting is not allowed.");
}
}
class Game implements Playable
{
public void play()
{
System.out.println("Good luck!");
}
}
When you run this program, the following will be displayed on the console:
Good luck!
Sorry, quitting is not allowed.