Home > Articles > Programming > Java

Inheritance in Java

📄 Contents

  1. Inheritance Basics
  2. Programming with Inheritance
  3. Summary
  • Print
  • + Share This
This chapter is from the book

Programming with Inheritance

You do not have to die in order to pass along your inheritance.
—Ad for an estate-planning seminar

Inheritance via derived classes is a powerful tool. It does, however, require that you learn some new techniques in order to use it productively. In this section, we discuss some of these techniques.

Constructors in Derived Classes

A derived class, such as the class Student in Display 2, has its own constructors. The base class from which it was derived, such as Person, also has its own constructors. When defining a constructor for the derived class, the typical first action is to call a constructor of the base class. For example, consider defining a constructor for the class Student. One of the things that need to be initialized is the student's name. This name initializing is normally done by the constructors for the base class Person (since the instance variable name was introduced in the definition of Person). Thus, a natural first action for a constructor for the class Student is a call to a constructor for its base class Person. For example, consider the following definition of a constructor for the derived class Student (from Display 2):

public Student(String initialName, int initialStudentNumber)
{
  super(initialName);
  studentNumber = initialStudentNumber;
}

The line

super(initialName);

is a call to a constructor for the base class, in this case, a call to a constructor for the class Person. Notice that you use the reserved word super to call the constructor of the base class. You do not use the name of the constructor; you do not use Person(initialName).

There are some details to worry about with the use of super: It must always be the first action taken in a constructor definition. You cannot use it later in the definition of a constructor. In fact, if you do not include a call to the base-class constructor, then Java will automatically include a call to the default constructor of the base class as the first action of any constructor for a derived class.

Consider the following definition of a constructor for the class Student (Display 2):

public Student()
{
  super();
  studentNumber = 0;//Indicating no number yet
}

This definition is completely equivalent to the following:

public Student()
{
  studentNumber = 0;//Indicating no number yet
}

If there is no call to a constructor of the base class, then Java will automatically insert a call to the default constructor of the base class; that is, Java will automatically insert a call to super(). Some programmers prefer to explicitly write in such calls to a base-class default constructor, whether or not Java would call it automatically. That makes the code a bit clearer, because all actions are explicitly shown.

The this Method (Optional)

When defining a constructor another common action is a call to one of the other constructors. You can use the reserved word this in a way similar to how you use super, but with this, the call is to a constructor of the same class and not a call to a constructor for the base class. For example, consider the following definition of a constructor that you might want to add to the class Student (from Display 2):

public Student(String initialName)
{
  this(initialName, 0);
}

The one statement in the body of this constructor definition is a call to the constructor whose definition begins

public Student(String initialName, int initialStudentNumber)

As with super, any use of this must be the first action in a constructor definition. Thus, a constructor definition cannot contain both a call using super and a call using this. What if you want a call with super and a call with this? In that case, use a call with this, and have the constructor that is called with this have super as its first action.

Call to an Overridden Method

When you are defining a constructor for a derived class, you can use super as a name for the constructor of the base class. You can also use super to call a method of the base class that is overridden (redefined) in the derived class, but the way you do this is a bit different.

For example, consider the method writeOutput for the class Student. It uses the following to output the name of the Student:

System.out.println("Name: " + getName());

Alternatively, you could output the name by calling the method writeOutput of the class Person (Display 1), since the writeOutput method for the class Person will output the person's name. The only problem is that if you use the name writeOutput for a method in the class Student, it will mean the method named writeOutput in the class Student. What you need is a way to say "writeOutput() as it is defined in the base class." The way you say that is super.writeOutput(). So, an alternative definition of the writeOutput method for the class Student is the following:

public void writeOutput()
{
  super.writeOutput();
  System.out.println("Student Number: " + studentNumber);
}

If you replace the definition of writeOutput in the definition of Student (Display 2) with the preceding, then the class Student will behave exactly the same as it did before.

Programming Example: Multilevel Derived Classes

You can form a derived class from a derived class. In fact, this is common. For example, in Display 4 we have defined the class Undergraduate, which is a derived class of the class Student (Display 2). This means that an object of the class Undergraduate has all the methods and instance variables of the class Student. But Student is already a derived class of Person (Display 1). So, this also means that an object of the class Undergraduate has all the methods and instance variables of the class Person. An object of the class Person has the instance variable name. An object of the class Student has the instance variables name and studentNumber. An object of the class Undergraduate has the instance variables name, studentNumber, and the added instance variable level. An object of the class Undergraduate must use accessor and mutator methods to access and change the instance variables name and studentNumber, but it definitely has these instance variables.

Display 4—A Derived Class of a Derived Class

public class Undergraduate extends Student
{
  private int level;// 
  public Undergraduate()
  {
    super();
    level = 1;
  }

  public Undergraduate(String initialName, 
          int initialStudentNumber, int initialLevel)
  {
    super(initialName, initialStudentNumber);
    level = initialLevel;
  }

  public void reset(String newName, 
             int newStudentNumber, int newLevel)
  {
    reset(newName, newStudentNumber);
    level = newLevel;
  }

  public int getLevel()
  {
    return level;
  }

  public void setLevel(int newLevel)
  {
    level = newLevel;
  }

  public void writeOutput()
  {
    super.writeOutput();
    System.out.println("Student Level: " + level);
  }

  public boolean equals(Undergraduate otherUndergraduate)
  {
    return (super.equals(otherUndergraduate)
       && (this.level == otherUndergraduate.level));
  }
}

Note that a chain of derived classes like this can produce a good deal of efficient reusing of code. Both the classes Undergraduate and Student (as well as any other classes derived from either of them), in effect, reuse the method code given in the definition of the class Person, because they inherit all the methods of the class Person.

