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(
"
The 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("
The 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.