Lesson Wednesday

Generally speaking, there are two primary ways we use polymorphism when inheriting classes into one another in Java:

  1. We can inherit one complete class into another complete class. This is the case with Java's Date class being inherited into the Timestamp class.

  2. We can define an abstract class to inherit into one or more complete classes. An abstract class is simply a superclass that cannot instantiate its own objects. It has methods like a class, it has attributes like a class, but it doesn't have a constructor and cannot make objects. It simply holds code other classes may inherit.

Let's walk through when to declare and inherit an abstract class versus when we would inherit another complete class.

When to Inherit Complete Classes

Sometimes you'll want to have two classes that are similar, but one is more specialized. They require similar functionality, but one is more specific, or requires additional features and capabilities.

Let's walk through an example case. Pretend we're creating a message board/forum.

  • Our forum will likely have both normal Users, and Admins with additional power to edit/delete/update posts and/or accounts.

  • Both Admin and User would require a lot of the same functionality, though: They both need attributes for a username and email, among other things. They also both need methods to update their own information, and create new posts. Even though Admins need extra capabilities too, these basic functionalities are the same for all types of accounts on our forum.

  • However, Admin needs extra functionality that Users don't have: They must be able to do things like delete posts, or ban users.

  • To help keep our classes DRY, the Admin class could inherit the User class to gain all the basic user functionality, then also define its own custom functionality specific to Admin, like this:

message-board/src/main/java/User.java
public class User {
  public String username;
  public String email;
  public Image avatar;

  public User(String username, String email, Image avatar){
    this.username = username;
    this.email = email;
    this.avatar = avatar;
  }

  public void postNewThread() {
    // code for posting new conversation thread
  }

  public void updateUserAvatar() {
    // code for updating avatar
  }
 ...
}
message-board/src/main/java/Admin.java
public class Admin extends User {

  public Admin(String username, String email, Image avatar){
    this.username = username;
    this.email = email;
    this.avatar = avatar;
  }

  public void deleteConversationThread() {
    // code for removing a conversation thread
  }

  public void banUser() {
    // code for banning a naughty user
  }
  ...
}
  • Now, each has its own constructor, and each can create its own objects. However, because the Admin class contains the magic words extends User it also has access to every public attribute and method in the User class.

  • Notice we reference username, email, and avatar fields in the Admin constructor. Yet, they're not declared at the top of the Admin class. This is because it's inheriting these fields from User.

  • We can also automatically call any public method from User on Admin. We could call someAdmin.updateAvatar() anywhere in our application even though we didn't define it in the Admin class, because we inherited it from User.

  • Remember, because we want to be able to instantiate both Admin and User objects, we do not use an abstract class. You cannot instantiate objects from an abstract class. They just hold code.

Generally, one rule of thumb is that if we can say "X is a Y", we can likely inherit from another complete class. In this case, we could say "An Admin is a User" because they are a user; just a more specialized user with extra capabilities.

When to Inherit Abstract Classes

Alternatively, when should you use an abstract class? Well, generally speaking, when you have multiple classes that share the same general attributes and/or methods, but none are necessarily more specific than one another.

For instance, pretend we are making an application to keep track of a small camping store's inventory. This store only sells two types of items: Tents and SleepingBags. Their classes could look like this:

camping-supply/src/main/java/SleepingBag.java
public class SleepingBag {
  private String color;
  private String brand;
  private String model;
  private float price;
  private int temperatureRating;

  public SleepingBag(String color, String brand, String model, float price, int temperatureRating){
    this.color = color;
    this.brand = brand;
    this.model = model;
    this.price = price;
    this.temperatureRating = temperatureRating;
  }

  public onSale(){
    // code to add sleeping bag to weekly list of sale items.
  }

}
camping-supply/src/main/java/Tent.java
public class Tent {
  private String color;
  private String brand;
  private String model;
  private float price;
  private int capacity;

  public Tent(String color, String brand, String model, float price, int capacity){
    this.color = color;
    this.brand = brand;
    this.model = model;
    this.price = price;
    this.capacity = capacity;
  }

  public onSale(){
    // code to add tent to weekly list of sale items.
  }

}

Now, notice how a lot of the content in both classes is similar. Yet, it doesn't make sense for a Tent to inherit from a SleepingBag, or vis versa. A tent is not a sleeping bag, and a sleeping bag is not a tent.

However, we can house the shared attributes and methods in an abstract class, like this:

camping-supply/src/main/java/CampingGear.java
public abstract class CampingGear {
  public String color;
  public String brand;
  public String model;
  public float price;
}

  public onSale(){
    // code to add any gear to weekly list of sale items.
  }
}

Then, both the Tent and SleepingBag classes could each inherit this abstract class to gain the attributes and methods they share. Yet, they can still define their own attributes and methods, too:

camping-supply/src/main/java/Tent.java
public class Tent extends CampingGear {
  private int capacity;

  public Tent(String color, String brand, String model, float price, int capacity){
    this.color = color;
    this.brand = brand;
    this.model = model;
    this.price = price;
    this.capacity = capacity;
  }
}
camping-supply/src/main/java/SleepingBag.java
public class SleepingBag extends CampingGear {
  private int temperatureRating;

  public SleepingBag(String color, String brand, String model, float price, int temperatureRating){
    this.color = color;
    this.brand = brand;
    this.model = model;
    this.price = price;
    this.temperatureRating = temperatureRating;
  }
}
  • Both Tent and SleepingBag objects still both have properties for color, brand, model, and price. Additionally, they both have access to the onSale() method, too. They receive all of this because each class includes the magic words extends CampingGear.

  • Each still has its own constructor, and each can create its own objects.

  • Notice we reference color, brand, price and model fields in the both constructors. Yet, they're not declared at the top of the SleepingBag or Tent classes. This is because it's inheriting these fields from CampingGear.

  • We can also automatically call any public method from CampingGear on either SleepingBag or Tent, because we inherited it into each class.

  • CampingGear is declared abstract because our store only sells Tents and SleepingBags. We have no reason to create CampingGear objects, so we declare it as an abstract class. It does not instantiate objects, it simply houses code other classes may extend. This allows us to DRY up our Tent and SleepingBag classes.

Are you beginning to see how object inheritance and polymorphism can keep our classes neat and tidy? In the next lesson, we'll declare an abstract class in our virtual pets application, and inherit it in multiple other classes.

Terminology


  • Abstract Class: A superclass that cannot instantiate its own objects. It has methods like a class, it has attributes like a class, but it doesn't have a constructor and cannot make objects. It simply holds code other classes may inherit.

Overview


  • Abstract classes are best used when you have multiple classes that share the same general attributes and/or methods, but none are necessarily more specific than one another.