Note the constructors for the class Undergraduate. They each begin with an invocation of super, which in this context stands for a constructor of the base class Student. But the constructors for the class Student also begin with an invocation of super, which in this case stands for a constructor of the base class Person. Thus, when a constructor for Undergraduate is invoked (using new), first a constructor for Person is invoked, then a constructor for Student is invoked, and then all the code following super in the constructor for Undergraduate is executed.

Notice the definition of the reset method in the class Undergraduate, which we reproduce in what follows:

public void reset(String newName, 
             int newStudentNumber, int newLevel)
{
  reset(newName, newStudentNumber);
  level = newLevel;
}

Note that the method starts with an invocation of reset with only two arguments. This is an invocation of the method named reset in the base class Student. In the class Undergraduate, the method named reset is overloaded. In the class Undergraduate there are two definitions for the method name reset. One takes two arguments and the other takes three arguments. The one that takes two arguments is inherited from the class Student, but it is still a full-fledged method of the class Undergraduate.

In the three-argument version of reset (which is defined in the class Student and reproduced in the previous displayed code), the method reset of the base class Student resets the values of the instance variables name and studentNumber (to newName and newStudentNumber, respectively), with the invocation

reset(newName, newStudentNumber);

Then, the new instance variable level is reset to newLevel with an assignment statement.

Remember that within the definition of the class Undergraduate, the private instance variables name and studentNumber (of base classes) cannot be referenced by name, and so a mutator method is need to change them. The reset method of the class Student is perfect for this purpose.

In contrast to the definition of the reset method in the class Undergraduate, notice the definition of the method writeOutput, which we reproduce in what follows:

public void writeOutput()
{
  super.writeOutput();
  System.out.println("Student Level: " + level);
}

The definition of reset gave us an example of overloading. The method writeOutput gives us an example of overriding.

With the method name reset, the version defined in the class Undergraduate has a different number of parameters than the one defined in the class Student. So, there is no conflict in having both versions of reset in the derived class Undergraduate. This is an example of overloading. By contrast to the method name reset, the method writeOutput defined in the derived class Undergraduate has exactly the same parameter list as the version in the base class Student. Thus, when writeOutput is invoked, Java must decide which definition of writeOutput to use. For an object of the derived class Undergraduate, it uses the version of writeOutput given in the definition of the class Undergraduate. This is an example of overriding the definition of a method name (writeOutput) in a derived class. However, the definition of the method writeOutput that was given in the base class Student is still inherited by the derived class Undergraduate. But, in order to invoke the version in the base class Student within the definition of the derived class Undergraduate, you must place super and dot in front of the method name writeOutput, as in the first line of the method definition of writeOutput, given in the definition of the derived class Undergraduate and displayed within the previous paragraph.

In Display 4 you can see a similar use of super in the method definitions of equals. Note that it is perfectly permissible and indeed common to redefine names like writeOutput and equals in a derived class. The versions in the base class are still there and can be accessed by using the prefix super and a dot.

A Subtle Point About Overloading and Overriding (Optional)

Consider the method name equals in the classes Student and Undergraduate. They have different parameter lists. The one in the class Student has a parameter of type Student, while the one in the class Undergraduate has a parameter of type Undergraduate. They have the same number of parameters, namely one, but that one parameter is of different types in the two definitions. A difference in type is enough to qualify for overloading. To qualify as overloading, the two method definitions need to have a different number of parameters or a parameter position of different types in the two methods. So the second definitions of equals given in the derived class Undergraduate is, in some technical sense, overloading and not overriding.

Why, then, did we use super in the definition of equals, within the derived class Undergraduate? To help us analyze the situation, we reproduce the definition (used in the derived class Undergraduate) below:

public boolean equals(Undergraduate otherUndergraduate)
{
  return (super.equals(otherUndergraduate)
      && (this.level == otherUndergraduate.level));
}

Since the invocation super.equals is an invocation of an overloaded version of equals, why do we need the super? To see why, try omitting it, as follows:

return (equals(otherUndergraduate)
      && (this.level == otherUndergraduate.level));

The argument otherUndergraduate is of type Undergraduate, so Java would assume this refers to the definition of equals in the class Undergraduate. In order to force Java to use the definition of equals in the base class Student, we need to use the super and dot.

Java Tip: You Cannot Use Multiple supers

As we already noted, within the definition of a method of a derived class, you can call an overridden method of the base class by prefacing the method name with super and a dot. However, you cannot repeat the use of super to invoke a method from some ancestor class other than a direct parent. Suppose that the class Student is derived from the class Person, and the class Undergraduate is derived from the class Student. You might think that you can invoke a method of the class Person within the definition of the class Undergraduate, by using super.super, as in

super.super.writeOutput();//ILLEGAL!

You might hope that the previous statement would invoke the version of the method writeOutput that was defined in the class Person. However, as the comment indicates, it is illegal to have such a train of super's in Java.

Programming Tip: An Object of a Derived Class Has More than One Type

Consider the class Undergraduate in Display 4. It is a derived class of the class Student. In the real world, every undergraduate is also a student. This relationship holds in Java as well. Every object of the class Undergraduate is also an object of the class Student. Thus, if you have a method that has a formal parameter of type Student, then the argument in an invocation of this method can be an object of type Undergraduate. In this case, the method could only use those instance variables and methods that belong to the class Student, but every object of the class Undergraduate has all these instance variables and methods so there are still lots of meaningful things that the method can do with an object of type Undergraduate.

For example, suppose that the classes Student and Undergraduate are defined as in Display 2 and Display 4 and consider the following method definition that might occur in some class:

