CMUQ 15-121 Abstract Classes and Interfaces



1. Introduction

We’ve previously looked at inheritance, a method where-by one class can inherit instance variables and methods from another. Now we’ll take a look at two special types of classes specifically designed to be inherited from.

2. Abstract Classes

An abstract class is a class in Java that contains instance variables and methods as usual, but can never be instantiated. (This means you can never use the new keyword to create one.) Here is an example of an abstract class:

public abstract class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

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

    public abstract void printMe();

}

There are a few important things to notice here:

  1. The abstract keyword in the class declaration. This declares the class to be abstract.
  2. The abstract method printMe() that is included. This method is declared, but has no implementation.

So, what is the usefulness of a class that can’t be instantiated and has methods that are declared but not implemented? The answer is inheritance. Abstract classes are designed to serve as parent classes to other classes. For example, I might declare a Student class that inherits from the Person class:

public class Student extends Person {
    private String andrewId;

    public Student(String name, String andrewId) {
        super(name);
        this.andrewId = andrewId;
    }

    public void printMe() {
        System.out.println("Student");
        System.out.println("----------");
        System.out.println("Name: " + this.getName());
        System.out.println("andrew ID: " + this.andrewId);
    }
}

And another one called Contractor:

public class Contractor extends Person {
    private String company;

    public Contractor(String name, String company) {
        super(name);
        this.company = company;
    }

    public void printMe() {
        System.out.println("Contractor");
        System.out.println("----------");
        System.out.println("Name: " + this.getName());
        System.out.println("Company: " + this.company);
    }
}

Notice how both classes inherit from Person, and both have a printMe method. In fact, they must implement printMe since it was declared abstract in the abstract, parent Person class. This also means that I can store a reference to either one in a Person variable and use it:

ArrayList<Person> theList = new ArrayList<Person>();
theList.add(new Student("Ahmed Jassim", "ahmedj"));
theList.add(new Contractor("MD Shafi", "Qatar Cleaning Company"));
for(Person p: theList) {
    p.printMe();
    System.out.println("");
}

3. Interfaces

An interface is a special type of abstract class. There are three important differences between interfaces and abstract classes, however:

  1. An interface contains only abstract method declarations. No instance variables.
  2. Objects don’t inherit from interfaces, instead they implement them.
  3. An object can implement multiple interfaces. (But it can’t inherit from multiple classes.)

So what’s the point of an interface? It’s a nice way to specify a set of methods an object should implement if it wants to be compatible with something.

3.1. Example: Comparable

Consider the following definition of a simple Pet class:

public class Pet {
    private String name;
    private String owner;

    public Pet(String name, String owner) {
        this.name = name;
        this.owner = owner;
    }

    public String toString() {
        return this.name + " (Owned by: " + this.owner + ")";
    }
}

You decide to create an ArrayList of pets:

ArrayList<Pet> theList = new ArrayList<Pet>();

theList.add(new Pet("Mittens", "Shaikha"));
theList.add(new Pet("Destroyer", "John"));
theList.add(new Pet("Zebbie", "Shaikha"));
theList.add(new Pet("Destroyer", "Adam"));

Then, you want to sort them by name. After some Googling, you find mention of Collections.sort, a nice library method you can use to sort an ArrayList. So you call it:

ArrayList<Pet> theList = new ArrayList<Pet>();

theList.add(new Pet("Mittens", "Shaikha"));
theList.add(new Pet("Destroyer", "John"));
theList.add(new Pet("Zebbie", "Shaikha"));
theList.add(new Pet("Destroyer", "Adam"));

Collections.sort(theList);

However, compiling gets you this error:
The method sort(List<T>) in the type Collections is not applicable for the arguments (ArrayList<Pet>)

You do some more Googling and finally discover that in order to sort an ArrayList, the items inside the list must implement the Comparable interface. The reason for this is that, by default, Java doesn’t know how to compare Pet objects and determine which one is greater or less than another.

You read more about Comparable and realize that the interface only has one method you need to implement, so you end up with the following:

public class Pet implements Comparable<Pet> {
    private String name;
    private String owner;

    public Pet(String name, String owner) {
        this.name = name;
        this.owner = owner;
    }

    public String toString() {
        return this.name + " (Owned by: " + this.owner + ")";
    }

    /* 
     * Compares this object with the specified object for order. Returns a negative integer, zero,
     * or a positive integer as this object is less than, equal to, or greater than the specified
     * object.
     * 
     * In short, compare this with p and return an integer indicating who is larger.
     */
    public int compareTo(Pet p) {
        if (this.name.compareTo(p.name) < 0) {
            return -1;
        }
        else if (this.name.compareTo(p.name) == 0 ) {
            return 0;
        }
        else {
            return 1;
        }
    }
}

Now, when you run the following code:

ArrayList<Pet> theList = new ArrayList<Pet>();

theList.add(new Pet("Mittens", "Shaikha"));
theList.add(new Pet("Destroyer", "John"));
theList.add(new Pet("Zebbie", "Shaikha"));
theList.add(new Pet("Destroyer", "Adam"));

Collections.sort(theList);

for(Pet p: theList) {
    System.out.println(p);;
}

You get the following output:

Destroyer (Owned by: John)
Destroyer (Owned by: Adam)
Mittens (Owned by: Shaikha)
Zebbie (Owned by: Shaikha)

This is great! Except for one small problem: Two pets have the same name, and when that happens you would like to sort by owner name to break the tie. Here’s an updated compareTo to handle that:

public int compareTo(Pet p) {
    if (this.name.compareTo(p.name) < 0) {
        return -1;
    }
    else if (this.name.compareTo(p.name) == 0 ) {
        // We can call and return the result of the String compareTo directly in this case.
        return this.owner.compareTo(p.owner);
    }
    else {
        return 1;
    }
}

And now our code outputs what we want:

Destroyer (Owned by: Adam)
Destroyer (Owned by: John)
Mittens (Owned by: Shaikha)
Zebbie (Owned by: Shaikha)

3.2. More Complex Situations

We’ll do a more complex interface scenario in class, so be sure to check the demo code page for the sample code.