In the Class Inheritance chapter, we learned about forming a contract between classes, superclasses, and users. By creating subclasses that share a superclass, we can share fields and methods between them to decrease the amount of code we write, create a predictable interface for users, and employ polymorphism. In this chapter, we will learn how to make our contracts better through abstract classes and interfaces.

Why Abstract Classes & Interface are Important & Useful

A regular class defines the fields, implements fully functioning methods and can be instantiated; such a class is referred to as a concrete class. That is the vast majority of classes you will write and use in your programming career. Abstract classes and interfaces cannot be instantiated, but they, too, define fields and methods — although they may not implement methods. They are best for forming a contract between a class, its subclasses, and users of its subclasses.

Abstract Class

An abstract class is much like a regular class in that it can have fields and methods. What makes it special is its methods may or may not have bodies. That means an abstract class can have method signatures that are declared as abstract as well as full methods as usual.

An abstract method is a method that has no body and requires subclasses of the abstract class its in to implement its body. This is useful when you have a method that you want all subclasses to have but you want to leave it up to the different subclasses to implement the method how they want. Further, a subclass of an abstract class must implement all of its abstract methods.

Take the Human class’s attack() instance method from the Class Inheritance chapter for example. We want every subclass of the Human class to have a method of attacking another Human opponent, but we expect each subclass to have a different way of attacking. We should make our attack() method abstract instead of implementing it in the Human class as well as every subclass. That way, we won’t be able to forget to implement it in every subclass and we will keep our code clean by not writing expectantly useless code in our superclass. There are a couple methods of the Human class that we should make abstract, but in order to do that, we must make the Human class an abstract class. To do that, we simply add the abstract keyword to the class’s signature before the class keyword.

public abstract class Human {
//add fields, methods, and abstract methods
}

For methods that we expect to behave exactly the same across all our subclasses, we can write them out in full as usual. Methods such as getters and setters fit this bill. Further, we can still have our shared fields in our abstract class. Let’s take a look at our new Human class as an upgrade from the Class Inheritance chapter:

public abstract class Human {
protected String name;
...

public Human(String name) {...}

public abstract void attack(Human human);
public abstract void defend();
public abstract void jump();
public abstract double heal();

public String getName() {...}
public double getHealth(){...}
public long getExperience(){...}
public void setAttackPower(int attackPower) {...}
public long gainExperience(long experience){...}
public double heal(double additionalHealth) {...}
public double decreaseHealth(int opponentAttackPower) {...}
}

In that code snippet, you can see that we have our Human class and its attack(), defend(), jump(), and heal() instance methods are abstract. This is because they are all functions that should be unique to each class that implements them; Taoist instances — magical characters — won’t heal the same way as Warrior instances, so they should implement the heal() method differently. You’ve seen the rest of the class before, so those parts have been omitted.

At this point, it is important to note that static methods cannot be abstract because in simplest terms, abstract means that a method implements no functionality and static means there is definitely functionality available in the method or field even without an instance of the class. Thus, having a static abstract method would be would be a logical contradiction which Java does not allow.

Interfaces

An interface is a bit different than anything we’ve seen thus far. A Java interface is more like an abstract class than a regular class. An interface can only contain method signatures and static final fields. An interface is merely a contract between the interface and classes that implement it. Like with abstract classes, classes that implement an interface must implement its methods’ bodies to provide functionality.

Interfaces are best for creating a contract that will ensure that all classes that implement it behave similarly by abiding by the contract. This will allow users to safely expect parts of your program to behave similarly much like you would expect all characters in a fighting game to behave similarly. In our Conquer game from the Class Inheritance chapter, we expect all characters to be able to attack other characters, defend themselves from attacks, jump, and heal themselves among other functions. These functions are functions all characters can perform, not only Human instances:

public interface Character {
Random randomGenerator = new Random();

String getName();
double getHealth();
long getExperience();
int getAttackPower();
void setAttackPower(int attackPower);

void defend();
void jump();
int heal();
void attack(Character opponent);
double decreaseHealth(int opponentAttackPower);
long gainExperience(long experience);
}

In the Character interface, numCharactersInGame field is implicitly static and all of the methods are implicitly public, so there’s no need to include the static and public modifiers respectively.

A top-level interface cannot be private. Also, all methods of an interface are intuitively public, so don’t include any methods you think should be private or protected.

Say we want to allow our Human characters to have Pet characters as their companions, we would expect that Pet instances can do things like attack, defend, jump, and heal but not other things that are unique to Human instances. Further, we may have many different types of pets; a user may want to have a dog, cat, or a bird as a pet. For that, we would have a Dog class, Cat class, and a Bird class. Each of those classes would, in turn, extend the Pet class which will define shared instance fields, implement shared methods, and declare the abstract methods that all subclasses must implement.

