Before you begin

This unit is part of the “Intro to Java programming” learning path. Although the concepts discussed in the individual units are standalone in nature, the hands-on component builds as you progress through the units, and I recommend that you review the prerequisites, setup, and unit details before proceeding.

Unit objectives

  • Understand the concept of a class hierarchy
  • Know the various ways to implement a constructor
  • Learn when and why to use abstract classes and methods
  • Know how to assign a reference from one class to a variable of a type belonging to another class.

How inheritance works

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 — so 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 1.

Listing 1. Public Person class

public class Person {
  
  public static final String STATE_DELIMITER = "~";
  
  public Person() {
    // Default constructor
  }
  
  public enum Gender {
    MALE,
    FEMALE,
    UNKNOWN
  }
  
  public Person(String name, int age, int height, int weight, String eyeColor, Gender gender) {
    this.name = name;
    this.age = age;
    this.height = height;
    this.weight = weight;
    this.eyeColor = eyeColor;
    this.gender = gender;
  }


  private String name;
  private int age;
  private int height;
  private int weight;
  private String eyeColor;
  private Gender gender;

The Person class in Listing 1 implicitly inherits from Object. Because inheriting from Object is 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 and protected methods and variables.

Defining a class hierarchy

Now suppose you have an Employee class that inherits from Person. Employee‘s class definition would look something like this:


public class Employee extends Person {

  private String taxpayerIdentificationNumber;
  private String employeeNumber;
  private BigDecimal salary;
  // . . .
}

The Employee inheritance relationship to all of its superclasses (its inheritance graph) implies that Employee has access to all public and protected variables and methods in Person (because Employee directly extends Person), as well as those in Object (because Employee actually extends Object, 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 direct superclass, but a class can have any number of subclasses. That’s the most important thing to remember about inheritance hierarchy in the Java language.

Single versus multiple inheritance

Languages like C++ support the concept of multiple inheritance: At any point in the hierarchy, a class can directly inherit from one or more classes. The Java language supports only single inheritance— meaning you can only use the extends keyword with a single class. So the class hierarchy for any Java class always consists of a straight line all the way up to java.lang.Object. However, as you’ll learn in Unit 17: Interfaces, the Java language supports implementing multiple interfaces in a single class, giving you a workaround of sorts to single inheritance.

Constructors and inheritance

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 topic, I’ll review some basic rules about how constructors are defined and invoked.

Constructor basics

Remember that a constructor always has the same name as the class it’s 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 generates 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 just the name of the Person object being created. From Employee‘s default constructor, you could invoke the Person constructor shown in Listing 2:

Listing 2. 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 only if you are sure you’ll need them. Listing 3 defines a constructor in Employee that looks like the one in Person so that they match up. This approach is much less confusing from a maintenance standpoint.

Listing 3. Invoking a superclass

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);
  }
}

Declaring a constructor

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, the following two declarations are functionally identical:


public class Person {
  public Person() {
  }
}
// Meanwhile, in Employee.java
public class Employee extends Person {
  public Employee() {
  }
}


public class Person {
  public Person() {
  }
}
// Meanwhile, in Employee.java
public class Employee extends Person {
  public Employee() {
  super();
  }
}

No-arg constructors

If you provide an alternate constructor, you must explicitly provide the default constructor; otherwise it is unavailable. For example, the following code gives 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() {
  }
}

The Person class in this example has no default constructor, because it provides an alternate constructor without explicitly including the default constructor.

How constructors invoke constructors

A constructor can invoke another constructor in the same class via the this keyword, along with an argument list. Like super(), the this() call must be the first line in the constructor, as in this example:


public class Person {
  private String name;
  public Person() {
    this("Some reasonable default?");
  }
  public Person(String name) {
    this.name = name;
  }

}

You see this idiom frequently. One constructor delegates to another, passing in a default value if that constructor is invoked. This technique is also a great way to add a new constructor to a class while minimizing impact on code that already uses an older constructor.

Constructor access levels

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 might be able to think of use cases in which 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. In that case, I’d use a static method to create instances of the class, and that method — being included in the class — would be allowed to invoke the private constructor.

Inheritance and abstraction

If a subclass overrides a method from a superclass, the method is essentially hidden because calling it through a reference to the subclass invokes the subclass’s version of the method, not the superclass’s version. However, the superclass method is still 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 calls the subclass method if it’s invoked through a reference to the subclass.

This capability also 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 detail can cause you no end of grief as you gain proficiency in Java programming. Eclipse provides ample warnings — for example, that you’re hiding a variable from a superclass, 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 abstracting 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.

Abstract classes and methods

At times, you 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, sometimes 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.

Using abstraction

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 get a compile error:


public void someMethodSomwhere() {
  Employee p = new Employee();// compile error!!
}

The compiler is complaining that Employee is abstract and can’t be instantiated.

The power of abstraction

Suppose that you need a method to examine the state of an Employee object and make sure that it’s valid. This need would seem to be common to all Employee objects, but it would have zero potential for reuse because it would behave differently among all potential subclasses. 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 implements 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 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 path that could restrict your application. You can always refactor common behavior (which is the entire point of having abstract classes) further up the inheritance graph — and it’s almost always better to refactor after you’ve discovered that you do need to. Eclipse has wonderful support for refactoring.

Second, as powerful as abstract classes are, resist using them. Unless your superclasses contain much common behavior and aren’t meaningful on their own, 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.

Assignments: Classes

