Chapter 5 - Java for Beginners Course

Abstract Classes and Methods

Java allows us to define classes that cannot be instantiated and that may have methods defined without any implementation. These are called abstract classes and are used as parent classes in hierarchies of related classes that have shared state/behavior, but where specific implementation details are added in the subclasses.

The abstract keyword allows us to define both abstract classes and methods, for example:

public abstract class AbstractClassName {
    //...
    abstract void exampleAbstractMethod(int parameter);
    //...
}

Details about abstract classes and methods

Abstract classes can’t be instantiated, this is, we can’t do new AbstractClassName() for example. The opposite of an abstract class is called a concrete class which are the type of classes we’ve seen in all our previous sections.

Abstract methods don’t have a body and can only be defined inside an abstract class. These methods just define a signature of a method that should be implemented in concrete subclasses.

Abstract methods are a type of contract between a superclass and its subclasses where the superclass defines methods that should exist in the subclasses but doesn’t know the implementation details. An abstract class doesn’t need to define abstract methods.

In every other respect, an abstract class behaves in the same way as a concrete class.

An example using Numeral Systems

To clarify the concept, let’s take a look at an example. In this scenario let’s assume we are building an application that allows us to convert and print out a number into different numeral systems. For example, if we provide number 4 to a binary number system, we should get "100" as the output, or if we provide 10 to a Roman number system we should get "X".

Based on our requirement above, our numeral system should have 2 behaviors, one to print and another to convert a number, so we could think of defining a class in the following lines:

public class NumeralSystem {
    public String convert(int number) {
        String result = "";
        // logic to convert the number goes here...
        return result;
    }

    public void print(int number) {
        String convertedNumber = convert(number);
        System.out.println(convertedNumber);
    }
}

Now, the print method on the class above is pretty well defined and we can say it works for most of our numeral systems, as in, it converts the number using the convert method and then prints out the result. However, you can see that the convert method is still undefined, we don’t know what it needs to do as it really depends on the number system we’re implementing.

As some of the state or behavior above can be shared across multiple similar classes, and it doesn’t make sense for users to create objects of NumeralSystem, we could think about converting our existent class into abstract. Also, as the convert method really depends on the numeral system, we should also convert it into an abstract method to force concrete classes to implement it:

public abstract class NumeralSystem {

    public abstract String convert(int number);

    public void print(int number) {
        String convertedNumber = convert(number);
        System.out.println(convertedNumber);
    }

}

Now, let’s define an initial subclass of NumeralSystem to represent a binary numeral system:

public class BinaryNumeralSystem extends NumeralSystem {

}

In its current form, the Java compiler will throw an error stating that our BinaryNumeralSystem must implement the abstract convert method inherited from NumeralSystem. This is because our BinaryNumeralSystem is a concrete class (it doesn’t have the abstract keyword) and concrete classes cannot have abstract methods, so we need to provide an implementation.

So, let’s do just that:

public class BinaryNumeralSystem extends NumeralSystem {
    @Override
    public String convert(int number) {
        return Integer.toBinaryString(number);
    }
}
Integer.toBinaryString is one of multiple utility methods provided in Java to do common operations on numbers. This is an static method, a topic we’ll cover in this chapter.

Once we provide the implementation of the convert method, the compilation error is gone.

Note that the Java compiler didn’t generate an error with regards to the print method at any point as it is a concrete method (it has an implementation) so our concrete class doesn’t need to do anything with it. We can of course override it if required.

For the purpose of the example, let’s define another numeral system:

public class HexadecimalNumeralSystem extends NumeralSystem {
    @Override
    public String convert(int number) {
        return Integer.toHexString(number);
    }
}

We’ve now implemented two separate NumeralSystem subclasses that share a common behavior (print) and have a different way of handling the convert method.

Let’s give this a try:

In the example code run the JavaAbstractClassApp
BinaryNumeralSystem binary = new BinaryNumeralSystem();
HexadecimalNumeralSystem hexa = new HexadecimalNumeralSystem();

int number = 15;

System.out.println(number + " in binary is represented as: ");
binary.print(number);

System.out.println(number + " in hexadecimal is represented as: ");
hexa.print(number);

Output:

15 in binary is represented as:
1111
15 in hexadecimal is represented as:
f