From bottom to top, the hierarchy of concrete classes, abstract classes, and interface goes like so:
concrete class → abstract class → interface.
If a class extends a superclass — concrete or abstract — that implements an interface, you can declare the necessary methods from the interface in the superclass. Doing this will fulfill the subclass’s duty to abide by the contract between it and the interface because your subclass will inherit the necessary methods of the contract from its superclass. Alternatively, the class can implement the interface methods on its own.

Let’s take a look at our abstract Pet class and our concrete Archer and Dog subclasses; we will make use of the Character interface and abstract Human class from earlier:

public abstract class Human implements Character{
protected Pet pet;
...

public Human(String name) {
...
numCharacters++;
}
    public void setPet(Pet pet) { this.pet = pet; } 
public Pet getPet() { return pet; }
public abstract void attack(Character opponent);
    ...
}
public abstract class Pet implements Character{
protected Human owner;
...
    public Pet(String name, Human owner) {
this.name = name;
this.owner = owner;
gainExperience(1);
numCharacters++;
}

public Human getOwner() { return owner; }
    public abstract void attack(Character opponent);
...
}
public class Archer extends Human {
private int numArrows = 0;

public Archer(String name) {
super(name);
findArrows();
}

private void findArrows() {
System.out.println("Looking for arrows");
}
    @Override
public void attack(Character opponent) {...}
    @Override
public void defend() {...}
    @Override
public void jump() {...}
    @Override
public int heal() { return 0; }
}
public class Dog extends Pet {
public Dog(String name, Human owner) {
super(name, owner);
}

public void bark() {
System.out.println("Wolf Wolf!");
}

@Override
public void attack(Character opponent) {...}
    @Override
public void defend() {...}
    @Override
public void jump() {...}
    @Override
public int heal() { return 0; }
}

Now we have a wonderful contract set up between subclasses, abstract superclasses, and an interface to rule them all. The Character interface defines the behavioral interface for all characters in the game. The Human abstract class implements the Character interface and defines behavior and fields for all human characters in the game. The Human class also implements some of the methods of the Character interface that are common for all Human characters. The Archer class is a concrete class that extends the Human class and is used to create archer characters in the game. Furthermore, the Archer class also implements the Character interface because its superclass, Human, does. Therefore, Archer instances are also Human instances and since Human instances are also instances of the Character interface, Archer instances are Character instances. Thus, Archer instances are archer and human characters. That gives us the following hierarchy, from bottom to top:

Archer instance Archer concrete class Human abstract class Character interface.

The Dog concrete class and the Pet abstract class work similarly with our Character interface:

Dog instance Dog concrete class Pet abstract class Character interface.

An Archer is a Human is a Character. A Dog is a Pet is a Character.

An Archer is a Human is a Character. A Dog is a Pet is a Character. Therefore, both an Archer and a Dog are equally Character instances like all other Human and Pet instances would be if you create more classes that extend those abstract classes or implement the Character interface.

You should be proud of yourself at this point. You now know everything you need to know about classes, interfaces, and contracts to get started building fun projects that employ polymorphism. Here’s a starter program:

public class Main {
public static void main(String[] args){
Archer niceArcher = new Archer("Tom");
Human modernArcher = new Archer("Stacy");

Dog modernDog = new Dog("Hunter", modernArcher);
Pet petDog = new Dog("Buddy", niceArcher);

System.out.println("\nStart Game\n");
modernArcher.attack(niceArcher);
niceArcher.getPet().attack(modernArcher);
modernArcher.attack(niceArcher.getPet());
modernDog.attack(petDog.getOwner());
petDog.attack(modernArcher.getPet());
}
/*Prints:
Tom: Found 6 arrows ...
Stacy: Found 4 arrows ...
Stacy: I have a new pet. Hi Hunter!
Hunter: Wolf Wolf!
Tom: I have a new pet. Hi Buddy!
Buddy: Wolf Wolf!

Start Game

Stacy: Attacking Tom with my arrows!
Tom: I've been hit. My health now = 90.0
Buddy: Biting Stacy
Stacy: I've been hit. My health now = 99.0
Stacy: Attacking Buddy with my arrows!
Buddy: Wolf Wolf!
Buddy: Health now = 90.0
Hunter: Biting Tom
Tom: I've been hit. My health now = 89.0
Buddy: Biting Hunter
Hunter: Wolf Wolf!
Hunter: Health now = 99.0
*/
}

Polymorphism is the ability for objects to take on many forms. Notice that the niceArcher is of type Human but is instantiated as a new Archer. That means that the niceArcher passes more than one is-a test because its both a Human and an Archer. Furthermore, notice that each object can attack all other objects. This is only possible because each object is also an instance of the Character interface, and the attack() instance method of Character takes another a Character instance as the single argument. With all of that, we can say that these objects are polymorphic.

Remember that if you don’t quite understand the importance of polymorphism yet, we will cover it in more depth soon. For now, take a look at the supporting code and make sure you understand it. Try to change it around and make your own contracts.