Lincoln

@LincolnWDaniel

Java for Humans {Class Inheritance}

Inheritance is one of the most powerful concepts in all of programming. Without it, it’d be tough to do many of the cool things we do. One of my favorite games as a kid was Conquer 2.0. The game was fun because you could be anything, and at the same time, each thing was like the others. Yes different and the same at the same time. Isn’t that wonderful? Let me explain.

In Conquer, you can be an Archer, Monk, Ninja, Warrior, or one of two types of a Taoist. The great thing about the game is that no one class is much different from another and if you want to be another class, it isn’t too hard to understand how it works. This is possible because every class is built upon a single class, and, consequently, they all share similar attributes and functionalities. There is no Human class in Conquer, but it is clear that all of the classes mentioned above are humans with distinctive super powers.

An Archer can fly and shoot fire arrows, a Ninja can run fast and attack with stars, a Warrior can wield a sword and a sword, and a Taoist can perform magical acts and heal others. While those classes appear different, they are more alike than you may think. Every class extends the capability of a Human. A Human can jump, attack, and heal itself. Further, every human has a name, health, attack power, and experience. All of that adds up to two attributes and five functions. That’s essentially all it takes to create a game like Conquer. Where it gets fun is when we create the different classes with unique attributes and functionalities while still inheriting all the attributes and functionalities universal to all humans from the Human class. This is where subclassing and inheritance come in.

Why Inheritance is Important & Useful

By default, all classes in Java inherit from the Object class provided by Java. Therefore, the Object class is the superclass to all other classes and it defines methods that all its subclasses share. While the Object class only defines the methods its subclasses share, superclasses can define fields their subclasses share as well. This is important because it allows us to have the vital is-a relationship between objects. Even more important is the ability to write parts of our class definition once and share it with other classes that are similar in some ways. This is great for keeping our code DRY (Don’t Repeat Yourself) and encapsulating shared functionalities of our classes in their superclasses.

In our Conquer game, if we know that all classes can jump, attack, and heal as well as have a name, health, attack power, and experience, we can put all of that in a superclass called Human. With a Human superclass, we will only need to add the unique parts of each subclass to the class itself. This will allow us to write less code as well as build a predictable interface for users of our game.

Subclassing & Inheritance

To have a superclass is to extend, or inherit from, a class. Thus, to be a subclass is to extend a class. Every class in Java can have only one direct superclass from which it inherits some of its fields and methods. However, a class that has a superclass can also be inherited from as to create a chain of inheritance; this is known as class hierarchy.

From its superclass, a class inherits all fields and methods that are not declared as private. This creates a contract between the subclass, its superclass, and the user of the classes. With the contract, the user can expect the behavior of the superclass to be present in its subclasses. However, the contract only goes one way. Fields and methods declared in a subclass are not available in the superclass unless they are declared there, too.

Extending a class is easy in Java. All you have to do is choose a class to extend and write the keyword “extends” followed by the name of the class to extend at the end of your class declaration but before the opening closing brace:

public class Human {
//add fields and methods
}

public class Archer extends Human {
//inherits fields and methods of its Human superclass
//add more fields and methods for this class
}

In that code snippet, we have a Human class which, in theory, has fields and methods. We also have a Archer class which, also in theory, has its own fields and methods and extends our Human class to inherit its fields and methods. Let’s add some fields and methods to our Human class to see how the Archer can inherit them:

public class Human {
protected String name;
protected double health = 100.0;
protected long experience = 0;
protected int attackPower = 1;

public Human(String name) {
this.name = name;
gainExperience(1);
}

public long gainExperience(long experience){
this.experience += experience;
return experience;
}

public long addAttackPower(long attackPower) {
this.attackPower += attackPower;
return attackPower;
}

public double heal(double additionalHealth) {
health += additionalHealth;
return health;
}

public void attack(Human opponent) {
opponent.decreaseHealth(attackPower);
}

private void decreaseHealth(int attackPower) {
health -= attackPower;
}

public void jump() {
System.out.println("Jumped up");
}
    ...
}

Now, instances of every subclass of our Human class will be able to attack other Human instances, be attacked, heal, jump, gain experience, and increase their attack power. Notice that I said that instances of every subclass of our human class will be able to attack other Human instances. I say this because every instance of a class is also an instance of the class’s superclass.

