Inheritance
You've encountered examples of inheritance a few times already in this tutorial. This section reviews some of Part 1's material on inheritance and explains in more detail how inheritance works — including the inheritance hierarchy, constructors and inheritance, and inheritance abstraction.
Classes in Java code exist in hierarchies. Classes above a given class in a hierarchy are superclasses of that class. That particular class is a subclass of every class higher up the hierarchy. A subclass inherits from its superclasses. The java.lang.Object class is at the top of the class hierarchy, meaning every Java class is a subclass of, and inherits from, Object.
For example, suppose you have a Person class that looks like the one in Listing 12:
Listing 12. Public
Person class
package com.makotogroup.intro;
// . . .
public class Person {
public static final String GENDER_MALE = "MALE";
public static final String GENDER_FEMALE = "FEMALE";
public Person() {
//Nothing to do...
}
private String name;
private int age;
private int height;
private int weight;
private String eyeColor;
private String gender;
// . . .
}
|
The Person class in Listing 12 implicitly inherits from Object. Because that's assumed for every class, you don't need to type extends Object for every class you define. But what does it mean to say that a class inherits from its superclass? It simply means that Person has access to the exposed variables and methods in its superclasses. In this case, Person can see and use Object's public methods and variables and Object's protected methods and variables.
Now suppose you have an Employee class that inherits from Person. Its class definition (or inheritance graph) would look something like this:
public class Employee extends Person {
private String taxpayerIdentificationNumber;
private String employeeNumber;
private BigDecimal salary;
// . . .
}
|
The Employee inheritance graph implies that Employee has access to all public and protected variables and methods in Person (because it directly extends it), as well as Object (because it actually extends that class, too, though indirectly). However, because Employee and Person are in the same package, Employee also has access to the package-private (sometimes called friendly) variables and methods in Person.
To go one step deeper into the class hierarchy, you could create a third class that extends Employee:
public class Manager extends Employee {
// . . .
}
|
In the Java language, any class can have at most one superclass, but a class can have any number of subclasses. That is the most important thing to remember about inheritance hierarchy in the Java language.
Constructors aren't full-fledged object-oriented members, so they aren't inherited; instead, you must explicitly implement them in subclasses. Before I go into that, I'll review some basic rules about how constructors are defined and invoked.
Remember that a constructor always has the same name as the class it is used to construct, and it has no return type. For example:
public class Person {
public Person() {
}
}
|
Every class has at least one constructor, and if you don't explicitly define a constructor for your class, the compiler will generate one for you, called the default constructor. The preceding class definition and this one are identical in how they function:
public class Person {
}
|
Invoking a superclass constructor
To invoke a superclass constructor other than the default constructor, you must do so
explicitly. For example, suppose Person has a constructor that takes the name of the Person object being created. From Employee's default constructor, you could invoke the Person constructor shown in Listing 13:
Listing 13. Initializing a new
Employee
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
}
// Meanwhile, in Employee.java
public class Employee extends Person {
public Employee() {
super("Elmer J Fudd");
}
}
|
You would probably never want to initialize a new Employee object this way, however. Until you get more comfortable with object-oriented concepts, and Java syntax in general, it's a good idea to implement superclass constructors in subclasses if you think you will need them, and invoke them homogeneously. Listing 14 defines a constructor in Employee that looks like the one in Person so that they match up. It's much less confusing from a maintenance standpoint.
Listing 14. Invoking a superclass homogeneously
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
// Meanwhile, in Employee.java
public class Employee extends Person {
public Employee(String name) {
super(name);
}
}
|
The first thing a constructor does is invoke the default constructor of its immediate superclass, unless you — on the first line of code in the constructor — invoke a different constructor. For example, these two declarations are functionally identical, so pick one:
public class Person {
public Person() {
}
}
// Meanwhile, in Employee.java
public class Employee extends Person {
public Employee() {
}
}
|
Or:
public class Person {
public Person() {
}
}
// Meanwhile, in Employee.java
public class Employee extends Person {
public Employee() {
super();
}
}
|
If you provide an alternate constructor, you must explicitly provide the default constructor, or it is not available. For example, the following code would give you a compile error:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
// Meanwhile, in Employee.java
public class Employee extends Person {
public Employee() {
}
}
|
This example has no default constructor, because it provides an alternate constructor without explicitly including the default constructor. This is why the default constructor is sometimes called the no-argument (or no-arg) constructor; because there are conditions under which it is not included, it's not really a default.
How constructors invoke constructors
A constructor from within a class can be invoked by another constructor using the this keyword, along with an argument list. Just like super(), the this() call must be the first line in the constructor. For example:
public class Person {
private String name;
public Person() {
this("Some reasonable default?");
}
public Person(String name) {
this.name = name;
}
}
// Meanwhile, in Employee.java
|
You will see this idiom frequently, where one constructor delegates to another, passing in some default value if that constructor is invoked. It's also a great way to add a new constructor to a class while minimizing impact on code that already uses an older constructor.
Constructors can have any access level you want, and certain rules of visibility apply. Table 1 summarizes the rules of constructor access:
Table 1. Constructor access rules
| Constructor access modifier | Description |
|---|---|
public | Constructor can be invoked by any class. |
protected | Constructor can be invoked by an class in the same package or any subclass. |
| No modifier (package-private) | Constructor can be invoked by any class in the same package. |
private | Constructor can be invoked only by the class in which the constructor is defined. |
You may be able to think of use cases where constructors would be declared protected or even package-private, but how is a private constructor useful? I've used private constructors when I didn't want to allow direct creation of an object through the new keyword when implementing, say, the Factory pattern (see Resources). In that case, a static method would be used to create instances of the class, and that method, being included in the class itself, would be allowed to invoke the private constructor:
If a subclass overrides a method from a superclass, that method is essentially hidden because calling that method through a reference to the subclass invokes the subclass's version of the method, not the superclass's version. This isn't to say the superclass method is no longer accessible. The subclass can invoke the superclass method by prefacing the name of the method with the super keyword (and unlike with the constructor rules, this can be done from any line in the subclass method, or even in a different method altogether). By default, a Java program will call the subclass method if it is invoked through a reference to the subclass.
The same applies to variables, provided the caller has access to the variable (that is, the variable is visible to the code trying to access it). This can cause you no end of grief as you gain proficiency in Java programming. Eclipse will provide ample warnings that you are hiding a variable from a superclass, however, or that a method call won't call what you think it will.
In an OOP context, abstraction refers to generalizing data and behavior to a type higher up the inheritance hierarchy than the current class. When you move variables or methods from a subclass to a superclass, you say you are abstracting those members. The main reason for doing this is to reuse common code by pushing it as far up the hierarchy as possible. Having common code in one place makes it easier to maintain.
There are times when you will want to create classes that only serve as abstractions and do not necessarily ever need to be instantiated. Such classes are called abstract classes. By the same token, you will find that there are times when certain methods need to be implemented differently for each subclass that implements the superclass. Such methods are abstract methods. Here are some basic rules for abstract classes and methods:
- Any class can be declared
abstract. - Abstract classes cannot be instantiated.
- An abstract method cannot contain a method body.
- Any class with an abstract method must be declared
abstract.
Suppose you don't want to allow the Employee class to be instantiated directly. You simply declare it using the abstract keyword, and you're done:
public abstract class Employee extends Person {
// etc.
}
|
If you try to run this code, you'll get a compile error:
public void someMethodSomwhere() {
Employee p = new Employee();// compile error!!
}
|
The compiler is complaining that Employee is abstract and cannot be instantiated.
Suppose that you need a method to examine the state of an Employee object and make sure it is valid. This need would seem to be common to all Employee objects, but would behave sufficiently differently among all potential subclasses that there is zero potential for reuse. In that case, you declare the validate() method abstract (forcing all subclasses to implement it):
public abstract class Employee extends Person {
public abstract boolean validate();
}
|
Every direct subclass of Employee (such as Manager) is now required to implement the validate() method. However, once a subclass has implemented the validate() method, none of its subclasses need to implement it.
For example, suppose you have an Executive object that extends Manager. This definition would be perfectly valid:
public class Executive extends Manager {
public Executive() {
}
}
|
When (not) to abstract: Two rules
As a first rule of thumb, don't abstract in your initial design. Using abstract classes early in the design forces you down a certain path, and that could restrict your application. Remember, common behavior (which is the entire point of having abstract classes) can always be refactored further up the inheritance graph. It is almost always better to do this once you've discovered that you do need it. Eclipse has wonderful support for refactoring.
Second, as powerful as they are, resist the use of abstract classes when you can. Unless your superclasses contain lots of common behavior, and on their own are not really meaningful, let them remain nonabstract. Deep inheritance graphs can make code maintenance difficult. Consider the trade-off between classes that are too large and maintainable code.
When assigning a reference from one class to a variable of a type belonging to another class, you can do so, but there are rules. Let's look at this example:
Manager m = new Manager(); Employee e = new Employee(); Person p = m; // okay p = e; // still okay Employee e2 = e; // yep, okay e = m; // still okay e2 = p; // wrong! |
The destination variable must be of a supertype of the class belonging
to the source reference, or the compiler will give you an error.
Basically, whatever is on the right side of the assignment must be a
subclass or the same class as the thing on the left. If not, it's
possible for assignments of objects with different inheritance graphs
(such as Manager and Employee) to be assigned to a variable of the wrong
type. Consider this example:
Manager m = new Manager(); Person p = m; // so far so good Employee e = m; // okay Employee e = p; // wrong! |
While an Employee is a Person, it is most definitely not a Manager, and
the compiler enforces this.




