Sign up for a free trial of our self-paced courses.

Java Tutorial

Inheritance

In this lesson, you will learn how classes acquire members of other classes.

Lesson Goals

  • Learn about the OOP concept of inheritance.
  • To examine the role of inheritance in a program.
  • Learn how to declare and use Java classes that extend existing classes.
  • Learn about the concept and role of polymorphism in Java.

Inheritance

Inheritance creates a new class definition by building upon an existing definition (you extend the original class).

The new class can, in turn, can serve as the basis for another class definition.

  • All Java objects use inheritance.
  • Every Java object can trace back up the inheritance tree to the generic class Object.

The keyword extends is used to base a new class upon an existing class

Several pairs of terms are used to discuss class relationships (these are not keywords).

Inheritance terms

  • Note that traditionally the arrows point from the inheriting class to the base class, and the base class is drawn at the top - in the Unified Modeling Language (UML) the arrows point from a class to another class that it depends upon (and the derived class depends upon the base class for its inherited code).
  • The parent class/child class terms are not recommended, since parent and child is more commonly used for ownership relationships (like a GUI window is a parent to the components placed in it).

A derived class instance may be used in any place a base class instance would work - as a variable, a return value, or parameter to a method.

Inheritance is used for a number of reasons (some of the following overlap):

  • To model real-world hierarchies.
  • To have a set of pluggable items with the same "look and feel," but different internal workings.
  • To allow customization of a basic set of features.
  • When a class has been distributed and enhancements would change the way existing methods work (breaking existing code using the class).
  • To provide a "common point of maintenance."

When extending a class, you can add new fields and methods and you can change the behavior of existing methods (which is called overriding the methods).

  • You can declare a method with the same signature and write new code for it.
  • You can declare a field again, but this does not replace the original field; it shadows it (the original field exists, but any use of that name in this class and its descendants refers to the memory location of the newly declared element).

Inheritance Examples

Say you were creating an arcade game, with a number of different types of beings that might appear - wizards, trolls, ogres, princesses (or princes), frogs, etc. All of these entities would have some things in common, such as a name, movement, ability to add/subtract from the player's energy - this could be coded in a base class Entity.

For entities that can be chosen and controlled by a player (as opposed to those that merely appear during the course of the game but can't be chosen as a character) a new class Playable could extend Entity by adding the control features.

Then, each individual type of entity would have its own special characteristics; those that can be played would extend Playable, the rest would simply extend Entity.

you could then create an array that stored Entity objects, and fill it with randomly created objects of the specific classes. For example, your code could generate a random number between 0 and 1; if it is between 0.0 and 0.2, create a Wizard, 0.2 - 0.4 a Prince, etc.

Arcade Game Inheritance

The Java API is a set of classes that make extensive use of inheritance. One of the classes used in the GUI is Window; its family tree looks like this:

Inheritance in Java GUI Classes

Payroll with Inheritance

Our payroll program could make use of inheritance if we had different classes of employees: exempt employees, nonexempt employees, and contract employees

  • They all share basic characteristics such as getting paid (albeit via different algorithms), withholding, having to accumulate year-to-date numbers for numerous categories.
  • But they have different handling regarding payment calculations, benefits, dependents, etc.
  • Exempt employees get a monthly salary, while nonexempt get a wage * hours, contract employees are handled similarly to nonexempt, but cannot have benefits or dependents.

This would leave us with an inheritance scheme as follows:

Employee and Dependent Inheritance

Note that a scheme with ContractEmployee extending NonexemptEmployee might also be a reasonable approach

Derived Class Objects

You can view a derived class object as having a complete base class object inside it. Let's assume that the Entity class defines the fields name, energy, and position, and methods moveTo() and changeEnergy().

The Playable class adds a field playerID, and the Wizard class adds a spells field (an array of spells they can cast) and a castSpell() method.

Inheritance as Boxes Within Boxes

Any Wizard object contains all the elements inside its box, include those of the base classes. So, for example, the complete set of properties in a Wizard object is:

  • name
  • energy
  • position
  • playerID
  • spells

A Wizard reference to a Wizard object has access to any public elements from any class in the inheritance chain from Object to Wizard. Code inside the Wizard class has access to all elements of the base classes (except those defined as private in the base class - those are present, but not directly accessible).

A more complete description of access levels is coming up.

Note: although it appears that a base class object is physically located inside the derived class instance, it is not actually implemented that way.

Polymorphism

Inheritance and References

If a derived class extends a base class, it is not only considered an instance of the derived class, but an instance of the base class as well. The compiler knows that all the features of the base class were inherited, so they are still there to work in the derived class (keeping in mind that they may have been changed).

This demonstrates what is known as an Is A relationship - a derived class object is A base class instance as well.

It is an example of polymorphism; that one reference can store several different types of objects. For example, in the arcade game example, for any character that is used in the game, an Entity reference variable could be used, so that at runtime, any subclass can be instantiated to store in that variable.

  • Entity shrek = new Ogre();
    Entity merlin = new Wizard();
  • For the player's character, a Playable variable could be used.
    Playable charles = new Prince();

When this is done, however, the only elements immediately available through the reference are those know to exist; that is, those elements defined in the reference type object. Note that:

  • The compiler decides what to allow you to do with the variable based upon the type declared for the variable.
  • merlin.moveTo() would be legal, since that element is guaranteed to be there.
  • merlin.castSpell() would not be legal, since the definition of Entity does not include it, even though the actual object does have that capability.
  • The following example gives a hint as to why this is the case:
    Entity x;
    		if (Math.random() < 0.5) x = new Wizard();
    		else x = new Troll();
    There is no way the compiler could determine what type of object would actually be created.
  • The variables names above, shrek, merlin, and charles, are probably not good choices: presumably we know shrek is an ogre, and always will be, so the type might as well be Ogre (unless, of course, he could transmogrify into something else during the game ...).

Dynamic Method Invocation

When a method is called through a reference, the JVM looks to the actual class of the instance to find the method. If it doesn't find it there, it backs up to the ancestor class (the class this class extended) and looks there (and if it doesn't find it there, it backs up again, potentially all the way to Object).

