Chapter 4
IN THIS CHAPTER
Explaining inheritance
Creating subclasses
Using protected access
Creating final classes
Demystifying polymorphism
Creating custom exception classes
As you find out in Book 3, Chapter 1 , a Java class can be based on another class. Then the class becomes like a child to the parent class: It inherits all the characteristics of the parent class, good and bad. All the fields and methods of the parent class are passed on to the child class. The child class can use these fields or methods as they are, or it can override them to provide its own versions. In addition, the child class can add fields or methods of its own.
In this chapter, you discover how this magic works, along with the basics of creating and using Java classes that inherit other classes. You also find out a few fancy tricks that help you get the most out of inheritance.
The word inheritance conjures up several noncomputer meanings:
In Java, inheritance refers to a feature of object-oriented programming that lets you create classes that are derived from other classes. A class that’s based on another class is said to inherit the other class. The class that is inherited is called the parent class, the base class, or the superclass. The class that does the inheriting is called the child class, the derived class, or the subclass.
You need to know a few important things about inheritance:
play
, for example, but each class is free to provide its own implementation of the
play
method. In this case, all classes that extend the base class provide their own implementation of the
play
method.Inheritance is best used to implement is-a-type-of relationships. Here are a few examples: Solitaire is a type of game; a truck is a type of vehicle; an invoice is a type of transaction. In each case, a particular kind of object is a specific type of a more general category of objects.
The following sections provide more examples that help illustrate these points.
Inheritance is often explained in terms of real-world objects such as cars and motorcycles or birds and reptiles. Consider various types of vehicles. Cars and motorcycles are two distinct types of vehicles. If you’re writing software that represents vehicles, you could start by creating a class called
Vehicle
that would describe the features that are common to all types of vehicles, such as wheels; a driver; the ability to carry passengers; and the ability to perform actions such as driving, stopping, turning, and crashing.
A motorcycle is a type of vehicle that further refines the
Vehicle
class. The
Motorcycle
class would inherit the
Vehicle
class, so it would have wheels; a driver; possibly passengers; and the ability to drive, stop, turn, and crash. In addition, it would have features that differentiate it from other types of vehicles, such as two wheels and handlebars used for steering control.
A car is also a type of vehicle. The
Car
class would inherit the
Vehicle
class, so it too would have wheels; a driver; possibly some passengers; and the ability to drive, stop, turn, and crash. Also, it would have some features of its own, such as four wheels, a steering wheel, seat belts and air bags, and an optional automatic transmission.
Because you’re unlikely ever to write a program that simulates cars, motorcycles, and other vehicles, take a look at a more common example: games. Suppose that you want to develop a series of board games such as Life, Sorry!, and Monopoly. Most board games have certain features in common:
Each specific type of game has these basic characteristics but adds features of its own. The game Life adds features such as money, insurance policies, spouses, children, and a fancy spinner in the middle of the board. Sorry! has cards that you draw to determine each move and safety zones within which other players can’t attack you. Monopoly has Chance and Community Chest cards, properties, houses, hotels, and money.
If you were designing classes for these games, you might create a generic
BoardGame
class that defines the basic features common to all board games and then use it as the base class for classes that represent specific board games, such as
LifeGame
,
SorryGame
, and
MonopolyGame
.
If vehicles or games don’t make the point clear enough, here’s an example from the world of business. Suppose that you’re designing a payroll system, and you’re working on the classes that represent the employees. You realize that the payroll includes two types of employees: salaried employees and hourly employees. So you decide to create two classes, sensibly named
SalariedEmployee
and
HourlyEmployee
.
You quickly discover that most of the work done by these two classes is identical. Both types of employees have names, addresses, Social Security numbers, totals for how much they’ve been paid for the year, how much tax has been withheld, and so on.
The employee types also have important differences. The most obvious one is that the salaried employees have an annual salary, and the hourly employees have an hourly pay rate. Also, hourly employees have a schedule that changes week to week, and salaried employees may have a benefit plan that isn’t offered to hourly employees.
Thus you decide to create three classes instead of just two. A class named
Employee
handles all the features that are common to both types of employees; then this class is the base class for the
SalariedEmployee
and
Hourly Employee
classes. These classes provide the additional features that distinguish salaried employees from hourly employees.
One of the most important aspects of inheritance is that a class derived from a base class can in turn be used as the base class for another derived class. Thus you can use inheritance to form a hierarchy of classes.
You’ve already seen how an
Employee
class can be used as a base class to create two types of subclasses: a
SalariedEmployee
class for salaried employees and an
HourlyEmployee
class for hourly employees. Suppose that salaried employees fall into two categories: management and sales. Then you could use the
SalariedEmployee
class as the base class for two more classes:
Manager
and
SalesPerson
.
Thus, a
Manager
is a type of
SalariedEmployee
. Because a
SalariedEmployee
is a type of
Employee
, a
Manager
is also a type of
Employee
.
The basic procedure for creating a subclass is simple: You just use the
extends
keyword on the declaration for the subclass. The basic format of a class declaration for a class that inherits a base class is this:
public class ClassName
extends BaseClass
{
// class body goes here
}
Suppose that you have a class named
Ball
that defines a basic ball, and you want to create a subclass named
BouncingBall
that adds the ability to bounce:
public class BouncingBall extends Ball
{
// methods and fields that add the ability to bounce
// to a basic Ball object:
public void bounce()
{
// the bounce method
}
}
}
Here I’m creating a class named
BouncingBall
that extends the
Ball
class. (Extends
is Java’s word for inherits.
)
The subclass automatically has all the methods and fields of the class it extends. Thus, if the
Ball
class has fields named
size
and
weight
, the
BouncingBall
class has those fields too. Likewise, if the
Ball
class has a method named
throw
, the
BouncingBall
class gets that method too.
You need to know some important details to use inheritance properly:
A subclass inherits all the members from its base class. Constructors are not considered to be members, however. As a result, a subclass does not inherit constructors from its base class.
public
or
private
) of any members inherited from the base class is the same in the subclass. That means that you can’t access from the subclass methods or fields that are declared in the base class as
private
.protected
hides fields and methods from other classes but makes them available to subclasses. For more information, see the section “Protecting Your Members
,” later in this chapter.private
or
protected
, to a subclass. The
BouncingBall
class shown earlier in this section, for example, adds a public method named
bounce
.If a subclass declares a method that has the same signature as a public method of the base class, the subclass version of the method overrides the base class version of the method. This technique lets you modify the behavior of a base class to suit the needs of the subclass.
Suppose you have a base class named
Game
that has a method named
play
. The base class, which doesn’t represent any particular game, implements this method:
public class Game
{
public void play()
{
}
}
Then you declare a class named
Chess
that extends the
Game
class but also provides an implementation for the
play
method:
public class Chess extends Game
{
public void play()
{
System.out.println("I give up. You win.");
}
}
Here, when you call the
play
method of a
Chess
object, the game announces that it gives up. (I was going to provide a complete implementation of an actual chess game program for this example, but it would have made this chapter about 600 pages long. So I opted for the simpler version here.)
Note that to override a method, three conditions have to be met:
public
access. You can’t override a
private
method.You’re already familiar with the
public
and
private
keywords, which are used to indicate whether class members are visible outside the class or not. When you inherit a class, all the public members of the superclass are available to the subclass, but the private members aren’t. They do become part of the derived class, but you can’t access them directly in the derived class.
Java provides a third visibility option that’s useful when you create subclasses:
protected
. A member with
protected
visibility is available to subclasses but not to other classes. Consider this example:
public class Ball
{
private double weight;
protected double getWeight()
{
return this.weight;
}
protected void setWeight(double weight)
{
this.weight = weight;
}
}
public class BaseBall extends Ball
{
public BaseBall()
{
setWeight(5.125);
}
}
Here, the
getWeight
and
setWeight
methods are declared with
protected
access, which means that they’re visible in the subclass
BaseBall
. These methods aren’t visible to classes that don’t extend
Ball
, however.
You already know about the
this
keyword: It provides a way to refer to the current object instance. It’s often used to distinguish between a local variable or a parameter and a class field with the same name. For example:
public class Ball
{
private int velocity;
public void setVelocity(int velocity)
{
this.velocity = velocity;
}
}
Here the
this
keyword indicates that the
velocity
variable referred to on the left side of the assignment statement is the class field named
velocity
, not the parameter with the same name.
But what if you need to refer to a field or method that belongs to a base class? To do that, you use the
super
keyword. It works similarly to
this
but refers to the instance of the base class rather than the instance of the current class.
Consider these two classes:
public class Ball
{
public void hit()
{
System.out.println("You hit it a mile!");
}
}
class BaseBall extends Ball
{
public void hit()
{
System.out.println("You tore the cover off!");
super.hit();
}
}
Here the
hit
method in the
BaseBall
class calls the
hit
method of its base class object. Thus, if you call the
hit
method of a
BaseBall
object, the following two lines are displayed on the console:
You tore the cover off!
You hit it a mile!
You can also use the
super
keyword in the constructor of a subclass to explicitly call a constructor of the superclass. For more information, see the next section.
When you create an instance of a subclass, Java automatically calls the default constructor of the base class before it executes the subclass constructor. Consider the following classes:
public class Ball
{
public Ball()
{
System.out.println(
"Hello from the Ball constructor");
}
}
public class BaseBall extends Ball
{
public BaseBall()
{
System.out.println(
"Hello from the BaseBall constructor");
}
}
If you create an instance of the
BaseBall
class, the following two lines are displayed on the console:
Hello from the Ball constructor
Hello from the BaseBall constructor
If you want, you can explicitly call a base class constructor from a subclass by using the
super
keyword. Because Java automatically calls the default constructor for you, the only reason to do this is to call a constructor of the base class that uses a parameter. Here’s a version of the
Ball
and
BaseBall
classes in which the
BaseBall
constructor calls a
Ball
constructor that uses a parameter:
public class Ball
{
private double weight;
public Ball(double weight)
{
this.weight = weight;
}
}
public class BaseBall extends Ball
{
public BaseBall()
{
super(5.125);
}
}
Here the
BaseBall
constructor calls the
Ball
constructor to supply a default weight for the ball.
super
to call the superclass constructor, you must do so in the very first statement in the constructor.
If you don’t explicitly call
super
, the compiler inserts a call to the default constructor of the base class. In that case, the base class must have a default constructor. If the base class doesn’t have a default constructor, the compiler refuses to compile the program.
Object
class, which has no superclass.Java has a
final
keyword that serves three purposes. When you use
final
with a variable, it creates a constant whose value can’t be changed after it has been initialized. Constants are covered in Book 2, Chapter 2
, so I won’t describe this use of the
final
keyword more here. The other two uses of the
final
keyword are to create final methods and final classes. I describe these two uses of
final
in the following sections.
A final method
is a method that can’t be overridden by a subclass. To create a final method, you simply add the keyword
final
to the method declaration. For example:
public class SpaceShip
{
public final int getVelocity()
{
return this.velocity;
}
}
Here the method
getVelocity
is declared as
final
. Thus, any class that uses the
SpaceShip
class as a base class can’t override the
getVelocity
method. If it tries, the compiler issues the error message
("Overridden method final")
.
Here are some additional details about final methods:
Final methods execute more efficiently than nonfinal methods because the compiler knows at compile time that a call to a final method won’t be overridden by some other method. The performance gain isn’t huge, but for applications in which performance is crucial, it can be noticeable.
A final class
is a class that can’t be used as a base class. To declare a class as final, just add the
final
keyword to the class declaration:
public final class BaseBall
{
// members for the BaseBall class go here
}
Then no one can use the
BaseBall
class as the base class for another class.
When you declare a class to be final, all of its methods are considered to be final as well. That makes sense when you think about it. Because you can’t use a final class as the base class for another class, no class can possibly be in a position to override any of the methods in the final class. Thus all the methods of a final class are final methods.
An object of a derived type can be treated as though it were an object of its base type. If the
BaseBall
class extends the
Ball
class, for example, a
BaseBall
object can be treated as though it were a
Ball
object. This arrangement is called upcasting,
and Java does it automatically, so you don’t have to code a casting operator. Thus the following code is legal:
Ball b = new BaseBall();
Here an object of type
BaseBall
is created. Then a reference to this object is assigned to the variable
b
, whose type is
Ball
, not
BaseBall
.
Now suppose that you have a method in a ball-game application named
hit
that’s declared like this:
public void hit(Ball b)
In other words, this method accepts a
Ball
type as a parameter. When you call this method, you can pass it either a
Ball
object or a
BaseBall
object, because
BaseBall
is a subclass of
Ball
. So the following code works:
BaseBall b1 = new BaseBall();
hit(b1);
Ball b2 = b1;
hit(b2);
public void toss(BaseBall b)
Then the following code does not compile:
Ball b = new BaseBall();
toss(b); // error: won't compile
You can explicitly cast the
b
variable to a
BaseBall
object, however, like this:
Ball b = new BaseBall();
toss((BaseBall) b);
Note that the second statement throws an exception of type
ClassCastException
if the object referenced by the
b
variable isn’t a
BaseBall
object. So the following code won’t work:
Ball b = new SoftBall();
toss((BaseBall) b); // error: b isn't a Softball
Ball b = new SoftBall();
SoftBall s = (SoftBall)b; // cast the Ball to a
// SoftBall
s.riseBall();
But there’s a better way: Java lets you cast the
Ball
object to a
SoftBall
and call the
riseBall
method in the same statement. All you need is an extra set of parentheses, like this:
Ball b = new SoftBall();
((SoftBall) b).riseBall();
Here the expression
((SoftBall) b)
returns the object referenced by the
b
variable, cast as a
SoftBall
. Then you can call any method of the
SoftBall
class by using the dot operator. (This operator throws a
ClassCastException
if
b
is not a
SoftBall
object.)
As described in the preceding section, a variable of one type can possibly hold a reference to an object of another type. If
SalariedEmployee
is a subclass of the
Employee
class, the following statement is perfectly legal:
Employee emp = new SalariedEmployee();
Here the type of the
emp
variable is
Employee
, but the object it refers to is a
SalariedEmployee
.
Suppose you have a method named
getEmployee
whose return type is
Employee
but that actually returns either a
SalariedEmployee
or an
HourlyEmployee
object:
Employee emp = getEmployee();
In many cases, you don’t need to worry about which type of employee this method returns, but sometimes you do. Suppose that the
SalariedEmployee
class extends the
Employee
class by adding a method named
getFormattedSalary
, which returns the employee’s salary formatted as currency. Similarly, the
HourlyEmployee
class extends the
Employee
class with a
getFormattedRate
method that returns the employee’s hourly pay rate formatted as currency. Then you’d need to know which type of employee a particular object is, to know whether you should call the
getFormattedSalary
method or the
getFormattedRate
method to get the employee’s pay.
To tell what type of object has been assigned to the
emp
variable, you can use the
instanceof
operator, which is designed specifically for this purpose. Here’s the preceding code rewritten with the
instanceof
operator:
Employee emp = getEmployee();
String msg;
if (emp instanceof SalariedEmployee)
{
msg = "The employee's salary is ";
msg += ((SalariedEmployee) emp).getFormattedSalary();
}
else
{
msg = "The employee's hourly rate is ";
msg += ((HourlyEmployee) emp).getFormattedRate();
}
System.out.println(msg);
Here the
instanceof
operator is used in an
if
statement to determine the type of the object returned by the
getEmployee
method. Then the
emp
can be cast without fear of
CastClassException
.
The term polymorphism refers to the ability of Java to use base class variables to refer to subclass objects; to keep track of which subclass an object belongs to; and to use overridden methods of the subclass, even though the subclass isn’t known when the program is compiled.
This sounds like a mouthful, but it’s not hard to understand when you see an example. Suppose that you’re developing an application that can play the venerable game Tic-Tac-Toe. You start by creating a class named
Player
that represents one of the players. This class has a public method named
move
that returns an
int
to indicate which square of the board the player wants to mark:
class Player
{
public int move()
{
for (int i = 0; i < 9; i++)
{
System.out.println(
"\nThe basic player says:");
System.out.println(
"I'll take the first open square!");
return firstOpenSquare();
}
return -1;
}
private int firstOpenSquare()
{
int square = 0;
// code to find the first open square goes here
return square;
}
}
This basic version of the
Player
class uses a simple strategy to determine what its next move should be: It chooses the first open square on the board. This strategy stokes your ego by letting you think you can beat the computer every time. (To keep the illustration simple, I omit the code that actually chooses the move.)
Now you need to create a subclass of the
Player
class that uses a more intelligent method to choose its next move:
class BetterPlayer extends Player
{
public int move()
{
System.out.println("\nThe better player says:");
System.out.println(
"I'm looking for a good move…");
return findBestMove();
}
private int findBestMove()
{
int square = 0;
// code to find the best move goes here
return square;
}
}
As you can see, this version of the
Player
class overrides the
move
method and uses a better algorithm to pick its move. (Again, to keep the illustration simple, I don’t show the code that actually chooses the move.)
The next thing to do is write a short class that uses these two
Player
classes to play a game. This class contains a method named
playTheGame
that accepts two
Player
objects. It calls the
move
method of the first player and then calls the
move
method of the second player:
public class TicTacToeApp
{
public static void main(String[] args)
{
Player p1 = new Player();
Player p2 = new BetterPlayer();
playTheGame(p1, p2);
}
public static void playTheGame(Player p1, Player p2)
{
p1.move();
p2.move();
}
}
Notice that the
playTheGame
method doesn’t know which of the two players is the basic player and which is the better player. It simply calls the
move
method for each
Player
object.
When you run this program, the following output is displayed on the console:
Basic player says:
I'll take the first open square!
Better player says:
I'm looking for a good move…
When the
move
method for
p1
is called, the
move
method of the
Player
class is executed. But when the
move
method for
p2
is called, the
move
method of the
BetterPlayer
class is called.
The last topic I want to cover in this chapter is how to use inheritance to create your own custom exceptions. I cover most of the details of working with exceptions in Book 2, Chapter 8 , but I hadn’t explored inheritance, so I couldn’t discuss custom exception classes in that chapter. I promised that I’d get to it in this minibook. The following sections deliver on that long-awaited promise.
As you know, you use the try/catch statement to catch exceptions and the
throw
statement to throw exceptions. Each type of exception that can be caught or thrown is represented by a different exception class. What you may not have realized is that those exception classes use a fairly complex inheritance chain, as shown in Figure 4-1
.
The following paragraphs describe the classes in this hierarchy:
Throwable
:
The root of the exception hierarchy is the
Throwable
class. This class represents any object that can be thrown with a
throw
statement and caught with a
catch
clause.
Error
:
This subclass of
Throwable
represents serious error conditions that reasonable programs can’t recover from. The subclasses of this class represent the specific types of errors that can occur. If the Java Virtual Machine runs out of memory, for example, a
VirtualMachineError
is thrown. You don’t have to worry about catching these errors in your programs.
Exception
:
This subclass of
Throwable
represents an error condition that most programs should try to recover from. Thus,
Exception
is effectively the top of the hierarchy for the types of exceptions you catch with
try/catch
statements.
With the exception (sorry) of
RuntimeException
, the subclasses of
Exception
represent specific types of checked exceptions that must be caught or thrown. Note that some of these subclasses have subclasses of their own. The exception class named
IOException
, for example, has more than 25 subclasses representing different kinds of I/O exceptions that can occur.
RuntimeException
:
This subclass of
Exception
represents unchecked exceptions. You don’t have to catch or throw unchecked exceptions, but you can if you want to. Subclasses of
RuntimeException
include
NullPointerException
and
ArithmeticException
.If your application needs to throw a custom exception, you can create an exception class that inherits any of the classes in this hierarchy. Usually, however, you start with the
Exception
class to create a custom checked exception. The next section explains how to do that.
To create a custom exception class, you just define a class that extends one of the classes in the Java exception hierarchy. Usually you extend
Exception
to create a custom checked exception.
Suppose that you’re developing a class that retrieves product data from a file or database, and you want methods that encounter I/O errors to throw a custom exception rather than the generic
IOException
that’s provided in the Java API. You can do that by creating a class that extends the
Exception
class:
public class ProductDataException extends Exception
{
}
Unfortunately, constructors aren’t considered to be class members, so they aren’t inherited when you extend a class. As a result, the
ProductDataException
has only a default constructor. The
Exception
class itself and most other exception classes have a constructor that lets you pass a string message that’s stored with the exception and can be retrieved via the
getMessage
method. Thus you want to add this constructor to your class, which means that you want to add an explicit default constructor too. So now the
ProductDataException
class looks like this:
public class ProductDataException extends Exception
{
public ProductDataException
{
}
public ProductDataException(String message)
{
super(message);
}
}
Although it’s possible to do so, adding fields or methods to a custom exception class is unusual.
As for any exception, you use a
throw
statement to throw a custom exception. You usually code this
throw
statement in the midst of a
catch
clause that catches some other, more generic exception. Here’s a method that retrieves product data from a file and throws a
ProductDataException
if an
IOException
occurs:
public class ProductDDB
{
public static Product getProduct(String code)
throws ProductDataException
{
try
{
Product p;
// code that gets the product from a file
// and might throw an IOException
p = new Product();
return p;
}
catch (IOException e)
{
throw new ProductDataException(
"An IO error occurred.");
}
}
}
Here’s some code that calls the
getProduct
method and catches the exception:
try
{
Product p = ProductDB.getProduct(productCode);
}
catch (ProductDataException e)
{
System.out.println(e.getMessage());
}
Here the message is simply displayed on the console if a
ProductDataException
is thrown. In an actual program, you want to log the error, inform the user, and figure out how to continue the program gracefully even though this data exception has occurred.