1. Common base class

Today we'll enjoy a smorgasbord of interesting topics. Remember when we introduced the ChessItem base class to simplify all of the classes representing chess pieces? I hope so 🙂

Now imagine that each piece has a draw() method that handles drawing it on the screen. You call the draw() method, and the piece draws itself at its current coordinates. It would be convenient to move this method to the base class.

If the draw() method was in the ChessItem base class, then we could override it in the piece classes and write elegant code like this:

class ChessBoard
{
   public void drawAllChessItems()
   {
      // Add the pieces to the list
      ArrayList<ChessItem> items = new ArrayList<ChessItem>();
      items.add(new King());
      items.add(new Queen());
      items.add(new Bishop());

      // Draw them regardless of their type
      for(ChessItem item: items)
      {
         item.draw();
      }
   }
}

By introducing the ChessItem base class, we were able to greatly simplify the code: there is no need to call the methods of each class separately, we can easily store all the objects in a single collection, etc.

But here's an interesting question: what should the draw() method declared directly in the ChessItem class draw on the screen? After all, there is no such piece in chess, so there is nothing to draw.

That's exactly right. What's more, it makes no sense to create ChessItem objects directly. It is not a chess piece, but rather just an abstraction — a class that we created for our convenience. This is how abstraction works in OOP: we move important data and methods (those shared by all pieces) to a base class, and keep their differences in separate descendant classes.


2. Abstract classes

Abstract classes

For such situations, Java has a special kind of class: the abstract class. They are designed to make it easier for programmers to work with similar classes and reduce the amount of duplicated code in them.

Here are three things to know about abstract classes.

Method with no implementation

An abstract class can have a method declaration without an implementation. This is exactly what makes the method abstract. The method body is simply replaced with a semicolon. And before the name of the method, we write the abstract keyword. Example:

public abstract class ChessItem
{
   public int x, y; // Coordinates
   private int value; // The piece's value
   public int getValue() // Ordinary method that returns value field
   {
      return value;
   }

   public abstract void draw(); // Abstract method. The implementation is missing.
}

Abstract class

Each method without an implementation is marked with the abstract keyword. If a class has even one abstract method, the class is also marked with the abstract keyword.

Prohibiting the creation of objects

You cannot created objects of an abstract class. Such code simply won't compile.

Code Description
ChessItem item = new ChessItem();
item.draw();
This code does not compile:
ChessItem item = new Queen();
item.draw();
But you can do this

Inheriting an abstract class

If your class inherits an abstract class, then you need to override all the inherited abstract methods, i.e. you need to write an implementation for them. Otherwise, your class itself will also have to be declared abstract.

If a class has even one unimplemented method declared directly in it or inherited from a parent class, then the class is considered abstract.

And why is all this necessary? Why are abstract classes needed? Isn't it possible to use ordinary ones instead? And instead of abstract methods, couldn't we just write two empty curly brackets as the method body?

We could. But these restrictions are akin to the private modifier. We use the private keyword to deliberately prevent other programmers from directly accessing data and to force them to use only our public methods when writing their classes.

It's the same with abstract classes. The author of an abstract class does not want objects of the class to be created. Instead, the author expects abstract methods to be inherited from the abstract class and then overridden.

The advantage of this approach is readily apparent in large projects. The more classes you have, the more clearly you need to delineate their roles. You will see the benefit of this approach in the near future. Every thing goes through this.