Using Subclasses and Inheritance

As you find out in Book III, 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 is, 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.

Introducing Inheritance

The word inheritance conjures up several different non-computer 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.

  Tip 

The terms subclass and superclass seem to be the preferred terms among Java gurus. So if you want to look like you know what you're talking about, use these terms. Also, be aware that the term subclass can be used as a verb. For example, when you create a subclass that inherits a base class, you are subclassing the base class.

You need to know a few important things about inheritance:

The following sections provide some examples that help illustrate these points.

Motorcycles, trains, and automobiles

Inheritance is often explained in terms of real-world objects such as cars and motorcycles, birds and reptiles, or other familiar real-world objects. For example, 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, or 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, or crash. In addition, it would have features that differentiate it from other types of vehicles. For example, it has two wheels and uses handlebars 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, or crash. Plus it would have some features of its own, such as having four wheels and a steering wheel, seat belts and air bags, and an optional automatic transmission.

Playing games

Because you'll unlikely ever actually write a program that simulates cars, motorcycles, or other vehicles, take a look at a more common example: games. Suppose you want to develop a series of board games such as Life, Sorry!, or Monopoly. Most board games have certain features in common:

Each specific type of game has these basic characteristics, but adds features of its own. For example, Life adds features such as money, insurance policies, spouses and children, and a fancy spinner in the middle of the board. Sorry! has cards you draw to determine each move and safety zones within which other players can't attack you. And 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.

A businesslike example

If vehicles or games don't make it clear enough, here's an example from the world of business. Suppose you're designing a payroll system and you're working on the classes that represent the employees. You realize that the payroll basically 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.

However, they have important differences. The most obvious is that the salaried employees have an annual salary and the hourly employees have an hourly pay rate. But there are other differences as well. For example, 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.

So 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 HourlyEmployee classes. These classes provide the additional features that distinguish salaried employees from hourly employees.

Inheritance hierarchies

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.

For example, 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. And because a SalariedEmployee is a type of Employee, a Manager is also a type of Employee.

  TECHNICAL STAUFF 

All classes ultimately derive from a Java class named Object. Any class that doesn't specifically state what class it is derived from is assumed to derive from the Object class. This class provides some of the basic features that are common to all Java classes, such as the toString method. For more information, see Book III, Chapter 5.

Creating Subclasses

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 }

For example, suppose 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.

  DESIGN PATTERN 

The Delegation pattern

Inheritance is one of the great features of object-oriented programming languages, such as Java. However, it isn't the answer to every programming problem. And, quite frankly, many Java programmers use it too much. In many cases, simply including an instance of one class in another class rather than using inheritance is easier. This technique is sometimes called the Delegation Design pattern.

For example, suppose you need to create a class named EmployeeCollection that represents a group of employees. One way to create this class would be to extend one of the collection classes supplied by the Java API, such as the ArrayList class. Then your EmployeeCollection class would be a specialized version of the ArrayList class, and would have all the methods that are available to the ArrayList class.

However, a simpler alternative would be to declare a class field of type ArrayList within your EmployeeCollection class. Then you could provide methods that use this ArrayList object to add or retrieve employees from the collection.

Why is this technique called the delegation? Because-rather than write code that implements the functions of the collection-you delegate that task to an ArrayList object, because ArrayList objects already know how to perform these functions. (For more information about the ArrayList class, see Book IV, Chapter 3.)

You need to know some important details to use inheritance properly:

Overriding Methods

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.

For example, 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:

Protecting Your Members

You're already familiar with the public and private keywords, used to indicate whether class members are visible outside of 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 a 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. For example, 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 protect access, which means they're visible in the subclass BaseBall. However, these methods aren't visible to classes that don't extend Ball.

Using this and super in Your Subclasses

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 similar to this, but refers to the instance of the base class rather than the instance of the current class.

For example, 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.

Inheritance and Constructors

When you create an instance of a subclass, Java automatically calls the default constructor of the base class before it executes the subclass constructor. For example, 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. For example, 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.

  REMEMBER 

You need to obey a few rules and regulations when working with superclass constructors:

  • If you use super to call the superclass constructor, you must do so in the very first statement in the constructor.
  •   Warning 

    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.

  • If the superclass is itself a subclass, the constructor for its superclass is called in the same way. This continues all the way up the inheritance hierarchy until you get to the Object class, which has no superclass.

Using final

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 once it has been initialized. Constants are covered in Book II, 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.

Final methods

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 an error message.

Here are some additional details about final methods:

Final classes

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.

Casting Up and Down

An object of a derived type can be treated as if it were an object of its base type. For example, if the BaseBall class extends the Ball class, a BaseBall object can be treated as if it were a Ball object. This 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 you have a method in a ballgame 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);

  Warning 

Automatic casting doesn't work the other way, however. Thus you can't use a Ball object where a BaseBall object is called for. For example, suppose your program has a method declared like this:

public void toss(BaseBall b)

Then the following code does not compile:

Ball b = new BaseBall(); toss(b); // error: won't compile

However, you can explicitly cast the b variable to a BaseBall object, 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

  Tip 

What if you want to call a method that's defined by a subclass from an object that's referenced by a variable of the superclass? For example, suppose the SoftBall class has a method named riseBall that isn't defined by the Ball class. How can you call it from a Ball variable? One way to do that is to create a variable of the subclass, and then use an assignment statement to cast the object:

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:

Ball b = new SoftBall(); ((SoftBall) b).riseBall();

Here the expression ((SoftBall) b) returns the object referenced by the b variable, cast as a SoftBall. You can then call any method of the SoftBall class using the dot operator. (This operator throws a ClassCastException if b is not a SoftBall object.)

  Tip 

As a general rule, you should declare method parameters with types as far up in the class hierarchy as possible. For example, rather than create separate toss methods that accept BaseBall and SoftBall objects, create a single toss method that accepts a Ball object. If necessary, the toss method can determine which type of ball it's throwing by using the instanceof operator, which is described in the next section.

Determining an Object s Type

As described in the previous section, a variable of one type can possibly hold a reference to an object of another type. For example, 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. For example, suppose 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 previous 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.

Poly What?

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 that hard to understand when you see an example. Suppose you're developing an application that can play the venerable game of Tic-Tac-Toe. You start out 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 omitted 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; // 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 actually 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.

  TECHNICAL STAUFF 

Java knows to call the move method of the BetterPlayer subclass because it uses a technique called late binding. Late binding simply means that when the compiler can't tell for sure what type of object a variable references, it doesn't hard-wire the method calls when the program is compiled. Instead, it waits until the program is executing to determine exactly which method to call.

Creating Custom Exceptions

The last topic I want to cover in this chapter is how to use inheritance to create your own custom exceptions. I covered most of the details of working with exceptions in Book II, Chapter 8. However, I hadn't explored inheritance, so I couldn't discuss custom exception classes in that chapter. So I promised I'd get to it in this minibook. The following sections deliver on that long-awaited promise.

The Throwable hierarchy

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 might not have realized is that those exception classes use a fairly complex inheritance chain, as shown in Figure 4-1.

Figure 4-1: The hierarchy of exception classes.

The following paragraphs describe each of the classes in this hierarchy:

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.

Creating an exception class

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.

For example, suppose 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. That means you want to add an explicit default constructor too. So the ProductDataException class now looks like this:

public class ProductDataException extends Exception { public ProductDataException { } public ProductDataException(String message) { super(message); } }

Although possible, adding additional fields or methods to a custom exception class is unusual.

Throwing a custom exception

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. For example, 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 gracefully continue the program even though this data exception has occurred.

Категории