Extending Classes

Java provides the reserved word extends for specifying a hierarchical relationship between two classes. For example, suppose you have a Vehicle class and want to introduce a Car class as a kind of Vehicle. Listing 3-1 uses extends to cement this relationship.

Listing 3-1. Relating two classes via extends class Vehicle {

// member declarations

class Car extends Vehicle

// member declarations

Listing 3-1 codifies a relationship that is known as an "is-a" relationship: a car is a kind of vehicle. In this relationship, Vehicle is known as the base class, parent class, or superclass; and Car is known as the derived class, child class, or subclass.

CAUTION: You cannot extend a final class. For example, if you declared Vehicle as final class Vehicle, the compiler would report an error upon encountering class Car extends Vehicle. Developers declare their classes final when they do not want these classes to be subclassed (for security or other reasons).

In addition to being capable of providing its own member declarations, Car is capable of inheriting member declarations from its Vehicle superclass. As Listing 3-2 shows, inherited members become accessible to members of the Car class.

Listing 3-2. Inheriting members class Vehicle {

private String make; private String model; private int year;

Vehicle(String make, String model, int year) {

this.make = make; this.model = model; this.year = year;

String getMake()

return make;

String getModel()

return model;

int getYear()

return year;

class Car extends Vehicle {

private int numWheels;

Car(String make, String model, int year, int numWheels) {

super(make, model, year);

this.numWheels = numWheels;

public static void main(String[] args) {

Car car = new Car("Ford", "Fiesta", 2009, 4); System.out.println("Make = " + car.getMake()); System.out.println("Model = " + car.getModel ()); System.out.println("Year = " + car.getYear ());

// Normally, you cannot access a private field via an object // reference. However, numWheels is being accessed from // within a method (main()) that is part of the Car class. System.out.println("Number of wheels = "+car.numWheels);

Listing 3-2's Vehicle class declares private fields that store a vehicle's make, model, and year; a constructor that initializes these fields to passed arguments; and getter methods that retrieve these fields' values.

The Car subclass provides a private numWheels field, a constructor that initializes a Car object's Vehicle and Car layers, and a main() class method for test-driving this application.

Car's constructor uses reserved word super to call Vehicle's constructor with Vehicle-oriented arguments, and then initializes Car's numWheels instance field. The super() call is analogous to specifying this() to call another constructor in the same class.

CAUTION: The super() call can only appear in a constructor. Furthermore, it must be the first code that is specified in the constructor.

If super() is not specified, and if the superclass does not have a noargument constructor, the compiler will report an error because the subclass constructor must call a noargument superclass constructor when super() is not present.

Car's main() method creates a Car object, initializing this object to a specific make, model, year, and number of wheels. Four System.out.println() method calls subsequently output this information.

The first three System.out.println() method calls retrieve their pieces of information by calling the Car instance's inherited getMake(), getModel(), and getYear() methods. The final System.out.println() method call accesses the instance's numWheels field directly.

NOTE: A class whose instances cannot be modified is known as an immutable class. Vehicle is an example. If Car's main() method, which can directly read or write numWheels, was not present, Car would also be an example of an immutable class.

A class cannot inherit constructors, nor can it inherit private fields and methods. Car does not inherit Vehicle's constructor, nor does it inherit Vehicle's private make, model, and year fields.

A subclass can override (replace) an inherited method so that the subclass's version of the method is called instead. Listing 3-3 shows you that the overriding method must specify the same name, parameter list, and return type as the method being overridden.

Listing 3-3. Overriding a method class Vehicle {

private String make; private String model; private int year;

Vehicle(String make, String model, int year) {

this.make = make; this.model = model; this.year = year;

void describe()

System.out.println(year + " " + make + " " + model);

class Car extends Vehicle {

private int numWheels;

Car(String make, String model, int year, int numWheels) {

super(make, model, year);

void describe()

System.out.print("This car is a "); // Print without newline - see Chapter 1. super.describe();

public static void main(String[] args) {

Car car = new Car("Ford", "Fiesta", 2009, 4); car.describe();

Listing 3-3's Car class declares a describe() method that overrides Vehicle's describe() method to output a car-oriented description. This method uses reserved word super to call Vehicle's describe() method via super.describe();.

NOTE: You call a superclass method from the overriding subclass method by prefixing the method's name with reserved word super and the member access operator. If you do not do this, you end up recursively calling the subclass's overriding method.

You can also use super and the member access operator to access non-private superclass fields from subclasses that replace these fields by declaring same-named fields.

If you were to compile and run Listing 3-3, you would discover that Car's overriding describe() method executes instead of Vehicle's overridden describe() method, and outputs This car is a 2009 Ford Fiesta.

CAUTION: You cannot override a final method. For example, if Vehicle's describe() method was declared as final void describe(), the compiler would report an error upon encountering an attempt to override this method in the Car class. Developers declare their methods final when they do not want these methods to be overridden (for security or other reasons).

Also, you cannot make an overriding method less accessible than the method it overrides. For example, if Car's describe() method was declared as private void describe(), the compiler would report an error because private access is less accessible than the default package access. However, describe() could be made more accessible by declaring it public, as in public void describe().

Suppose you happened to replace Listing 3-3's describe() method with the method shown in Listing 3-4.

Listing 3-4. Incorrectly overriding a method void describe(String owner) {

System.out.print("This car, which is owned by " + owner + ", is a "); super.describe();

The modified Car class now has two describe() methods, the explicitly declared method in Listing 3-4 and the method inherited from Vehicle. Listing 3-4 does not override Vehicle's describe() method. Instead, it overloads this method.

The Java compiler helps you detect an attempt to overload instead of override a method at compile time by letting you prefix a subclass's method header with the @Override annotation, as shown in Listing 3-5. (I will discuss annotations in Chapter 5.)

Listing 3-5. Annotating an overriding method

^Override void describe()

System.out.print("This car is a "); super.describe();

Specifying @Override tells the compiler that the method overrides another method. If you overload the method instead, the compiler reports an error. Without this annotation, the compiler would not report an error because method overloading is a valid feature.

TIP: Get into the habit of prefixing overriding methods with the @Override annotation. This habit will help you detect overloading mistakes much sooner.

Chapter 2 discussed the initialization order of classes and objects, where you learned that class members are always initialized first, and in a top-down order (the same order applies to instance members). Implementation inheritance adds a couple more details:

■ A superclass's class initializers always execute before a subclass's class initializers.

■ A subclass's constructor always calls the superclass constructor to initialize an object's superclass layer, and then initializes the subclass layer.

Java lets you extend a single class, which is commonly referred to as single inheritance. However, Java does not permit you to extend multiple classes, which is known as multiple implementation inheritance, because it leads to ambiguities.

For example, suppose Java supported multiple implementation inheritance, and you decided to model a tiglon (a cross between a tiger and a lioness) via the class structure shown in Listing 3-6.

Listing 3-6. Modeling a tiglon class Tiger {

void describe() {

// Code that outputs a description of the tiger's appearance and behaviors.

class Lioness {

void describe() {

// Code that outputs a description of the lioness's appearance and behaviors.

class Tiglon extends Tiger, Lioness

// Which describe() method does Tiglon inherit?

Listing 3-6 shows an ambiguity resulting from each of Tiger and Lioness possessing a describe() method. Which of these methods does Tiglon inherit? A related ambiguity arises from same-named fields, possibly of different types. Which field is inherited?

Was this article helpful?

0 0

Post a comment