Chapter 4

Using Subclasses and Inheritance

IN THIS CHAPTER

check Explaining inheritance

check Creating subclasses

check Using protected access

check Creating final classes

check Demystifying polymorphism

check 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.

Introducing Inheritance

The word inheritance conjures up several noncomputer meanings:

  • Children inherit certain characteristics from the parents. Two of my three children have red hair, for example. (Ideally, they won’t be half bald by the time they’re 30.)
  • Children can also inherit behavior from their parents. As they say, the apple doesn’t fall far from the tree.
  • When someone dies, his heirs get his stuff. Some of it is good stuff, but some of it may not be. My kids are going to have a great time rummaging through my garage, deciding who gets what.
  • You can inherit rights as well as possessions. You may be a citizen of a country by virtue of being born to parents who are citizens of that country.

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. When you create a subclass that inherits a base class, for example, you are subclassing the base class.

You need to know a few important things about inheritance:

  • A derived class automatically takes on all the behavior and attributes of its base class. Thus, if you need to create several classes to describe types that aren’t identical but have many features in common, you can create a base class that defines all the common features. Then you can create several derived classes that inherit the common features.
  • A derived class can add features to the base class it inherits by defining its own methods and fields. This is one way that a derived class distinguishes itself from its base class.
  • A derived class can also change the behavior provided by the base class. A base class may provide that all classes derived from it have a method named 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.
  • tip 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.

Motorcycles, trains, and automobiles

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.

Game play

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:

  • They have a playing board with locations that players can occupy.
  • They have players that are represented by objects.
  • The game is played by each player taking a turn, one after the other. When the game starts, it keeps going until someone wins. (If you don’t believe me, ask the kids who tried to stop a game of Jumanji before someone won.)

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.

A businesslike example

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.

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.

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.

technicalstuff 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 3, Chapter 6.

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
}

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:

  • remember 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.

  • The visibility (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.
  • You can override a method by declaring a new member with the same signature in the subclass. For more information, see the next section.
  • A special type of visibility called 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.
  • You can add more methods or fields, private or protected, to a subclass. The BouncingBall class shown earlier in this section, for example, adds a public method named bounce.

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.

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:

  • The class must extend the class that defines the method you want to override.
  • The method must be declared in the base class with public access. You can’t override a private method.
  • The method in the subclass must have the same signature as the method in the base class. In other words, the name of the method and the parameter types must be the same.

Protecting Your Members

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.

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 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.

Understanding 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. 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.

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 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.

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 the error message ("Overridden method final").

Here are some additional details about final methods:

  • You might think that a subclass wouldn’t need to override a method, but there’s no reason to be sure of that. Predicting how other people might use your class is difficult. As a result, usually you should avoid using final methods unless you have a compelling reason to do so.
  • technicalstuff 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.

  • Private methods are automatically considered to be final because you can’t override a method you can’t see.

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 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);

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. 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

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

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? Suppose that 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, 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.)

tip As a general rule, you should declare method parameters with types as far up in the class hierarchy as possible. Rather than create separate toss methods that accept BaseBall and SoftBall objects, for example, you can 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 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.

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 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.

technicalstuff 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 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.

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

image

FIGURE 4-1: The hierarchy of exception classes.

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.

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.

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.

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. 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset