Java 11 Developer Certification - Inheritance - Superclass and Subclass

January 10, 2021

What we are covering in this lesson

  1. Introduction
  2. Subclasses and Superclasses a. Class Elements b. Rules for inheriting class members c. Inherited Fields applications
  3. Code Example on using subclass and superclass
  4. Method/Attribute Hiding

Introduction

Inheritance in an object-oriented language allows entities to be modelled and built from the most general to the most specific type in a hierarchical fashion. Allowing each class derived from another to inherit properties and behaviour from the parent class and extend both properties and behaviour to be more specific, generally.

We have already covered in one of the previous videos about inheritance, and described three types of inheritance:

  • Inheritance of state - state is defined by the class’s static and instance fields.
  • Inheritance of implementation - implementation is defined by the behaviour of the class, its methods.
  • Inheritance of type - an inherited type in Java can be a class or an interface.

In this section, we will cover first two types of inheritance, which are both inherited from another class.

Java supports only single inheritance of state and implementation, meaning that classes can only extend one class or be a subclass of one class directly. The hierarchical tree can certainly be deeper than one level, but a child only has one direct parent or superclass.

  • A class that extends another class is a subclass.
  • A class that can be extended by another class is a superclass.
  • The ultimate superclass of all Java classes is java.lang.Object, from which all classes are implicitly extended.

Subclasses and Superclasses

Every class you create is a subclass of another, even if you don’t declare that. Every class in Java is a subclass of java.lang.object implicitly.

To explicitly declare class inheritance, we use the extends keyword in the class declaration, just like below.

class SubClass extends SuperClass

SubClass in the example is said to be the subclass of the class SuperClass, and can also be said to be a child class or a derived class.

SuperClass is said to be a superclass, or can also be called a base class or parent class.

A subclass is said to be of type SuperClass, and an instance of subclass can be used anywhere an instance of the superclass can be used, i.e., A subclass IS A superclass in Object Oriented Programming.

Suppose the superclass is Animal and subclass is Dog, then it can be said that Dog IS A Animal. So anywhere we need to use Animal instance, a Dog instance should work as well. However, the opposite is not always true, i.e., A Animal may NOT always be a Dog.

Class Elements

A class consists of two types of elements

  • Members: include any field, methods or any nested types (classes, enums, interfaces). Members of a class can be inherited by subclasses.
  • Non-member elements: include constructors, intializer blocks and static blocks. These are not inherited by subclasses.

    • Although constructors are not inherited, there is an implied call to the superclass’s constructor if an explicit call is not made.
    • There is absolutely no way to call intializer blocks or static blocks on the superclass, explicitly from subclass.

Rules for inheriting class members

The rules for inheriting class members are of follows:

  • A subclass does NOT inherit the private members of its parent.
  • A subclass inherits all of the public and protected members of its parent, regardless of the package the subclass is in.
  • If the subclass is in the same package as its parent, it also inherits the package-private members of the parent.

Inherited Fields applications

Inherited members can be used as is, or we can replace them, hide them, or supplement them as follows:

  • Inherited Fields Can Be:

    • Used directly as is, like any other fields.
    • Hidden by the subclass - the subclass declares a field with the same name as the one in the superclass (hiding is not recommended).
    • Supplemented - the subclass can declare new fields that are not in the superclass.
  • Inherited Methods Can Be:

    • Used directly as is, like any other fields.
    • Overridden - ths subclass can declare a new instance method, that has the same signature and return type as the one in the superclass.
    • Hidden by the subclass - the subclass can declare a new static method, that has the same signature as the one in the superclass.
    • Supplemented - the subclass can declare new methods within the subclass, that are not in the superclass.

So it’s important to note that although we say that a subclass does not inherit private fields of the superclass, the object which is instantiated from the sub class type is instantiated with the private field variables of the superclass.

Code Example on using subclass and superclass

Lets look at the below code which will explain the subclass and superclass concepts in detail.

Animal.java

package maxCode.online.inheritance;

import java.time.LocalDate;

public class Animal {
	// We describe elements that all instances would have
	private String name = "Unspecified";
	private String owner = "Unspecified";
	private String breed = "Unspecified";
	private AnimalType type = AnimalType.UNKN;

	// It could be argued a locator chip may or may not be on every pet
	// but we add it here
	private LocalDate chipDate;

	// You can define an enum within a class. Here we
	// define some of the animals we expect to see as subclasses
	static enum AnimalType {
		CAT, DOG, HORSE, HAMSTER, GOAT, SHEEP, UNKN;
	}

	// We'll use to populate properties on Animal through Constructor
	Animal(String name, String owner, String breed, AnimalType type) {
		this.name = name;
		this.owner = owner;
		this.breed = breed;
		this.type = type;
	}

	// toString return's a nice formatted String that describes Animal
	public String toString() {
		return this.owner + "'s " + this.type + " is a " + this.breed + " named " + this.name;
	}

	// We limit getters/setters for demo purposes here, just to this one particular attribute
	public LocalDate getChipDate() {
		System.out.println("parent getChipDate");
		return chipDate;
	}

	public void setChipDate(LocalDate chipDate) {
		this.chipDate = chipDate;
	}

	// static method describing what steps are required to examine the Animal
	public static void examineAnimal() {
		System.out.println("Check eyes");
		System.out.println("Check teeth");
		System.out.println("Check coat");
	}
}

AnimalMain.java

package maxCode.online.inheritance;

import java.time.LocalDate;

class Dog extends Animal {
	// Supplement fields
	private int pedigreeId;

	// Hide field on superclass
	private LocalDate chipDate;

	// Constructors are not inherited, create one, pass thru to super
	public Dog(String name, String owner, String breed, AnimalType type, int pedigreeId) {
		super(name, owner, breed, type);
		this.pedigreeId = pedigreeId;
	}

	// override toString method and extend functionality of the superclass
	public String toString() {
		String str = super.toString();
		if (pedigreeId > 0) {
			str += " (pedigree Id = " + pedigreeId + ")";
		}
		return str;
	}

	public static void examineAnimal() {
		System.out.println("Examine for breathing problems");
	}

	public LocalDate getChildChipDate() {
		System.out.println("child getChildChipDate");
		return chipDate;
	}

	// Hides method on parent
	public void setChipDate(LocalDate chipDate) {
		this.chipDate = chipDate;
	}
}

public class AnimalMain {
	public static void main(String[] args) {
		// Create a Dog
		Dog pug = new Dog("George", "Ralph", "Pug", Animal.AnimalType.DOG, 775533);

		// Call a method on the superclass from the subclass instance
		pug.setChipDate(LocalDate.now());

		// Call the overridden method (note that toString() is implied
		System.out.println(pug);

		// Call the overridden method
		System.out.println("Chip Date = " + pug.getChipDate());

		// Try to force the call to the parent's method
		Animal a = pug;
		System.out.println("Chip Date = " + a.getChipDate());

		// Call the supplemented method
		System.out.println("Chip Date = " + pug.getChildChipDate());

		// Call the static methods
		System.out.println("\nExecuting the Animal static class");
		Animal.examineAnimal();
		
		System.out.println("\nExecuting the Dog static class");
		Dog.examineAnimal();
	}
}

Output

Ralph's DOG is a Pug named George (pedigree Id = 775533)
parent getChipDate
Chip Date = null
parent getChipDate
Chip Date = null
child getChildChipDate
Chip Date = 2021-01-10

Executing the Animal static class
Check eyes
Check teeth
Check coat

Executing the Dog static class
Examine for breathing problems

We have 2 classes, Animal and AnimalMain class. Animal class is the base class which defines common attributes which would apply to any animal. We also have a package private enum AnimalType, with the list of animal types. The data is set in the package private constructor, the toString method returns the formatted output, and a examineAnimal method to print the output.

The main class (AnimalMain) has a Dog class which extends Animal. We are adding a new field pedigreeId and hiding the field chipDate by defining it in this class again. The toString method is also overriden here, which would extend the functionality of toString method of the parent class.