Sooner or later, it will find the method, since if it wasn't defined somewhere in the chain of inheritance, the compiler would not have allowed the class to compile.

In this manner, what you could consider the most advanced (or most derived) version of the method will run, even if you had a base class reference.

So, for our arcade game, an Entity reference could hold a Wizard, and when the moveTo method is called, the Wizard version of moveTo will run.

An interesting aspect of dynamic method invocation is that it occurs even if the method is called from base class code. If, for example:

  • The Entity class moveTo method called its own toString method.
  • The Ogre class didn't override moveTo, but did override toString.
  • For an Ogre stored in an Entity variable, the moveTo method was called.

The Entity version of moveTo would run, but its call to toString would invoke the toString method from Ogre!

Creating a Derived Class

The syntax for extending a base class to create a new class is:

[modifiers] class DerivedClassName extends BaseClassName {
	(new or revised field and method definitions go here)
}

If you do not extend any class, Java assumes you are extending Object by default

Your new class can use the fields and methods contained in the original class (subject to the note coming up in a few pages about access keywords), add new data fields and methods, or replace fields or methods.

A derived class object may be stored in a base class reference variable without any special treatment. If you then want to store that object in a derived class reference again, you can force that with a typecast.

Java doesn't allow multiple inheritance, where one class inherits from two or more classes. Note that:

  • It does have a concept called an interface, which defines a set of method names.
  • A class may implement an interface, defining those methods in addition to whatever other methods are in the class.
  • This allows for several otherwise unrelated classes to have the same set of method names available, and to be treated as the same type of object for that limited set of methods.

Inheritance Example - A Derived Class

When a derived class is created, an object of the new class will in effect contain all the members of the base and derived classes. In some cases, the accessibility modifiers can limit base class members availability in the derived class, but we will cover that issue later.

The following maps out the relation between the derived class and the base class (note that the diagrams show the apparent memory allocation, in a real implementation the base class memory block is not inside the derived class block).