public class SomeClass
{
  public static void compareNumbers(Student s1, Student s2)
  {
    if (s1.getStudentNumber() == s2.getStudentNumber())
      System.out.println(s1.getName() 
                + " has the same number as "
                + s2.getName());
    else
      System.out.println(s1.getName() 
                + " has a different number than "
              + s2.getName());
   }
  ...
}

A program that uses this method might contain the following code:

Student studentObject = new Student("Jane Doe", 1234);
System.out.println("Enter name:");
String undergradName = SavitchIn.readLine();
System.out.println("Enter student number :");
int undergradStudentNumber = SavitchIn.readLineInt();
Undergraduate undergradObject = 
 new Undergraduate(undergradName, undergradStudentNumber, 1);
SomeClass.compareNumbers(studentObject, undergradObject);

If you look at the heading for the method compareNumbers, you will see that both parameters are of type Student. However, the invocation

SomeClass.compareNumbers(studentObject, undergradObject);

uses one argument of type Student and one object that is of type Undergraduate. Why could we use an object of type Undergraduate where an argument of type Student is required? The answer is that every object of type Undergraduate is also of type Student. To make the point a little more dramatically, notice that you can reverse the two arguments and the method invocation will still be legal, as shown below:

SomeClass.compareNumbers(undergradObject, studentObject);

(Note that there is no automatic type casting here. An object of the class Undergraduate is a member of the class Student, and so it is of type Student. It need not be, and is not, type cast to an object of the class Student.)

An object can actually have more than two types as a result of inheritance. Recall that the class Undergraduate (Display 4) is a derived class of the class Student (Display 2), and Student is a derived class of the class Person (Display 1). This means that every object of the class Undergraduate is also an object of type Student and an object of type Person. Thus, everything that works for objects of the class Person also works for objects the class Undergraduate.

For example, suppose that the classes Person and Undergraduate are defined as in Displays 1 and 4 and consider the following code, which might occur in a program:

Person joePerson = new Person("Josephine Student");
System.out.println("Enter name:");
String newName = SavitchIn.readLine();
Undergraduate someUndergrad = new Undergraduate(newName, 222, 3);
if (joePerson.sameName(someUndergrad))
  System.out.println("Wow, same names!");
else
  System.out.println("Different names");

If you look at the heading for the method sameName in Display 1, you will see that it has one parameter and that parameter is of type Person. However, the call

joe.sameName(someUndergrad)

which is used in the preceding if statement is perfectly legal, even though the argument someUndergrad is an object of the class Undergraduate (that is, is of type Undergraduate) and the argument is supposed to be of type Person. This is because every object of the class Undergraduate is also an object of the class Person. The object someUndergrad is of type Undergraduate, and it is also of type Person. This is one of the nice features of inheritance. Everything that works for objects of an ancestor class also works for objects of any descendant class.

As we already saw, if A is a derived class of class B, and B is a derived class of class C, then an object of class A is of type A, it is also of type B, and it is also of type C. This works for any chain of derived classes no matter how long the chain is.

Java has an "Eve" class, that is, a class that is an ancestor of every class. In Java, every class is a derived class of a derived class of... (for some number of iterations of "a derived class of") of the class Object. So, every object of every class is of type Object, as well as being of the type of its class (and also of the types of all other ancestor classes). Even classes that you define yourself are descendent classes of the class Object. If you do not make your class a derived class of some class, then Java acts as if you made it a derived class of the class Object.

Because an object of a derived class has the types of all of its ancestor classes (as well as its "own" type), you can assign an object of a class to a variable of any ancestor type, but not the other way around. For example, if Student is a derived class of Person, and Undergraduate is a derived class of Student, then the following are all legal:

Person p1, p2;
p1 = new Student();
p2 = new Undergraduate();

However, the following are all illegal:

Student s = new Person(); //ILLEGAL!
Undergraduate ug = new Person(); //ILLEGAL!
Undergraduate ug2 = new Student(); //ILLEGAL!

This all makes perfectly good sense. For example, a Student is a Person, but a Person is not necessarily a Student. Some programmers find the phrase "is a" to be useful in deciding what types an object can have and what assignments to variables are legal.

As another example, if Employee is a derived class of Person, then an Employee is a Person, So you can assign an Employee object to a variable of type Person. However, a Person is not necessarily an Employee, so you cannot assign an object created as just a plain Person to a variable of type Employee.

Programming Tip: "Is a" and "Has a"

As we noted in the previous subsection a Student is a Person, so we made the Student class a derived class of the Person class. This is an example of the "is a" relationship between classes. It is one way to make a more complex class out of a simpler class.

Another way to make a more complex class out of a simpler class is know as the "has a" relationship. For example, if you have a class Date that records a date. Then you might add a date of enrollment to the Student class by adding an instance variable of type Date to the Student class. In this case we say a Student "has a" Date. As another example, if we have a class to simulate a MechanicalArm and we are defining a class to simulate a robot, then we can give the Robot class an instance variable of type MechanicalArm. In this case we say that a Robot "has a" MechanicalArm.

In most situations you can make your code work with either an "is a" relationship or a "has a" relationship. It seems silly (and it is silly) to make the Robot class a derived class of the MechanicalArm class, but it can be done and can be made to work (perhaps with difficulty). Fortunately, the best programming technique is usually to simply follow what sounds most natural in English. It makes more sense to say "A Robot has a MechanicalArm" than it is to say "A Robot is a MechanicalArm." So, it makes better programming sense to have a MechanicalArm as an instance variable of a Robot class. It makes little sense to make the Robot class a derived class of the MechanicalArm class.

You will often encounter these terms "is a" and "has a" in the literature on programming techniques.