In the main class, we create a new instance of type Dog and later assign this to Animal type of object.

The output shows that when getChipDate is called on the parent, the chip date comes null. This is because we have overriden it in the child class. The examineAnimal method also executes differently for child and parent instances.

We had hid ChipDate on the parent, executing the getChipDate method became a bit confusing, since it could not return the child’s chipDate attribute. If we had created getChipDate() on Dog, we would have removed our ability to access the parent class’s chipDate attribute altogether. So it’s generally a good idea to avoid hiding class attributes including private ones.

Method/Attribute Hiding

Lets look at the concept of hiding here.

  • You hide an instance variable by creating a variable of the same name and type on the subclass.
  • You hide a static variable by creating a static variable of the same name and type on the subclass.
  • You hide a static method by creating a static method with the same name and parameter types on the subclass.
  • You DO NOT hide an instance method from a subclass, you override it.

Hiding doesn’t necessarily mean that you cannot access the parent’s declared members. It just means that if you do not use a qualifier or casting, the Java Virtual Machine will see only the subclass’s members and not the parent’s. In the case of instance variables, your object will maintain placeholders for both the parent’s variables and the child’s.

Lets have a closer look at inherited static variable usage in case of inheritance.

We have an Employee class, which represents an Employee of a company, its attributes set via constructor. We have Company class, which represents a Company, which has two static fields and two public methods. Then we have a Branch class, which extends Company class, and sets the branchName in constructor. The main class OnBoardBranch, creates instances of the other classes, and calls their respective methods. All looks good till now!

package maxCode.online.inheritance;

//Employee Class with type, name attributes
class Employee {
	private String type;
	private String name;

	Employee(String name, String type) {
		this.name = name;
		this.type = type;
	}
}

// Company has two static fields, and two methods which increment
// the fields. Leaving them public for demonstration purposes
class Company {
	public static int branchCount;
	public static int employeeCount;

	public void addEmployee(Employee e) {
		employeeCount++;
	}

	public void addBranch(Branch b) {
		branchCount++;
	}

	static {
		System.out.println("Company Static Initializer");
		branchCount = 10;
	}
}

// Company is a subclass of Branch
class Branch extends Company {
	private String branchName = "unspecified";
	public int branchCount;
	public int employeeCount;

	Branch(String branchName) {
		this.branchName = branchName;
	}

	{
		System.out.println("Branch Static Initializer");
		employeeCount = 200;
	}

	public void addEmployee(Employee e) {
		employeeCount++;
	}
}

public class OnBoardBranch {
	public static void main(String[] args) {
		// Create some objects
		Branch b = new Branch("RedBranch");
		Employee e1 = new Employee("Carol", "President");
		Employee e2 = new Employee("Ralph", "Vice President");
		Company main = new Company();

		// Execute the methods that should effect the static fields.
		main.addBranch(b);
		b.addEmployee(e1);
		b.addEmployee(e2);

		// The static variable defined on Company accessed here
		System.out.println("Number of Branches = " + Company.branchCount);
		System.out.println("Number of Employees = " + Company.employeeCount);

		// What does it mean to access the static variables from Branch?
		System.out.println("Call from Branch: Number of Branches = " + b.branchCount);
		System.out.println("Call from Branch: Number of Employees = " + b.employeeCount);
	}
}

Output

Company Static Initializer
Branch Static Initializer
Number of Branches = 11
Number of Employees = 0
Call from Branch: Number of Branches = 0
Call from Branch: Number of Employees = 202

Things to note in the above code

  • Since we have branchCount and EmployeeCount variables in both Company class and Branch class, the call from branch results in 0 number of branches and 202 employee count based on the static initializers.
  • Although all the functionality for accessing static fields is inherited by a class, the instance of a class doesn’t actually get its own copy (counters here) of the static field variables.
  • We can use the same attribute name type as the parent’s static attribute.
  • What you can’t do is have a method on a child class that overrides a parent’s static method, unless the child’s method is also static.

Thats all for Superclass and Subclass concepts, we will look at abstract classes in the next section.