Class Code Apparent Memory Allocation
public class MyBase {
	public int x;
	public void show() {
		System.out.println("x =" + x);
}
MyBase memory structure
class MyDerived extends MyBase {
	public int y;
	public void show() {
		System.out.println("x = " + x);
		System.out.println("y = " + y);
	}
}
MyDerived memory structure

Since everything in MyBase is public, code in the MyDerived class has free access to the x value from the MyBase object inside it, as well as y and show() from itself.

The show() method from MyBase is also available, but only within MyDerived class code (but some work is required to get it, since it is hidden by the show() method added with MyDerived) - code in other classes cannot invoke the MyBase version of show() at all.

Inheritance and Access

When inheritance is used to create a new (derived) class from an existing (base) class, everything in the base class is also in the derived class. It may not be accessible; however, the access in the derived class depends on the access in the base class:

Base class access Accessibility in derived class
public public
protected protected
private Inaccessible
Unspecified (package access) Unspecified (package access)

Note that private elements become inaccessible to the derived class - this does not mean that they disappear, or that that there is no way to affect their values, just that they can't be referenced by name in code within the derived class

Also note that a class can extend a class from a different package

Inheritance and Constructors - the super Keyword

Since a derived class object contains the elements of a base class object, it is reasonable to want to use the base class constructor as part of the process of constructing a derived class object.

Constructors are "not inherited". In a sense, this is a moot point, since they would have a different name in the new class, and can't be called by name under any circumstances, so, for example, when one calls new Integer(int i) they shouldn't expect a constructor named Object(int i) to run.

Within a derived class constructor, however, you can use super( parameterList ) to call a base class constructor. Note that:

  • It must be done as the first line of a constructor.
  • Therefore, you can't use both this() and super() in the same constructor function.
  • If you do not explicitly invoke a form of super-constructor, then super() (the form that takes no parameters) will run.
  • For the superclass, its constructor will either explicitly or implicitly run a constructor for its superclass.
  • So, when an instance is created, one constructor will run at every level of the inheritance chain, all the way from Object up to the current class.

Code Sample:

Java-Inheritance/Demos/Inheritance1.java
class MyBase {
  private int x;
  public MyBase(int x) {
    this.x = x;
  }
  public int getX() {
    return x;
  }
  public void show() {
    System.out.println("x=" + x);
  }
}

class MyDerived extends MyBase {
  private int y;
  public MyDerived(int x) {
    super(x);
  } 
  public MyDerived(int x, int y) {
    super(x);
    this.y = y;
  }
  public int getY() {
    return y;
  } 
  public void show() {
    System.out.println("x = " + getX());
    System.out.println("y = " + y);
  }
}

public class Inheritance1 {
  public static void main(String[] args) { 
    MyBase b = new MyBase(2);
    b.show();
    MyDerived d = new MyDerived(3, 4);
    d.show();
  }
}

The diagram below shows the structure of our improved classes:

Apparent memory allocation for expanded base and derived classes

  • A MyDerived object has two constructors available, as well as both the getX and getY methods and the show method.
  • Both MyDerived constructors call the super constructor to handle storage of x.
  • The show method in the derived class overrides the base class version.
  • Note that you can tell which class's version of show() is running by the different spacing around the = sign.
  • x from the base class is not available in the derived class, since it is private in MyBase, so the show method in MyDerived must call getX() to obtain the value.

Derived Class Methods that Override Base Class Methods

As we saw before, you can create a method in the derived class with the same name as a base class method. Note that:

  • The new method overrides (and hides) the original method.
  • You can still call the base class method from within the derived class if necessary, by adding the super keyword and a dot in front of the method name.
  • The base class version of the method is not available to outside code.
  • You can view the super term as providing a reference to the base class object buried inside the derived class.
  • You cannot do super.super. to back up two levels.
  • You cannot change the return type when overriding a method, since this would make polymorphism impossible.

Example: a revised MyDerived using super.show()

class MyDerived extends MyBase {
	int y;
	public MyDerived(int x) {
		super(x);
	} 
	public MyDerived(int x, int y) {
		super(x);
		this.y = y;
	}
	public int getY() {
		return y;
	} 
	public void show() {
		super.show();
		System.out.println("y = " + y);
	}
}

Use of super to call a base class version of an overridden method

Inheritance and Default Base Class Constructors

One base class constructor will always run when instantiating a new derived class object.

  • If you do not explicitly call a base class constructor, the no-arguments base constructor will be automatically run, without the need to call it as super().
  • But if you do explicitly call a base class constructor, the no-arguments base constructor will not be automatically run.
  • The no-arguments (or no-args for short) constructor is often called the default constructor, since it is the one that will run by default (and also because you are given it by default if you write no constructors).

Code Sample:

Java-Inheritance/Demos/Inheritance2.java
class Purple {
  protected int i = 0;
  public Purple() {
    System.out.println("Purple() running and i =  " + this.i);
  }
  public Purple(int i) {
    this.i = i;
    System.out.println("Purple(i) running and i = " + this.i);
  }
}

class Violet extends Purple {
  Violet() {
    System.out.println("Violet() running and i = " + this.i);
  }
  Violet(int i) {
    System.out.println("Violet(i) running and i = " + this.i);
  }
}

public class Inheritance2 {

