Using Abstract Classes and Interfaces
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.
Using Abstract Classes
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); }
Warning |
If you omit the abstract modifier from the class declaration, the Java compiler coughs up an error message to remind you that the class must be declared abstract. |
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.
DESIGN PATTERN |
The Abstract Factory pattern One common use for abstract classes is to provide a way to obtain an instance of one of several subclasses when you don't know which subclass you need in advance. To do this, you can create an Abstract Factory class that has one or more methods that return subclasses of the abstract class. For example, suppose you want to create a Ball object, but you want to let the user choose whether to create a SoftBall or a BaseBall. To use the Abstract Factory pattern, you create a class (I call it BallFactory) that has a method named getBallInstance. This method accepts a String parameter that's set to “BaseBall” if you want a BaseBall object or “SoftBall” if you want a SoftBall object. Here's the factory class: class BallFactoryInstance { public static Ball getBall(String t) { if (s.equalsIgnoreCase("BaseBall")) return new BaseBall(); if (s.equalsIgnoreCase("SoftBall")) return new SoftBall(); return null; } } Then, assuming the String variable userChoice has been set according to the user's choice, you can create the selected type of ball object like this: Ball b = BallFactory.getBallInstance(use rChoice); In an actual application, using an enum variable is better rather than a String variable to indicate the type of object to be returned. |
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 non-abstract method. (If it doesn't, the subclass is also abstract, so it too cannot be instantiated.)
Tip |
Abstract classes are useful when you want to create a generic type that is used as the superclass for two or more subclasses, but the superclass itself doesn't represent an actual object. For example, if all employees are either salaried or hourly, creating an abstract Employee class makes sense, and then use it as the base class for the SalariedEmployee and HourlyEmployee subclasses. |
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.)
TECHNICAL STAUFF A private method can't be abstract. That only makes sense, because a subclass can't override a private method, and abstract methods must be overridden.
- Although you can't create an instance of an abstract class, you can declare a variable using an abstract class as its type. Then use the variable to refer to an instance of any of the subclasses of the abstract class.
- A class can't specify both abstract and final. That would cause one of those logical paradoxes that result in the complete annihilation of the entire universe. Well, hopefully the effect would be localized. But the point is that because an abstract class can only be used if you subclass it, and a final class can't be subclassed, letting you specify both abstract and final for the same class doesn't make sense.
TECHNICAL STAUFF |
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. For example, the InputStream class (used by System.in) is abstract. |
Using Interfaces
An interface is similar to an abstract class. However, an interface can only include 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.
Tip |
Interfaces have two advantages over inheritance:
|
The following sections describe the details of creating and using interfaces.
Creating a basic interface
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:
- The interface itself is declared as 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 using 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.
Tip 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. For example: playable, readable, drivable, and stoppable. This type of adjective is commonly used for interface names.
- Another common way to name interfaces is to combine an adjective with a noun to indicate that the interface adds some capability to a particular type of object. For example, you can call an interface that provides methods unique to card games CardGame. This interface might have methods such as deal, shuffle, and getHand.
-
TECHNICAL STAUFF 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. However, that's considered bad form, because it might indicate that you think the default is private and not abstract.
Implementing an interface
To implement an interface, a class must do two things:
- It must specify an implements clause on its class declaration.
- It must provide an implementation for every method declared by the interface.
For example, 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.
Tip |
A class can implement more than one interface: 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.
Tip |
A class can possibly inherit a superclass and implement one or more interfaces. Here's an example: public class Poker extends Game implements Playable, CardGame { // inherits all members of the Game class // must implement methods of the Playable // and CardGame interfaces } |
Using an interface as a type
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 you have an interface named Dealable that defines a method named deal that accepts the number of cards to deal as a parameter:
public interface Dealable { void deal(int cards); }
Now, suppose 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 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.
More Things You Can Do with Interfaces
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….
Adding fields to an interface
Besides abstract methods, an interface can also 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.
TECHNICAL STAUFF |
Note that interface fields are automatically assumed to be static, final, and public. You can include these keywords when you create interface constants, but you don't have to. |
Extending interfaces
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 its fields and methods and the fields and methods of 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.
DESIGN PATTERN |
The Marker Interface pattern A marker interface is an interface that doesn't have any members. Its sole purpose in life is to identify a class as belonging to a set of classes that possess some capability or have some characteristic in common. The best-known example of a marker interface is the Java API Cloneable interface. It marks classes that can be cloned. The Object class, which all classes ultimately inherit, provides a method named clone that can be used to create a copy of the object. However, you're only allowed to call the clone method if the object implements the Cloneable interface. If you try to call clone for an object that doesn't implement Cloneable, CloneNotSupported Exception is thrown. (For more information about the clone method, refer to Book III, Chapter 6.) Here's the actual code for the Cloneable interface: public interface Cloneable { } In some cases, you might find a use for marker interfaces in your own application. For example, if you're working on a series of classes for creating games, you might create a marker interface named Winnable to distinguish games that have a winner from games that you just play for enjoyment. |
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.
Using interfaces for callbacks
In the theater, a callback is when you show up for an initial audition, they like what they see, and so they tell you 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 call-back is more like when you need to get a hold of someone on the phone, and you call them when you know they aren't there and leave your phone number on their voicemail so they can call you back.
Callbacks are handled in Java using 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 VI. For now, I look at callbacks 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:
- The first parameter is an int value that represents how often the timer events occur.
- The second parameter is an object that implements the Action-Listener 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 make use of this parameter here. (But you do use the ActionEvent class in Book VI.)
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 one-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.
Figure 5-1: The Tick Tock application in action.
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:
→ 1 |
The ActionListener interface is part of the java.awt. event package, so this import statement is required. |
→ 2 |
The Timer class is part of the javax.swing package, so this import statement is required. |
→ 10 |
This statement creates a new Timer object. The timer's interval is set to 1,000 milliseconds-which is equivalent to one 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. |
→ 11 |
This statement calls the start method to kick the timer into action. |
→ 15 |
The 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 included this dialog box to give the user a way to end the program. But in reality, I used it to give the timer some time to run. If you just end the main method after starting the timer, the application ends, which kills the timer. By using JOptionPane here, the application continues to run as long as the dialog box is displayed. (For more information about JOptionPane, see Book II, Chapter 2.) |
→ 20 |
The declaration of the Ticker class, which implements the ActionListener interface. |
→ 22 |
A private boolean class field that's used to keep track of whether the Ticker displays Tick… or Tock… Each time the actionPerformed method is called, this field is toggled. |
→ 24 |
The actionPerformed method, which is called at each timer interval. |
→ 28 |
Prints Tick… on the console if tick is true. |
→ 32 |
Prints Tock… on the console if tick is false. |
→ 34 |
Toggles the value of the tick variable. In other words, if tick is true, it's set to false. If tick is false, it's set to true. |