Inheritance in Java
Like mother, like daughter.
Common saying
In this article, we cover inheritance, one of the key concepts in object-oriented programming, and one that is needed in order to use many of the libraries that come with the Java programming language. Inheritance will allow you to use an existing class to help you define new classes, making it easier to reuse software.
A related concept in object-oriented programming is polymorphism, which we also discuss in this article. Polymorphism is a way to use inheritance so different kinds of objects use different definitions (different actions) for the same method name.
Inheritance Basics
All men are mortal.
Socrates is a man.
Therefore Socrates is mortal.
Typical Syllogism
Inheritance is a major component of object-oriented programming. Inheritance will allow you to define a very general class, and then later define more specialized classes by simply adding some new details to the older more general class definition. This saves work, because the more specialized class inherits all the properties of the general class and you, the programmer, need only program the new features.
For example, you might define a class for vehicles that has instance variables to record the vehicle's number of wheels and maximum number of occupants. You might then define a class for automobiles, and let the automobile class inherit all the instance variables and methods of the class for vehicles. The class for automobiles would have added instance variables for such things as the amount of fuel in the fuel tank and the license plate number, and would also have some added methods. (Some vehicles, such as a horse and wagon, have no fuel tank and normally no license plate, but an automobile is a vehicle that has these "added" items.) You would have to describe the added instance variables and added methods, but if you use Java's inheritance mechanism, you would get the instance variables and methods from the vehicle class automatically.
Before we construct an example of inheritance within Java, we first need to set the stage with the following Programming Example.
Programming Example: A Person Class
Display 1 contains a simple class called Person. This class is so simple that the only property it gives a person is a name. We will not have much use for the class Person by itself, but we will use the class Person in defining other classes. So, it is important to understand this class.
Display 1A Base Class
public class Person { private String name; public Person() { name = "No name yet."; } public Person(String initialName) { name = initialName; } public void setName(String newName) { name = newName; } public String getName() { return name; } public void writeOutput() { System.out.println("Name: " + name); } public boolean sameName(Person otherPerson) { return (this.name.equalsIgnoreCase(otherPerson.name)); } }
Most of the methods for the class Person are straightforward. Notice that the method setName and the constructor with one String parameter do the same thing. We need these two methods, even though they do the same thing, because only the constructor can be used after new when we create a new object of the class Person, but we need a different method, such as setName, to make changes to an object after the object is created.
The method sameName is similar to the equals methods we've seen, but since it uses a few techniques that you may not have completely digested yet, let's go over that definition, which we reproduce in what follows:
public boolean sameName(Person otherPerson) { return (this.name.equalsIgnoreCase(otherPerson.name)); }
Recall that when the method sameName is used, there will be a calling object of the class Person and an argument of the class Person. The sameName method will tell whether the two objects have the same name. For example, here is some sample code that might appear in a program:
Person p1 = new Person("Sam"); System.out.println("Enter the name of a person:"); String name = SavitchIn.readLine(); Person p2 = new Person(name); if (p1.sameName(p2)) System.out.println("They have the same name."); else System.out.println("They have different names.");
Consider the call p1.sameName(p2). When the method sameName is called, the this parameter is replaced with p1, and the formal parameter otherPerson is replaced with p2, so that the value of true or false that is returned is
p1.name.equalsIgnoreCase(p2.name)
Thus, the two objects are considered to have the same name (sameName will return true) provided the two objects have the same value for their name instance variables (ignoring any differences between uppercase and lowercase letters).
So, if the user enters Sam in response to the prompt
Enter the name of a person:
then the output will be
They have the same name.
If instead of Sam, the user enters Mary, then the output will be
They have different names.
Derived Classes
Suppose we are designing a college record-keeping program that has records for students, faculty, and (nonteaching) staff. There is a natural hierarchy for grouping these record types. They are all records of people. Students are one subclass of people. Another subclass is employees, which includes both faculty and staff. Students divide into two smaller subclasses: undergraduate students and graduate students. These subclasses my further subdivide into still smaller subclasses.
Although your program may not need any class corresponding to people or employees, thinking in terms of such classes can be useful. For example, all people have names, and the methods of initializing, outputting, and changing a name will be the same for student, staff, and faculty records. In Java, you can define a class called Person that includes instance variables for the properties that belong to all subclasses of people, such as students, faculty, and staff. The class definition can also contain all the methods that manipulate the instance variables for the class Person. In fact, we have already defined such a Person class in Display 1.
Display 2 contains the definition of a class for students. A student is a person, so we define the class Student to be a derived class of the class Person. A derived class is a class defined by adding instance variables and methods to an existing class.
Display 2A Derived Class
public class Student extends Person { private int studentNumber; public Student() { super();//super is explained in a later section. studentNumber = 0;//Indicating no number yet } public Student(String initialName, int initialStudentNumber) { super(initialName); studentNumber = initialStudentNumber; } public void reset(String newName, int newStudentNumber) { setName(newName); studentNumber = newStudentNumber; } public int getStudentNumber() { return studentNumber; } public void setStudentNumber(int newStudentNumber) { studentNumber = newStudentNumber; } public void writeOutput() { System.out.println("Name: " + getName()); System.out.println("Student Number : " + studentNumber); } public boolean equals(Student otherStudent) { return (this.sameName(otherStudent) && (this.studentNumber == otherStudent.studentNumber)); } }
The existing class that the derived class is built upon is called the base class. In our example, Person is the base class and Student is the derived class. The derived class has all the instance variables and methods of the base class, plus whatever added instance variables and methods you wish to add. If you look at Display 2, you will see that the way we indicate that Student is a derived class of Person is by including the phrase extends Person on the first line of the class definition, so that the class definition of Student begins.
public class Student extends Person
When you define a derived class, you give only the added instance variables and the added methods. For example, the class Student has all the instance variables and all the methods of the class Person, but you do not mention them in the definition of Student. Every object of the class Student has an instance variable called name, but you do not specify the instance variable name in the definition of the class Student. The class Student (or any other derived class) is said to inherit the instance variables and methods of the base class that it extends.
For example, suppose you create a new object of the class Student as follows:
Student s = new Student();
There is an instance variable s.name. Because name is a private instance variable, it is not legal to write s.name (outside of the definition of the class Person), but the instance variable is there, and it can be accessed and changed. Similarly, you can have the following method invocation:
s.setName("Warren Peace");
The class Student inherits the method setName (and all the other methods of the class Person) from the base class Person.
A derived class, like Student, can also add some instance variables and/or methods to those it inherits from its base class. For example, Student adds the instance variable studentNumber and the methods reset, getStudentNumber, setStudentNumber, writeOutput, and equals, as well as some constructors. (But we will postpone the discussion of constructors until we finish explaining the other parts of these class definitions.)
Display 3 contains a very small demonstration program to illustrate inheritance. Notice that the object s can invoke the method setName, even though this is a method of its base class Person. The class Student inherits setName from the class Person. The class Student also adds new methods. In the sample program, the object s of the class Student invokes the method setStudentNumber. The method setStudentNumber was not in the class Person.
Display 3Demonstrating Inheritance
public class InheritanceDemo { public static void main(String[] args) { Student s = new Student(); s.setName("Warren Peace"); //setName is inherited from the class Person. s.setStudentNumber(2001); s.writeOutput(); } }
Screen Output Name: Warren Peace Student Number: 2001
Overriding Method Definitions
In the definition of the class Student, we added a method called writeOutput that has no parameters. But, the class Person also has a method called writeOutput that has no parameters. If the class Student were to inherit the method writeOutput from the base class Person, then Student would contain two methods with the name writeOutput, both of which have no parameters. Java has a rule to avoid this problem. If a derived class defines a method with the same name as a method in the base class and that also has the same number and types of parameters as in the base class, then the definition in the derived class is said to override the definition in the base class, and the definition in the derived class is the one that is used for objects of the derived class.
For example, in Display 3, the following invocation of the method writeOutput for the object s of the class Student will use the definition of writeOutput in the class Student, not the definition of the method writeOutput in the class Person:
s.writeOutput();
Although you can change the body of an overridden method definition to anything you wish, you cannot make any changes in the heading of the overridden method. In particular, when overriding a method definition, you cannot change the return type of the method.
Overriding versus Overloading
Do not confuse method overriding with method overloading. When you override a method definition, the new method definition given in the derived class has the exact same number and types of parameters. On the other hand, if the method in the derived class were to have a different number of parameters or a parameter of a different type from the method in the base class, then the derived class would have both methods. That would be overloading. For example, suppose we added the following method to the definition of the class Student:
public String getName(String title) { return (title + getName()); }
In this case, the class Student would have two methods named getName: It would still inherit the method getName from the base class Person (refer to Display 1), and it would also have the method named getName that we just defined. This is because these two methods called getName have different numbers of parameters.
If you get overloading and overriding confused, you do have one small consolation. They are both legal. So, it is more important to learn how to use them than it is to learn to distinguish between them. Nonetheless, you should learn the difference between them.
Gotcha: The final Modifier
If you want to specify that a method definition cannot be overridden with a new definition in a derived class, then you can do so by adding the final modifier to the method heading, as in the following sample heading:
public final void specialMethod() { . . .
You are not very likely to need this modifier, but you are likely to see it in the specification of some methods in standard Java libraries.
If a method is declared to be final, then the compiler knows more about how it will be used, and so the compiler can generate more efficient code for the method.
An entire class can be declared final, in which case you cannot use it as base class to derive any other class from it.
Gotcha: Use of Private Instance Variables from the Base Class
An object of the class Student (Display 2) inherits an instance variable called name from the class Person (Display 1). For example, the following would set the value of the instance variable name of the object joe to "Josephine": (This also sets the instance variable studentNumber to 9891.)
Student joe = new Student("Josephine", 9891);
If you want to change joe.name (and joe.studentNumber), you can do so as follows:
joe.reset("Joesy", 9892);
But, you must be a bit careful about how you manipulate inherited instance variables such as name. The instance variable name of the class Student was inherited from the class Person, but the instance variable name is a private instance variable in the definition of the class Person. That means that name can only be directly accessed within the definition of a method in the class Person. An instance variable (or method) that is private in a base class is not accessible by name in the definition of a method for any other class, not even in a method definition of a derived class. Thus, although the class Student does have an instance variable named name (which was defined in the base class Person), it is illegal to directly access the instance variable name in the definition of any method in the class definition of Student!
For example, the following is the definition of the method reset from the definition of the class Student:
public void reset(String newName, int newStudentNumber) {//Legal definition setName(newName); studentNumber = newStudentNumber; }
You might have wondered why we needed to use the method setName to set the value of the name instance variable. You might be tempted to rewrite the method definition as follows:
public void reset(String newName, int newStudentNumber) {//Illegal definition name = newName;//ILLEGAL! studentNumber = newStudentNumber; }
As the comment indicates, this will not work. The instance variable name is a private instance variable in the class Person, and although a derived class like Student inherits the variable name, it cannot access it directly. It must use some public mutator (or accessor) method to access the instance variable name. The correct way to accomplish the definition of reset in the class Student is the way we did it in Display 2 (which we reproduced as the first of the preceding two possible definitions of reset).
The fact that a private instance variable of a base class cannot be accessed in the definition of a method of a derived class often seems wrong to people. After all, if you are a student and you want to change your name, nobody says, "Sorry, name is a private instance variable of the class Person." After all, if you are a student, you are also a person. In Java, this is also true; an object of the class Student is also an object of the class Person. However, the laws on the use of private instance variables and methods must be as we described, or else they would be pointless. If private instance variables of a class were accessible in method definitions of a derived class, then anytime you wanted to access a private instance variable, you could simply create a derived class and access it in a method of that class, and that would mean that all private instance variables would be accessible to anybody who wants to put in a little extra effort.
Gotcha: Private Methods Are Not Inherited
As we noted in the previous Gotcha section, an instance variable (or method) that is private in a base class is not directly accessible in the definition of a method for any other class, not even in a method definition for a derived class. Note that private methods are just like private variables in terms of not being directly available. But in the case of methods, the restriction is more dramatic. A private variable can be accessed indirectly via an accessor or mutator method. A private method is simply not available. It is just as if the private method were not inherited.
This should not be a problem. Private methods should just be used as helping functions, and so their use should be limited to the class in which they are defined. If you want a method to be used as a helping method in a number of inherited classes, then it is not just a helping method, and you should make the method public.