    public static void main(String[] args) {
    System.out.println("new Violet():");
    new Violet();

    System.out.println();
    System.out.println("new Violet(4):");
    new Violet(4);
  }
}

Each constructor prints a message so that we can follow the flow of execution. Note that using new Violet() causes Purple() to run, and that new Violet(4) also causes Purple() to run.

For the sake of simplicity, the i field has been made protected, but this is not considered a good practice.

If your base class has constructors, but no no-arguments constructor, then the derived class must call one of the existing constructors with super(args), since there will be no default constructor in the base class.

If the base class has a no-arguments constructor that is private, it will be there, but not be available, since private elements are hidden from the derived class. So, again, you must explicitly call an available form of base class constructor, rather than relying on the default.

Try the above code with the Purple() constructor commented out or marked as private.

The Instantiation Process at Runtime

In general, when an object is instantiated, an object is created for each level of the inheritance hierarchy. Each level is completed before the next level is started, and the following takes place at each level:

  1. The memory block for that level is allocated (for derived classes, this means it is sized for the added elements, since the inherited elements were in the base class memory block.
  2. The entire block is zeroed out.
  3. Explicit initializers for that level run, which may involve executable code, for example: private double d = Math.random();
  4. The constructor for that level runs. Note:
    • Since the class code has already been loaded, and any more basic code has been completed, any methods in this class or inherited from superclasses are available to be called from the constructor.
    • Note that if this level's constructor calls a superconstructor, all you are really doing is selecting which form of superconstructor will run at the appropriate time. Timing wise, that superconstructor was run before we got to this point.

When the process has completed, the expression that created the instance evaluates to the address of the block for the last unit in the chain.

Inheritance and static Elements

static methods in a class may not be overridden in a derived class. This is because the static method linkages are not resolved with the same dynamic mechanism that non-static methods use. The linkage is established at compile time.

Example - Factoring Person Out of Employee

Since personnel and probably other people in our overall corporate software suite (contact management, perhaps) would have some basic personal attributes in common, we will pull the first and last name fields into a base class representing a person.

  1. Create a new class, Person, in the employees package (that may not be the best place for it, but for convenience we will put it there).
  2. Cut the first and last name fields and the associated get and set methods out of Employee and paste them here (including getFullName).
  3. Create a constructor for Person that sets the first and last names; it would probably b e a good idea to create a default constructor as well.
  4. Declare Employee to extend Person; change the constructor that accepts the first and last name fields to call a super-constructor to accomplish that task.
  5. Note that if getPayInfo() uses firstName and lastname directly, you will need to revise it to call the get methods for those values - why?
  6. We can test the code using a simplified version of Payroll.

Code Sample:

Java-Inheritance/Demos/employees/Person.java
package employees;

public class Person {
  private String firstName;
  private String lastName;
 
  public Person() {
  }

  public Person(String firstName, String lastName) {
    setFirstName(firstName);
    setLastName(lastName);
  }

  public String getFirstName() { return firstName; }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() { return lastName; }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getFullName() {
    return firstName + " " + lastName;
  }

}

This class includes the name fields and related set and get methods.

Code Sample:

Java-Inheritance/Demos/employees/Employee.java
package employees;

public class Employee extends Person {

---- C O D E   O M I T T E D ----
	public Employee() {
	}
	public Employee(String firstName, String lastName) {
		super(firstName, lastName);
	}
	public Employee(String firstName,String lastName, int dept) {
		super(firstName, lastName);
		setDept(dept);
	}
	public Employee(String firstName, String lastName, double payRate) {
		super(firstName, lastName);
		setPayRate(payRate);
	}
	public Employee(
		String firstName, String lastName, int dept, double payRate) {
	this(firstName, lastName, dept);
	setPayRate(payRate);
	}


---- C O D E   O M I T T E D ----
public String getPayInfo() {
return "Employee " + id + " dept " + dept + " " +
			getFullName() + 
			" paid " + payRate;
	}
}

Since this class now extends Person, the name-related elements are already present, so we remove them from this code. We took advantage of the Person constructor that accepst first and last names in the corresponding Employee constructor.

Note that since getPayInfo calls getFullName, which is now inherited and publicly accessible, that code did not need to change.

Code Sample:

Java-Inheritance/Demos/Payroll.java
import employees.*;
import util.*;

public class Payroll {  
  public static void main(String[] args) {
    String fName = null;
    String lName = null;
    int dept = 0;
    double payRate = 0.0;
    double hours = 0.0;

    Employee e = null;

    fName = KeyboardReader.getPromptedString("Enter first name: ");
    lName = KeyboardReader.getPromptedString("Enter last name: ");
    dept = KeyboardReader.getPromptedInt("Enter department: ");
    do {
      payRate = KeyboardReader.getPromptedFloat("Enter pay rate: ");
      if (payRate < 0.0) System.out.println("Pay rate must be >= 0");
    } while (payRate < 0.0);
    e = new Employee(fName, lName, dept, payRate);
    System.out.println(e.getPayInfo());
  }
}

No changes need to be made to Payroll to take advantage of the addition of the inheritance hierarchy that we added. The only changes we made were for the sake of brevity.

To revisit the sequence of events when instantiating an Employee using the constructor that accepts the first and last names, department, and pay rate:

  1. Memory for an Object is allocated.
  2. Any Object initializers would run.
  3. The Object() constructor would run.
  4. Memory for a Person is allocated.
  5. If there were any Person initializers, they would run.
  6. The Person constructor would run, because that was the version selected by the Employee constructor we called.
  7. Memory for an Employee is allocated.
  8. If there were any Employee initializers, they would run.
  9. Any additional steps in the Employee constructor we called would run.

Typecasting with Object References

Object references can be typecast only along a chain of inheritance. If class MyDerived is derived from MyBase, then a reference to one can be typecast to the other.

An upcast converts from a derived type to a base type, and will be done implicitly, because it is guaranteed that everything that could be used in the base is also in the derived class.

Object o;
String s = new String("Hello");
o = s;

A downcast converts from a parent type to a derived type, and must be done explicitly.

Object o = new String("Hello");
String t;
t = (String) o;

even though o came from a String, the compiler only recognizes it as an Object, since that is the type of data the variable is declared to hold.

As a memory aid, if you draw your inheritance diagrams with the parent class on top, then an upcast moves up the page, while a downcast moves down the page.

More on Object Typecasts

The compiler will not check your downcasts, other than to confirm that the cast is at least feasible. For instance, there is no possibility of a typecase between String and Integer, since neither is a base class to the other.

But, the JVM will check at runtime to make sure that the cast will succeed. A downcast could fail at runtime because you might call a method that is not there, so the JVM checks when performing the cast in order to fail as soon as possible, rather than possibly having some additional steps execute between casting and using a method.

Object o = new Integer(7);
String t, u;

// compiler will allow the following line, but it will fail at runtime
t = (String) o;

// we will never reach this line
u = t.toUpperCase();

Since t was not actually a String, this would fail with a runtime exception during the cast operation. At that point, the runtime engine sees that it cannot make the conversion and fails.

You can use a typecast in the flow of an operation, such as:

MyBase rv = new MyDerived(2, 3);
System.out.println(
	"x=" + rv.getX() + " y=" + ((MyDerived) rv).getY() );

((MyBase) rv) casts rv to a MyDerived, for which the getY() method is run.

Note the parentheses enclosing the inline typecast operation; this is because the dot operator is higher precedence than the typecast; without them we would be trying to run rv.getY() and typecast the result (technically, Java does not consider the dot an operator, but a separator , like curly braces, parentheses, and square brackets - the net effect is the same, since separators get applied before operators).

Typecasting, Polymorphism, and Dynamic Method Invocation

The concept of polymorphism means "one thing - many forms."

In this case, the one thing is a base class variable; the many forms are the different types derived from that base class that can be stored in the variable.

Storing a reference in a variable of a base type does not change the contents of the object, just the compiler's identification of its type - it still has its original methods and fields.

You must explicitly downcast the references back to their original class in order to access their unique properties and methods. If you have upcast, to store a derived class object with a base class reference, the compiler will not recognize the existence of derived class methods that were not in the base class.

The collection classes, such as Vector, are defined to store Objects, so that anything you store in them loses its identity. You must downcast the reference back to the derived type in order to access those methods. The introduction of generics in Java 1.5 provides a solution to this annoyance (more on this in the Collections lesson).

During execution, using a base class reference to call to a method that has been overridden in the derived class will result in the derived class version being used. This is called dynamic method invocation.

The following example prints the same way twice, even though two different types of variable are used to reference the object:

Code Sample:

Java-Inheritance/Demos/Inheritance3.java
public class Inheritance3 {
  public static void main(String[] args) {
    MyDerived mD = new MyDerived(2, 3);
    MyBase mB = mD;
    mB.show();
    mD.show();
  }
}

Situations where you would often see a base class reference:

  • Parameters to methods that only need to work with base class elements of the incoming object.
  • As fields of a class where the class code will only work with the base class elements of the object.
  • Return values from methods where the actual class returned is not important to the user, and you want to hide the actual class from the user - for example, a java.net.Socket object has a public OutputStream getOutputStream () method that returns an instance of a specialized class that will send data across the network connection that the Socket holds; to use this class you only need to know that it is an OutputStream, since there is no way you could use this class without the Socket it is attached to.

More on Overriding

Changing Access Levels on Overridden Methods

You can change the access level of a method when you override it, but only to make it more accessible.

  • You can't restrict access any more than it was in the base class.
  • So, for example, you could take a method that was protected in the base class and make it public.
  • For example, if a method was public in the base class, the derived class may not override it with a method that has protected, private or package access.

This avoids a logical inconsistency:

  • Since a base class variable can reference a derived class object, the compiler will allow it to access something that was public in the base class.
  • If the derived class object actually referenced had changed the access level to private, then the element ought to be unavailable.
  • This logic could be applied to any restriction in access level, not just public to private.

As a more specific example of why this is the case, imagine that ExemptEmployee overrode public String getPayInfo() with private String getPayInfo().

The compiler would allow

Employee e = new ExemptEmployee();

// getPayInfo was public in Employee, so compiler should allow this
e.getPayInfo();
  • Because Employee, the type on the variable e, says that getPayInfo is public.
  • But, now at runtime, it shouldn't be accessible, since it is supposed to be private in ExemptEmployee.

Redefining Fields

A field in a derived class may be redefined, with a different type and/or more restrictive access - when you do this you are creating a second field that hides the first; this is called shadowing instead of overriding.

  • A new field is created that hides the existence of the original field.
  • Since it actually a new field being created, the access level and even data type may be whatever you want - they do not have to be the same as the original field.
  • I don't know of any good reason to do this deliberately, but it is possible.
    • A strange thing happens when you use a base class reference to such a class where the field was accessible (for example, public).
    • The base class reference sees the base class version of the field!
    But, if you were to extend a class from the Java API or other library, you wouldn't necessarily know what fields it had - this facility allows you to use whatever field names you want, and, as long as the base class versions were private, you would not get any adverse effects.

Object Typecasting Example

Say our game program needed to store references to Entity objects.

  • The exact type of Entity would be unknown at compile time.
  • But during execution we would like to instantiate different types of Entity at random.

Perhaps our code for Entity includes a method moveTo() that moves it to a new location. Many of the entities move the same way, but perhaps some can fly, etc. We could write a generally useful form of moveTo in the Entity class, but then override it as necessary in some of the classes derived from Entity.

Code Sample:

Java-Inheritance/Demos/EntityTest.java
class Entity {
  private String name;
  public Entity(String name) { this.name = name; }
  public String getName() { return name; }
  public void move() {
    System.out.println("I am " + name + ". Here I go!");
  }
}

class Playable extends Entity {
  public Playable(String name) { super(name); }
  public void move() {
    System.out.println("I am Playable " + getName() + ". Here we go!");
  }
}

class Ogre extends Entity {
  public Ogre(String name) { super(name); }
}

class Troll extends Entity {
  public Troll(String name) { super(name); }
}

class Prince extends Playable {
  public Prince(String name) { super(name); }
  // does not override public void move()
}

class Princess extends Playable {
  public Princess(String name) { super(name); }
  public void move() {
    System.out.println("I am Princess " + getName() + 
        ". Watch as I and my court move!");
  }
}

class Wizard extends Playable {
  public Wizard(String name) { super(name); }
  public void move() {
    System.out.println("I am the Wizard " + getName() + 
        ". Watch me translocate!");
  }
}

public class EntityTest {
  public static void main(String[] args) {
    String[] names = { "Glogg", "Blort", "Gruff", 
                      "Gwendolyne", "Snow White", "Diana",
                      "Merlin", "Houdini", "Charles", "George" };
    for (int i = 0; i < 10; i++) {
      int r = (int) (Math.random() * 5);
      Entity e = null;
      switch (r) {
        case 0: e = new Ogre(names[i]); break;
        case 1: e = new Troll(names[i]); break;
        case 2: e = new Wizard(names[i]); break;
        case 3: e = new Prince(names[i]); break;
        case 4: e = new Princess(names[i]); break;
      }
      e.move();
    }
  }
}

The compiler allows the calls to the moveTo method because it is guaranteed to be present in any of the subclasses - since it was created in the base class.

  • If not overridden in the derived class, then the base class version will be used.
  • If the method is overridden by the derived class, then the derived class version will be used.

At runtime, the JVM searches for the method implementation, starting at the actual class of the instance, and moving up the inheritance hierarchy until it finds where the method was implemented, so the most derived version will run.

Checking an Object's Type: Using instanceof

Given that you can have base class references to several different derived class types, you will eventually come to a situation where you need to determine exactly which derived class is referenced - you may not know it at the time the program is compiled.

  • In the above example, perhaps wizards, ogres, trolls, etc. have their own special methods.
  • How would you know which method to call if you had an Entity reference that could hold any subclass at any time?

The instanceof operator is used in comparisons - it gives a boolean answer when used to compare an object reference with a class name.

referenceExpression instanceof ObjectType
  • It will yield true if the reference points to an instance of that class.
  • It will also give true if the object is a derived class of the one tested.
  • If the test yields true, then you can safely typecast to call a derived class method (you still need to typecast to call the method - the compiler doesn't care that you performed the test).

For example:

if (e[i] instanceof Wizard) ((Wizard) e[i]).castSpell();

There is another method of testing:

  • Every object has a getClass() method that returns a Class object that uniquely identifies each class.
  • Every class has a class pseudo-property that provides the same Class object as above; this object is the class as loaded into the JVM (technically, class isn't a field, but syntax-wise it is treated somewhat like a static field).
  • With this method, a derived class object's class will compare as not equal to the base class.
if (e[i].getClass().equals(Wizard.class))
	((Wizard) e[i]).castSpell();
  • It is rare that you would need this type of test.

Methods Inherited from Object

There are a number of useful methods defined for Object.

Some are useful as is, such as:

Class getClass() - returns a Class object (a representation of the class that can be used for comparisons or for retrieving information about the class).

Others are useful when overridden with code specific to the new class:

Object clone() - creates a new object that is a copy of the original object. This method must be overridden, otherwise an exception will occur (the Object version of clone throws a CloneNotSupportedException).

The issue is whether to perform a shallow copy or a deep copy - a shallow copy merely copies the same reference addresses, so that both the original object and the new object point to the same internal objects; a deep copy makes copies of all the internal objects (and then what if the internal objects contained references to objects ...? ).

boolean equals(Object) - does a comparison between this object and another. If you don't override this method, you get the same result as if you used == (that is, the two references must point to the same object to compare as equal - two different objects with the same field values would compare as unequal) - that is how the method is written in the Object class. You would override this method with whatever you need to perform a comparison.

int hashCode() - returns an integer value used by collection objects that store elements using a hashtable. Elements that compare as the same using the equals(Object) method should have the same hashcode.

String toString() - converts this object to a string representation.

This method is called by a some elements in the Java API when the object is used in a situation that requires a String, for example, when you concatenate the object with an existing String, or send the object to System.out.println().

Note that the call to toString is not made automatically as a typecast to a String - the only behavior built into the syntax of Java is string concatenation with the + sign (so one of the operands must already be a String); the code in println is explicitly written to call toString.

If you don't override this method, you will get a strange string including the full class name and the hashcode for the object void finalize() - called by the JVM when the object is garbage-collected. This method might never be called (the program may end without the object being collected).

There are also a several methods (wait, notify, and notifyAll) related to locking and unlocking an object in multithreaded situations.

Code Sample:

Java-Inheritance/Demos/ObjectMethods.java
public class ObjectMethods {
  int id;
  String name;
  int age;

  ObjectMethods(int id, String name, int age) {
    this.id = id;
    this.name = name;
    this.age = age;
  }
  public boolean equals(Object x) {
    if (x == this) return true;
    else if (x instanceof ObjectMethods) {
      ObjectMethods omx = (ObjectMethods) x;
      return id == omx.id && name.equals(omx.name) && age == omx.age;
    }
    else return false;
  }
  public int hashCode() {
    return id + age * 1000;
  }
  public String toString() {
    return id + " " + name + " is " + age + " years old";
  }
  public Object clone() {
    return new ObjectMethods(id, name, age);
  }
  public static void main(String[] args) {
    ObjectMethods om1 = new ObjectMethods (1, "John", 6);
    ObjectMethods om2 = new ObjectMethods (1, "John", 6);
    ObjectMethods om3 = new ObjectMethods (2, "Jane", 5);
    ObjectMethods om4 = (ObjectMethods)om3.clone();
    System.out.println("Printing an object: " + om1);
    if (om1.equals(om2)) 
      System.out.println("om1 equals(om2)");
    if (om1.equals(om3))
      System.out.println("om1 equals(om3)");
    if (om1.equals("John"))
      System.out.println("om1 equals(\"John\")");
    if (om3.equals(om4))
      System.out.println("om3 equals(om4) which was cloned from om3");
    System.out.println("object class is: " + om1.getClass());
  }
}

The clone method returns Object rather than ObjectMethods, since that is how it was declared in the Object class, and you can't change the return type when overriding - thus the typecast on the returned value.

Similarly, the parameter to equals is Object, rather than ObjectMethods. This is not required by Java's syntax rules, but rather a convention that enables other classes to work with this class. For example, the Collections API classes use the equals method to determine if an object is already in a set. If we wrote the method as equals(ObjectMethods om) instead of equals(Object o), the collections classes would call equals(Object o) as inherited from Object, which would test for identity using an == test.

The hashCode method was written out of a sense of duty - Sun specifies that the behavior of hashCode "should be consistent with equals", meaning that if two items compare as equal, then they should have the same hash code - this will be revisited in the section on Collections.

Other Inheritance-related Keywords

abstract

abstract states that the item cannot be realized in the current class, but can be if the class is extended. Note:

  • For a class, it states that the class can't be instantiated (it serves merely as a base for inheritance).
  • For a method, it states that the method is not implemented at this level.
  • The abstract keyword cannot be used in conjunction with final.

abstract Classes

abstract Classes are used when a class is used as a common point of maintenance for subsequent classes, but either structurally doesn't contain enough to be instantiated, or conceptually doesn't exist as a real physical entity.

public abstract class XYZ { ... }
  • We will make the Employee class abstract in the next exercise: while the concept of an employee exists, nobody in our payroll system would ever be just an employee , they would be exempt, nonexempt, or contract employees.
  • While you cannot instantiate an object from an abstract class, you can still create a reference variable whose type is that class.

abstract Methods

The method cannot be used in the current class, but only in a inheriting class that overrides the method with code.

public abstract String getPayInfo();
  • The method is not given a body, just a semicolon after the parentheses.
  • If a class has an abstract method, then the class must also be abstract.
  • You can extend a class with an abstract method without overriding the method with a concrete implementation, but then the class must be marked as abstract.

final

final is used to mark something that cannot be changed.

final Classes

The class cannot be extended.

public final class XYZ { ... }

final Methods

The method cannot be overridden when the class is extended.

public final void display() { ... }

final Properties

final Properties marks the field as a constant.

public static final int MAX = 100;
  • A final value can be initialized in a constructor or initializer:
public final double randConstant = Math.random();

or

public final double randConstant;
  • Then, in a constructor:
randConstant = Math.random();
  • Note: String and the wrapper classes use this in two ways:
    1. The class is final, so it cannot be extended.
    2. The internal field storing the data is final as well, but set by the constructor (this makes the instance immutable - the contents cannot be changed once set)
  • in some cases, a declaration of final enables the compiler to optimize methods, since it doesn't have to leave any "hooks" in for potential future inheritance.

Note that final and abstract cannot be used for the same element, since they have opposite effects.