You can assign a reference from one class to a variable of a type belonging to another class, but certain rules apply. Take a 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 else the compiler gives you an error. Whatever is on the right side of the assignment must be a subclass or the same class as the thing on the left. To put it another way: a subclass is more specific in purpose than its superclass, so think of a subclass as being narrower than its superclass. And a superclass, being more general, is wider than its subclass. The rule is this, you may never make an assignment that will narrow the reference.

Now consider this example:


Manager m = new Manager();
Manager m2 = new Manager();
m = m2; // Not narrower, so okay
Person p = m; // Widens, so okay
Employee e = m; // Also widens
Employee e = p; // Narrows, so not okay!

Although an Employee is a Person, it’s most definitely not a Manager, and the compiler enforces this distinction.

Test your understanding

Refer to the following code listing for questions 2, 3, and 5.


package com.makotojava.intro;

public abstract class Person {
   
   private String name;
   
   String friendlyVariable;
   
   protected Person(String name) {
      this.name = name;
      this.friendlyVariable = name;
   }
   
   protected abstract boolean validate();
   
   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

}

package com.makotojava.intro2;

public class Employee extends Person {
   
   private String title;
   
   public String getTitle() {
      return title;
   }

   public void setTitle(String title) {
      this.title = title;
   }
   
   public static void main(String[] args) {
      Employee e = new Employee();
      
      e.getName();
   }

}

  1. A Java class can directly subclass (via extends) as many other classes as necessary. Explain your answer.

    1. True
    2. False
  2. Refer to the preceding code listing for this question. Employee is a subclass of Person. Explain your answer.

    1. True
    2. False
  3. Refer to the preceding code listing for this question. Employee can freely access the friendlyVariable variable of Person since it is a subclass of Person. Explain your answer.

    1. True
    2. False
  4. Employee is not required to implement the validate() method of Person. Explain your answer.

    1. True
    2. False
  5. Refer to the preceding code listing for this question. Correct the Employee code so that it will compile.

  6. Suppose that class A doesn’t declare a constructor. If class B extends A, it’s not required to implement any constructor. Explain your answer.

    1. True
    2. False
  7. Constructors are inherited just like any other methods. Explain your answer.

    1. True
    2. False
  8. How might a class with two constructors invoke one from the other?

    1. By using the super() keyword
    2. By using the this keyword to represent the constructor. The compiler will generate the rest.
    3. Java classes are not allowed to have more than one constructor.
    4. By default, when two or more constructors are in the same class, the compiler generates method stubs that automatically call each other.
    5. By using the this keyword to represent the called constructor on the first line of code of the calling constructor, and passing the correct arguments
    6. None of the above.
  9. How does one go about instantiating an abstract class?

    1. By invoking its public constructor.
    2. It is not possible to instantiate an abstract class.
    3. There is no such thing as an abstract class.
    4. None of the above.
  10. What is the main purpose of abstraction through the use of generic superclasses?

    1. Abstraction facilitates code reuse.
    2. Abstraction in Java does not work the same as it does in C++ or other languages.
    3. A superclass that implements abstraction cannot be instantiated.
    4. Abstraction provides a way to make the code run faster.

Check your answers

  1. A Java class can directly subclass (via extends) as many other classes as necessary. Explain your answer.

    1. True
    2. False
  2. Refer to the preceding code listing for this question. Employee is a subclass of Person. Explain your answer.

    1. True
    2. False
  3. Refer to the preceding code listing for this question. Employee can freely access the friendlyVariable variable of Person since it is a subclass of Person. Explain your answer.

    1. True
    2. False
  4. Employee is not required to implement the validate() method of Person. Explain your answer.

    1. True
    2. False
  5. Refer to the preceding code listing for this question. Correct the Employee code so that it will compile.

     package com.makotojava.intro2;
    
     import com.makotojava.intro.Person;
    
     public class Employee extends Person {
    
     private String title;
    
     public Employee(String name, String title) {
       super(name);
       this.title = title;
     }
    
     public String getTitle() {
    
       return title;
     }
    
     public void setTitle(String title) {
       this.title = title;
     }
    
     public static void main(String[] args) {
       Employee e = new Employee("John Doe", "Worker Bee");
    
       e.getName();
     }
    
     @Override
     protected boolean validate() {
       // TODO Auto-generated method stub
       return false;
     }
    
     }
    
  6. Suppose that class A doesn’t declare a constructor. If class B extends A, it’s not required to implement any constructor. Explain your answer.

    1. True
    2. False
  7. Constructors are inherited just like any other methods. Explain your answer.

    1. True
    2. False
  8. How might a class with two constructors invoke one from the other?

    1. By using the super() keyword
    2. By using the this keyword to represent the constructor. The compiler will generate the rest.
    3. Java classes are not allowed to have more than one constructor.
    4. By default, when two or more constructors are in the same class, the compiler generates method stubs that automatically call each other.
    5. By using the this keyword to represent the called constructor on the first line of code of the calling constructor, and passing the correct arguments.
    6. None of the above.
  9. How does one go about instantiating an abstract class?

    1. By invoking its public constructor.
    2. It is not possible to instantiate an abstract class.
    3. There is no such thing as an abstract class.
    4. None of the above.
  10. What is the main purpose of abstraction through the use of generic superclasses?

    1. Abstraction facilitates code reuse.
    2. Abstraction in Java does not work the same as it does in C++ or other languages.
    3. A superclass that implements abstraction cannot be instantiated.
    4. Abstraction provides a way to make the code run faster.

Previous: Building Java applicationsNext: Interfaces