Methods Inherited from the Class Object

Every class is a descendent class of the class Object. So, there should be some methods that every class inherits from the class Object. There are such methods, and they are useful methods. For example, every object inherits the methods equals and toString from some ancestor class, which either is the class Object or did itself inherit the methods ultimately from the class Object. However, the inherited methods equals and toString will not work correctly for (almost) any class you define. You need to override the inherited method definitions with new more appropriate definitions. Thus, whenever you defined the method equals for a class, you are technically speaking redefining the method equals.

The inherited method toString takes no arguments. The method toString is suppose to return all the data in an object coded into a String. However, you will not automatically get a nice string representation of the data. The inherited version of toString is almost always useless, unless it is redefined. You need to override the definition of toString so it produces an appropriate String for the data in objects of the class being defined. .

For example, the following definition of toString could be added to the class Student (Display 2):

public String toString()
{
  return("Name: " + getName()
          + "\nStudent number: " 
          + Integer.toString(studentNumber));
}

If this toString method were added to the class Student, then it can be used, among other things, to give output in the following way:

Student s = new Student("Joe Student", 2001);
System.out.println(s.toString());

The output produced would be

Name: Joe Student
Student number; 2001

(If the expression Integer.toString(studentNumber seems puzzling, just note that it converts an integer to the corresponding numeral string.)

Case Study: Character Graphics

Java has classes to draw graphics on your terminal screen. However, there are situations in which you have no graphics capability available on your screen or other output device. For example, some older terminals only allow for text output.

In this case study, you will design three simple classes to be used to produce simple graphics on a screen using only text output. These classes will produce their graphic figures by placing ordinary keyboard characters at certain places on each line, and will do this in such a way as to produce some simple pictures.

The origin of this case study is a request for graphics to be included in e-mail messages. The only thing you can be certain that you can include in an e-mail message so that the message can be handled by any mailer program, and so, understood by any person who receives the message, is plain text. So, this sort of character graphics is the only kind of graphics that will work for all people to whom you might send e-mail. In this case study, you will do this character graphics using the screen in order to have a demonstration prototype. The prototype will be used to demonstrate the graphics in the hope of obtaining a contract to create graphics that can be output to a file that can then be sent via e-mail. Although we have not yet covered output to files, it will turn out to be easy to modify these classes to send their output to a file rather than to the screen.

The exact details of what you will do in this case study are as follows: You will create two classes, one for a box and one for a triangle. You will then write a simple demonstration program that draws a pine tree using the triangle and box classes. If this prototype is successful with customers, you will then write more classes for figures, but these two are all you will do in this case study.

Each of the figures, the box and the triangle, will have an offset telling how far they are indented from the edge of the screen. Each figure will also have a size, although the size will be determined differently for a box and a triangle.

For a box, the size is given as the width and the height, expressed in the number of characters. Because characters are taller than they are wide, a box will look taller than you might expect when it is written on the screen. For example, a 5-by-5 box will not look square on the screen, but will look like what is shown in Display 5.

Display 5—Sample Boxes and Triangles

     _ _ _ _ _
     |     _|
     |     _|
     |     _|
     _ _ _ _ _
 
          *
         * *
        *   *
       *     *
      *       *
     *         *
    *           *
   ***************

For a triangle, the size will be given by its base. The triangle will point up and the base will be at the bottom. The slope of a side of the triangle is limited to what you get by indenting one character per line (if you want it to be smooth). So, once the base is chosen, you have no choice in what the sides of the triangle will be. Display 5 shows a sample of both a box and a triangle.

Because a box and a triangle are figures with many properties in common, you decide to design a base class named Figure. The classes Box and Triangle will then be derived classes of the class Figure. The class Figure will have instance variables for any properties that all figures have in common and will have methods for the actions that all figures have. Your analysis produces the following list of properties and actions:

Properties. All figures have an offset, which is the number of spaces the figure is indented, so you decide to store the offset in an instance variable of type int. All figures will have a size, but the size of some figures is described by a single number and the size of other figures is determined by two or more numbers. For example, the size of the kind of triangle we will be using in this case study is given by one number, but the size of a box is given as two numbers, the height and the width. So, you decide not to specify size as part of the class Figure. Thus, you decide that the class Figure will have only the following instance variable:

private int offset;

Actions. The only actions you need for all figures are to set the parameters for the figures and to draw the figures. Setting the parameters will be taken care of by constructors and reset methods. Display 6 contains the definition for the class Figure that you produce. The method drawHere will simply indent a number of spaces on the screen equal to the offset and then write an asterisk on the screen.

Display 6—The Figure Base Class

/*********************************************************
 *Class for simple character graphics figures to send to screen.
 *This class can draw an asterisk on the screen as a test. But,
 *it is not intended to be a figure used in graphics. 
 *It is intended to be used as a base class for the kinds
 *of figures that will be used in graphics applications. 
 *********************************************************/
public class Figure
{
  private int offset; 
 
  public Figure()
  {
    offset = 0; 
  }
  
  public Figure(int theOffset)
  {
    offset = theOffset;
  }
  
  public void setOffset(int newOffset)
  {
    offset = newOffset;
  }
  
  public int getOffset()
  {
    return offset;
  }
    
   
  /**************************************
   *Draws the figure at lineNumber lines down
   *from the current line.
   ***************************************/
  public void drawAt(int lineNumber)
  {
    int count;
    for (count = 0; count < lineNumber; count++)
      System.out.println(); 
    drawHere();
  }
  /**********************************
   *Draws the figure at the current line.
   **********************************/
  public void drawHere()
  {
    int count;
    for (count = 0; count < offset; count++)
      System.out.print(' ');
    System.out.println('*');
  }
 }

This is just so you can have something to test. You do not intend to use this version of drawHere in any application. You will override the definition of drawHere when you define classes for boxes and triangles.

The method drawAt has one parameter of type int. The method drawAt inserts a number of blank lines equal to this parameter and then draws the figure by calling drawHere. Of course, in this case, that does not produce very much, but when we override drawHere, then drawAt will also produce more interesting figures.

Next you turn your attention to the class for drawing a box. The class, called Box, will be a derived class of the class Figure. You need to decide on the instance variables and methods that will be added to those in the class Figure. You also need to decide which if any method definitions in Figure will be overridden with a changed definition.

Properties. The class Box inherits the offset instance variables, but you need to add instance variables for the height and the width of the box. Thus, the class definition looks like the following:

public class Box extends Figure
{
  private int height;
  private int width;

  <Still needs method definitions.>
}

Note that you do not list the instance variable offset, which Box inherits from the base class Figure.

Actions. The class Box has the usual constructors and reset methods. It inherits both the method drawAt and the method drawHere from the class Figure. However, you need to override the definition of the method drawHere so that it does indeed draw a box. So, you put drawHere on your list of methods to define.

Next, you consider the method drawAt. Does it need to be overridden? When you look at the method drawAt (Display 6), you will see that as long as drawHere is correctly defined, the method drawAt will work fine for a box or any other figure. Thus, you need not redefine the method drawAt. You only need to redefine the method drawHere.

Let's look first at a sample constructor. You will design one of the constructors so that it sets all instance variables to values given as arguments to the constructor. But, one instance variable, namely, offset, is a private instance variable of the base class Figure. Thus, you cannot access it directly, but must use a call to the base class constructor, super. Thus, the definition of this constructor is

public Box(int theOffset, int theHeight, int theWidth)
{
  super(theOffset);
  height = theHeight;
  width = theWidth;
}

The default constructor and reset method that you define are shown in Display 7. Note that the reset method needs to use an accessor method to reset the private instance variable offset, which is inherited from the base class Figure.

Display 7—The Box Class

/**************************************************
 *Class for a rectangular box to be drawn on the screen. 
 *Because each character is higher than it is wide, these
 *boxes will look higher than you might expect. Inherits
 *getOffset, setOffset, and drawAt from the class Figure.
 **************************************************/
public class Box extends Figure
{
  private int height;
  private int width;
 
  public Box()
  {
    super();
    height = 0;
    width = 0;
  }
  
  public Box(int theOffset, int theHeight, int theWidth)
  {
    super(theOffset);
    height = theHeight;
    width = theWidth;
  }
   
  public void reset(int newOffset, int newHeight, int newWidth)
  {
    setOffset(newOffset);
    height = newHeight;
    width = newWidth;
  }
  
  /**********************************
   *Draws the figure at the current line.
   **********************************/
  public void drawHere()
  {
    drawHorizontalLine();
    drawSides();
    drawHorizontalLine();
  }

  private void drawHorizontalLine()
  {
    spaces(getOffset()); 
    int count; 
    for (count = 0; count < width; count++)
      System.out.print('_');
    System.out.println();    
  }

  private void drawSides()
  {
    int count;
    for (count = 0; count < (height _ 2); count++)
      drawOneLineOfSides();
  }
  
  private void drawOneLineOfSides()
  {
    spaces(getOffset()); 
    System.out.print('|');
    spaces(width _ 2);    
    System.out.println('|');
  }

  //Writes the indicated number of spaces.
  private static void spaces(int number)
  {
    int count; 
    for (count = 0; count < number; count++)
      System.out.print(' ');
  }
}

In your definition of the default constructor for the class Box, you could omit the call to the base-class constructor super(), and it would be called automatically anyway, but you decide to leave it in for clarity.

Most of these definitions of constructors and accessor methods are similar to what they would be in any class definition. The definition of the method drawHere, however, depends heavily on the particulars of what figure it is drawing. You decide to define the method drawHere using the technique known as top-down design. The basic technique in top-down design is to break down the task to be done by the method into subtasks. You decide on the following subtasks:

To Draw a Box: 
1. Draw the top line.
2. Draw the side lines.
3. Draw the bottom line.

Note that not every way of choosing subtasks will work. You might at first be tempted to have two subtasks, one for each side of the box. However, output must be produced one line after the other and you are not allowed to back up, so you must draw the two sides together (if you want them to be side by side as they should be).

Thus, the definition of the method drawHere is easy:

public void drawHere()
{
  drawHorizontalLine();
  drawSides();
  drawHorizontalLine();
}

Although that was easy, it does postpone most of the work. You still need to define the methods drawHorizontalLine and drawSides. Because these are helping methods, they will be private methods.

You come up with the following pseudocode for drawHorizontalLine:

Output offset blank spaces.
Output width copies of the character '_'.
System.out.println();

Next, you turn your attention to the method drawSides. This task is to draw a figure like the following:

|    |
|    |
|    |
|    |

Noticing that each line is identical, you decide to break out the writing of one of these lines as a subtask. So, the definition of the method drawSides is

private void drawSides()
{
  int count;  
  for (count = 0; count < (height _ 2); count++)
    drawOneLineOfSides();
}

Note that you output two fewer lines than the height. The top and bottom horizontal lines account for those extra two units of height.

Just about all that there is left to do is to define the helping method drawOneLineOfSides. You design the following pseudocode for drawOneLineOfSides:

spaces(getOffset()); 
System.out.print('|');
spaces(width _ 2);
System.out.println('|');

Because you already have a method for the subtask of writing spaces, the pseudocode turns out to be Java code, and so the definition of drawOneLineOfSides is done. The complete class definition of Box is given in Display 7.

Although we will not stop to describe the testing process in this case study, all the methods in the class Figure, the class Box, and the class Triangle (which we have not yet discussed) need to be tested. Remember, each method should be tested in a program in which it is the only untested method.

Display 8 contains the definition of the class Triangle. You can design that class using the same techniques you used to design the class Box. We will only discuss one part of the method drawHere for which the technical details may not be clear at first reading. The method drawHere divides its task into two subtasks, draws the inverted V for the top of the triangle, and draws the horizontal line for the bottom of the triangle. We will only discuss the method drawTop that draws the inverted V.

Display 8—The Triangle Class

/***********************************************************
 *Class for triangles to be drawn on screen. For this class, 
 *a triangle points up and is completely determined by the size of
 *its base. (Screen character spacing determines the length of the
 *sides, given the base.)
 *Inherits getOffset, setOffset, and drawAt from Figure.
 ***********************************************************/
public class Triangle extends Figure
{
  private int base;

  public Triangle()
  {
    super();
    base = 0;
  }
  
 
  public Triangle(int theOffset, int theBase)
  {
    super(theOffset);
    base = theBase;
  }
 
 
  public void reset(int newOffset, int newBase)
  {
    setOffset(newOffset);
    base = newBase;
  }


  /******************************
   *Draws the figure at current line.
   ******************************/
  public void drawHere()
  {
    drawTop();
    drawBase();
  }

  private void drawBase()
  {
    spaces(getOffset());
    int count;  
    for (count = 0; count < base; count++)
      System.out.print('*'); 
    System.out.println();    
  }
  
  private void drawTop()
  {
    //startOfLine will be the number of spaces to the first '*' on a
    //line. Initially set to the number of spaces before the top '*'.
    int startOfLine = getOffset() + (base/2);
    spaces(startOfLine);
    System.out.println('*');//top '*'
    int count;
    int lineCount = (base/2) _ 1;//height above base
    //insideWidth == number of spaces between the two '*'s on a line.
    int insideWidth = 1;
    for (count = 0; count < lineCount; count++)
    {
      //Down one line so the first '*' is one more space to the left.
      startOfLine_ _;
      spaces(startOfLine);
      System.out.print('*');
      spaces(insideWidth);
      System.out.println('*');
      //Down one line so the inside is 2 spaces wider.
      insideWidth = insideWidth + 2;
    }
  }
  
  private static void spaces(int number)
  {
    int count; 
    for (count = 0; count < number; count++)
      System.out.print(' ');
  }
}

The method drawTop draws a figure like the following:

          *
         * *
        *   *
       *     *
      *       *
     *         *
    *           *

Note that there is an offset for the entire figure. The indenting for the wide bottom of the figure is exactly this offset. But going up from bottom to top, each line has a greater indentation. Alternatively, going down (as the computer must go), each line has a slightly smaller indentation. The indentation is smaller by one character each line. So if the indentation is given by the value of the int variable startOfLine, then the indentation can be performed by

spaces(startOfLine);

This can be made one line of a loop, and the value of startOfLine will be decreased by one on each loop iteration. The size of the gap between the two asterisks on the same line increases by two as you go down one line. If this gap is given by the value of the int variable insideWidth, then the loop for drawing all of the inverted V except for the top asterisk can be

for (count = 0; count < lineCount; count++)
{
  spaces(startOfLine);
  System.out.print('*');
  spaces(insideWidth);
  System.out.println('*');
  insideWidth = insideWidth + 2;
  startOfLine___;//THIS LINE WILL MOVE.
}

The complete method definition of drawTop is given in Display 8. The preceding loop is given in color. Also, in order to accommodate the code that comes before the loop, the line

startOfLine___;

becomes the first line of the loop instead of the last, but it is still decremented once on each loop iteration.

To complete this project you produce the sample application program shown in Display 9.

Display 9—Character Graphics Application

public class GraphicsDemo
{
  public static final int indent = 5;
  public static final int topWidth = 21;
  public static final int bottomWidth = 4;
  public static final int bottomHeight = 4;
  
  public static void main(String[] args)
  {
    System.out.println("    Save the Redwoods!");
 
    Triangle top = new Triangle(indent, topWidth);
    Box base = new Box(indent + (topWidth/2) _ (bottomWidth/2),
                  bottomHeight, bottomWidth);
    top.drawAt(1);
    base.drawAt(0);
  }
}
Screen Output 

    Save the Redwoods!
  
             *
            * *
           *   *
          *     *
         *       *
        *         *
       *           *
      *             *
     *               *
    *                 *
   *********************
           ----
           |  |
           |  |
           ---- 

Abstract Classes (Optional)

The class Figure defined in Display 6 was not intended to be used to create objects of the class Figure, but was designed to be used as a base class to derive other classes, such as the class Box (Display 7). Although we did not really need to create objects of the class Figure, it is still legal to do so, as in the second of the following two statements

Figure figureVariable;
figureVariable = new Figure();

However, in order to make this legal, we needed to give a definition to the method drawHere in the class Figure. We repeat that class definition in what follows:

public void drawHere()
{
  int count;
  for (count = 0; count < offset; count++)
    System.out.print(' ');
  System.out.println('*');
}

This definition of the method drawHere is just a placeholder. It does draw a single asterisk, but that was just so something would happen if you invoked it. But, we never intended to invoke the method drawHere with an object that is just a plain old Figure. We intended to use the method drawHere only with objects of derived classes (like the derived classes Box and Triangle). In such cases, you can declare the method to be abstract. This is done as follows:

public abstract void drawHere();

Note that the reserved word abstract is used in the method heading, that there is no method body, and that the method heading is followed by a semicolon. An abstract method is not intended to be used for anything, but is intended to be overridden in every class that is derived from the base class. If a method is abstract, then in any (nonabstract) derived class the method must be overridden and given a "new" definition.

Java requires that if a class has at least one abstract method, then the class must be declared to be abstract by including the reserved word abstract in the heading of the class definition, like in the following:

public abstract class Figure
{
  ...

A class defined in this way is said to be an abstract class. If a class is abstract, you cannot have objects of that class (unless they are objects of a derived class). An abstract class can only be used as a base class to derive other classes.

In Display 10 we have redone the class Figure as an abstract class. If we use this abstract version of the class Figure, then all of our derived classes would work as before, but we could not create objects that were just plain old Figures.

Display 10—The Figure Class Redone as an Abstract Class (Optional)

/*********************************************************
 *Abstract class for simple character graphics figures to send to 
 *screen. It is intended to be used as a base class for the kinds
 *of figures that will be used in graphics applications. 
 *********************************************************/
public abstract class Figure
{
  private int offset; 
 
  public abstract void drawHere();

  <All constructors and methods are identical to those in Display 7,        
  except for the method drawHere. Methods other than drawHere do NOT use the       
  reserved word abstract in their headings. Below we repeat one such method 
  definition:>

  /**************************************
   *Draws the figure at lineNumber lines down
   *from the current line.
   ***************************************/
  public void drawAt(int lineNumber)
  {
    int count;
    for (count = 0; count < lineNumber; count++)
      System.out.println(); 
    drawHere();
  } 
}

Note that even though the class Figure is abstract, not all of the methods are abstract. All the method definitions, except for the method drawHere, are exactly the same as in our original definition. They are full definitions (and do not use the reserved word abstract). When it makes sense to give a regular method definition in an abstract class, it should be given. This way as much detail as possible is pushed into the abstract class, so that such detail need not be repeated in derived classes.

Why have abstract classes? They simplify your thinking. We have already explained why we defined the class Figure. We defined the class Figure so we would only have to write the definitions of methods such as drawAt once for every kind of figure. An abstract class makes it easier to define classes like Figure by relieving you of the obligation to write useless method definitions. It says that if there is no point in defining some method, because it will always be overridden, then make it an abstract method (and so make the class abstract), and then you need not write the pointless method definition.

An abstract method serves a purpose, even though it is not given a full definition. An abstract method serves as a placeholder for a method that must be defined in all (nonabstract) derived classes. Note that in Display 10 the method drawAt includes an invocation of the method drawHere. If the abstract method drawHere were omitted, then this invocation of drawHere would be illegal.

Dynamic Binding and Polymorphism

In this section we discuss a very important feature of object-oriented programming (and of Java). We discuss one of the ways that different objects can have different definitions for the same method name, such as the method name drawAt. So, if b is a box object and t is a triangle object, then b and t use different definitions of drawAt, even though b and t may both be named by variables of the same type—in our example both objects will be named by variables of type Figure. (Hint: b and t are in more than one class, but now we are anticipating. So, let's get started.)

Dynamic Binding

Look at the definition of the method drawAt in the class Figure (Display 6). It makes a call to the method drawHere. If the only class around were Figure, this would not be anything exciting. But, we derived the class Box from the base class Figure. The class Box inherits the method drawAt from the class Figure, but the method Box overrides the definition of the method drawHere. "So what?" you may say. So plenty! Look at the poor compiler's job.

Consider the following:

Box b = new Box(1, 4, 4);
b.drawAt(2);

The method drawAt was defined in the class Figure, but it calls the method drawHere that was redefined in the method Box. The code for drawAt was compiled with the class Figure, and that class was compiled before the class Box was even written. So, this compiled code is using a definition of the method drawHere that was not even written at the time that drawAt was compiled. How can that be? When the code for drawAt is compiled, nothing is inserted for the call to drawHere except for an annotation that says, "use the currently applicable definition of drawHere." Then, when you invoke b.drawAt(2), the compiled code for drawAt reaches the annotation equivalent to "use the currently applicable definition of drawHere." This annotation is replaced by an invocation of the version of drawHere that goes with b. Because, in this case, b is of type Box, the version of drawHere will be the definition in the class Box.

It is important to note that the decision of which method definition to use depends on the object's place in the inheritance chain. It is not determined by the type of the variable naming the object. For example, consider the following code:

Box b = new Box(1, 4, 4);
Figure f = b;
f.drawAt(2);

As we noted earlier in this chapter, it is perfectly legal to assign an object of the class Box to a variable of type Figure. But, the object still remembers that it was created as a Box. In this case, f.drawAt(2) will use the definition of drawAt given in Box, not the definition of drawAt given in Figure. To determine which definition of drawAt to use, Java checks which class was used when the object was created using new.

(Java is so good at figuring out which definition of a method to use that even a type cast will not fool it. The meaning of b.drawAt(2) will always have the meaning defined in the method Box, even if you use a type cast to change the type of b to the type Figure such as the following:

Box b = new Box(1, 4, 4);
Figure f = (Figure)b;
f.drawAt(2);

In this case, f.drawAt(2) will use the definition of drawAt given in Box, not the definition of drawAt given in Figure.)

This way of handling an invocation of a method that may be overridden later is called dynamic binding or late binding, because the meaning of the method invocation is not bound to the location of the method invocation until you run the program. If Java did not use dynamic binding, then when you ran the preceding code, you would not see a triangle on the screen, but would only see what was drawn by the method drawHere of the class Figure (which happens to be a single asterisk).

Other languages do not automatically do dynamic binding as Java does. In many other languages, you must specify in advance what methods may need dynamic binding. This makes Java less efficient but easier to program and less prone to errors.

To see that dynamic binding really is a big deal, consider the following code:

Figure f;
Box b = new Box(1, 4, 4);
f = b;
f.drawAt(2);
Triangle t = new Triangle(1, 21);
f = t;
f.drawAt(2);

The two lines shown in color are identical, yet they use different method definitions for the name drawAt! The first draws a box and the second draws a triangle. An object remembers what method definitions it had when it was created with new. You can place the object in a variable of different (but ancestor) class type, but that has no effect on which method definition the object uses for an overwritten method. Let's pursue this a bit more to see that it is even more dramatic than it may appear at first glance.

Note that objects of the classes Box and Triangle inherit the method drawAt from the class Figure. The method drawAt is not overridden in the definitions of either of the classes Box or Triangle. So, the text of the method definition is even the same for both Boxes and Triangles! It is the method drawHere, which is invoked within the definition of drawAt that is (directly) overridden. In this situation you might say that the method name drawAt is indirectly overridden in the classes Box and Triangle.

Type Checking and Dynamic Binding

You need to be aware of how dynamic binding interacts with the Java compiler's type checking. For example, if Employee is a derived class of the class Person, then you can assign an object of type Employee to a variable of type Person, as in

Person p = new Employee();

But, that is not the end of the story.

Although you can assign an object of type Employee to a variable of type Person, you can only invoke a method that is in the class Person when the calling object is the variable p (of type Person). However, if the method is overridden in the definition of the class Employee and the object named by the variable p is of type Employee, then it is the version of the method defined in Employee that will be used. So, the variable determines what method names can be used, but the object determines which definition of the method name will be used. If you want to use a method name that was first introduced in the class Employee with the object named by the variable p of type Person, then you must use a type cast. Something like the following will work: (Assume setEmployeeNumber was first defined in the class Employee.)

Employee e = (Employee)p; 
e.setEmployeeNumber(5678);

Another example may help. Recall that Student (Display 2) is a derived class of Employee (Display 1). Now, suppose you have the following line in a program:

Person p = new Student("Joe", 1234);

The following is then legal:

p.setName("Josephine");
p.writeOutput();

And the second invocation will use the definition of writeOutput that was given in the class Student. (The object, not the variable, determines which definition of a method name will be used.)

On the other hand, the following is illegal, because setStudentNumber is not the name of a method in the class Person. (The variable determines which method names can be used.)

p.setStudentNumber(1234); //ILLEGAL

The variable p is of type Person, but the object in the variable p is still an object of type Student, and the object can still invoke the method setStudentNumber, but the compiler does not know this! To make the invocation legal you need a type cast, such as the following:

Student s = (Student)p;
s.setStudentNumber(1234); //Legal

You may think this is all just a silly exercise, because you would never assign an object of type Student to a variable of type Person. Not so. You may not often do the assignment directly, but you may very often do it without realizing it. Recall that you can "plug in" an argument of type Student for a method parameter of type Person. And, a parameter is a local variable that is assigned the value of the argument that is "plugged in" for it! In this case an object of type Student (the argument in the method invocation) is assigned to a variable of type Person (the parameter in the method definition).

(Yes, it is really the object reference, that is, the memory address of the object that is assigned to the variable, but that is a side issue and not relevant to the point at hand. Hey, this is complicated enough as is!)

Dynamic Binding with toString (Optional)

We noted that if you add an appropriate toString method to the class Student, then you can give output to the screen using the toString method, as illustrated by the following:

Student s = new Student("Joe Student", 2001);
System.out.println(s.toString());

Thanks to dynamic binding, you do not even need to use the method toString in your invocation of System.out.println. The following will work just as well and will produce the exact same output:

Student s = new Student("Joe Student", 2001);
System.out.println(s);

The method invocation System.out.println(s) is an invocation of the method println with the calling object System.out. One definition of the method println has a single argument of type Object. The definition is equivalent to the following:

public void println(Object theObject)
{
  System.out.println(theObject.toString()):
}

(The invocation of the method println inside the curly brackets is a different, overloaded, definition of the method println. That invocation inside the curly brackets uses a method println that has a parameter of type String, not a parameter of type Object.)

This definition of println was given before the class Student was defined. Yet in the invocation

 System.out.println(s);

with an object s of type Student (and hence also of type Object), it is the definition of toString in the class Student that is used, not the definition of toString in the class Object. Dynamic binding is what makes this work.

Polymorphism

Polymorphism comes from a Greek word meaning many forms. Its root meaning (in Computer Science) is using different definitions of the same method name in different contexts. With that original meaning, such things as overloading were considered polymorphism. However, the modern usage of the term is much more specific. As the word is currently used, it refers to the dynamic binding mechanism that determines which method action will be used for a (directly or indirectly) overridden method name. Thus, in current usage, polymorphism is basically another term to refer to dynamic binding.

So why is the title of this section "Dynamic Binding and Polymorphism"? Isn't that like saying "Dynamic Binding and Dynamic Binding?" Well, yes, but the terms still have a different feel to them. Dynamic binding is thought of as a process carried out by the computer, and polymorphism is thought of as something the objects do. (I know the objects are really in the computer. I said a different feel, not a totally different meaning.)

Polymorphism is one of the key features of object-oriented programming. In fact object-oriented programming is usually defined by listing its main features, which most authors list as encapsulation, inheritance, and polymorphism. So, polymorphism is a central feature of the object-oriented programming philosophy.

  • + Share This
  • 🔖 Save To Your Account

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020