The attack() instance method of our Human class takes a Human opponent as a parameter and drains its health. This is great because we only need to write one attack method that will work on all instances of our Human class regardless of the instance being an Archer, Monk, Wizard, or any other class that extends our Human class. This is polymorphism at work and we will learn more about it later. Simply put, polymorphism is the idea that we can group many different classes together as being one class, such as saying all archers, monks, and wizards are also humans. They all pass more than one is-a test. Without inheritance, we would have to write a different attack method for each class we may come in contact with. That would be awful.

Method Overriding

All classes that extend our Human class will share the same attack method. That means that every subclass of our Human class will attack the same way as the other subclasses of our Human class. This wouldn’t be such a good design because a user is expecting a Warrior to attack with a sword and an Archer to attack with a bow and arrow. Method overriding in Java allows us to accomplish such a design.

A subclass can override the methods of its superclass to implement its own behavior if it is necessary. Method overriding allows a subclass to decide how to implement behavior expected by the user through the inheritance contract. In our game, a user will expect that all classes can attack other classes, but they also expect that each class attacks in a unique way. Therefore, each subclass of our Human class should override the attack() instance method to implement its own attack behavior. This is easy in Java. All we have to do to override a method from a superclass is to add the @Override annotation above the method in the subclass:

public class FireArcher extends Archer {
int firePower = 1;
...

@Override
public void attack(Human opponent) {
System.out.println("Attacking " + opponent.getName() + " with fire arrows!");
opponent.decreaseHealth(attackPower + firePower);
}
}
public class Warrior extends Human {
...
    @Override
public void attack(Human opponent) {
System.out.println("Attacking " + opponent.getName() + " with my sword!");
super.attack(opponent);
}
}
public class Monk extends Human {
...
}
Notice that the FireArcher class extends the Archer class which extends the Human class. This is an example of a class hierarchy from bottom to top:
FireArcher Archer Human.

In that code snippet, we have a FireArcher, Warrior, and Monk class. All three classes extend the Human class and inherit its fields and methods, but only the FireArcher and the Warrior class override the attack() instance method of the Human class. The Monk class doesn’t do anything special when it attacks, so it is fine with the basic Human attack() method.

When we override methods of the superclass, we can choose to invoke the superclass’s implementation of that method by accessing the super class through the super keyword provided by Java and calling the method on it at any point in the method. We do this at the end of the overridden attack() method in the Warrior class but not in the FireArcher class because a FireArcher deals damage to opponents differently than most other classes; with its fire, a FireArcher deals additional damage to an opponent’s health.

Finally, because an instance of a class is also an instance of its superclass, a subclass must implement at least one of the constructor methods that are declared in its superclass and/or invoke the implementation of one of its superclass’s constructors by accessing the super class through the super keyword:

public class Archer extends Human {
private int numArrows = 0;
    //1. this constructor works    
public Archer(String name) {
super(name);
findArrows();
}
    //2. this constructor works    
public Archer(String name, int startingArrows) {
this.name = name;
numArrows = startingArrows;
}
    private void findArrows() {
System.out.println("Looking for arrows");
}
}

We must call the superclass’s constructor at the start in of the subclass’s constructor in at least one of the subclass’s constructors. In the second constructor, we don’t have to call the superclass’s constructor because it is called in the first constructor. There are many other rules surrounding the constructors of a superclass and its subclasses, but this is enough to get you started. Try to see how you can break the contract and you will quickly learn the other rules. Finally, notice that we accessed and set the name instance field in the Archer class’s second constructor although it is not declared directly in the Archer class. This is only possible because that field, as we learned earlier, is inherited from the Human superclass.

We can now create instances of all our classes — Human, Archer, Warrior, and FireArcher — and expect them to share methods and fields while behaving distinctively:

public class Main {
public static void main(String[] args){
Human human = new Human("Lisa");
Human archer = new Archer("Samuel");
FireArcher fireArcher = new FireArcher("Emmanuel");
Warrior warrior = new Warrior("Carolyn");

human.attack(fireArcher);
archer.attack(warrior);
warrior.attack(archer);
fireArcher.attack(human);
}
}

Abstract Class & Interfaces

We have learned a good deal about class inheritance, but there’s a little more to learn before we are masters of inheritance and polymorphism. Soon, we will learn how to make better class contracts with abstract classes and interface classes.

More by Lincoln

Topics of interest

More Related Stories