Introduction
According to some surveys such as that of JetBrains, version 8 of Java is currently the most used by developers all over the world, despite being a 2014 release. What you are reading is the first in a series of articles titled “Going beyond Java 8”, inspired by the contents of my book “Java for Aliens”. These articles will guide the reader step by step to explore the most important features introduced starting from version 9. The aim is to make the reader aware of how important it is to move forward from Java 8, explaining the enormous advantages that the latest versions of the language offer.
In this article we will talk about one of the typical dilemmas that arise when learning Java, as well as one of the most frequently asked questions in job interviews: what are the differences between an abstract class and an interface. Since several definitions have changed since version 8 of Java, this article will try to highlight all the differences to eliminate any doubts.
Abstract Class Definition
An abstract class is a class that differs from an ordinary class because:
- It is declared with the
abstractmodifier - It cannot be instantiated
- Can declare abstract methods (i.e., methods declared with the
abstractmodifier)
Let’s make it clear that nothing prevents an abstract class from implementing all its methods. It is not true that an abstract class must necessarily have at least one abstract method. The reverse is true: if a class contains an abstract method then it must be declared abstract.
For the rest, it is a class like any other and can declare constructors, concrete methods, variables, constants, static members, nested types, can implement interfaces, extend another class, be extended by other classes and so on. An example of an abstract class could be the following:
public abstract class Vehicle {
private String description;
public abstract void accelerate();
public abstract void decelerate();
// constructors and setters and getters methods omitted
}
Note that the accelerate and decelerate methods are declared abstract. In fact, we are defining a generic concept (a vehicle that moves) that cannot be implemented in a concrete way (each vehicle moves in a different way).
When to declare an abstract class
We have said that an abstract class that declares at least one abstract method must also be declared abstract. This is the standard case, the Vehicle class for example, is abstract because we don’t know how to define its accelerate and decelerate methods since we don’t know what vehicle we’re talking about. It follows that this class has been defined to be extended by concrete subclasses, for example the Car class, the Ship class, and the Plane class, each of which will provide a different implementation to the accelerate and decelerate overridden methods.
At the design level, therefore, abstract classes are a fundamental tool. In fact, declaring an abstract class means wanting to direct the development towards the creation of subclasses of the abstract class. This will not only allow us to save code that we would have had to put in all the subclasses and that instead we can put in the abstract superclass, but above all it will give us the possibility to use the objects of the subclasses Car, Ship, and Plane, as if they were vehicles (see section on polymorphism).
Therefore, it makes no sense to declare an abstract class if you don’t intend to extend it. Furthermore, it is essential to bear in mind that abstract classes have the task of representing concepts that are too generic for the context in which they are defined. In particular, the abstract modifier imposes an important design constraint: the class cannot be instantiated (even if the class defines all its methods).
In conclusion, we must use an abstract class to define a concept that is too generic to be instantiated with respect to the context of our program, and then extend it with concrete classes by exploiting the benefits of inheritance and polymorphism.
Too Generic Objects: Example
Suppose we want to create an application that allows you to compose and play music. This application must define virtual musical instruments, i.e., software instruments that simulate real musical instruments. To do this, we could create a hierarchy of musical instruments, of which we report a part below:
public abstract class Instrument { // Abstract Class
private String name;
private String type;
public abstract void play(String note); /* Each instrument sounds different!
* Impossible to define this
* method! */
// Rest of code omitted
}
public class Guitar extends Instrument { // Concrete class
private int numberOfStrings;
@Override
public void play(String note) { // Override of the inherited method
// Method implementation for a guitar
}
// Rest of code omitted
}
public abstract class WindInstrument extends Instrument { /* Abstract class
* extending the
* Instrument class */
private String material;
/* Method "play" inherited abstract and not overridden because it is too
* generic! */
// Rest of code omitted
}
public class Flute extends WindInstrument { /* Concrete classe extending
* WindInstrument */
@Override
public void play(String note) {
// Method implementation for a flute.
}
// Rest of code omitted
}
In this example we have seen that Instrument is the superclass of the concrete class Guitar (which redefines the method play) and of the abstract class WindInstrument (that does not override the play abstract method). Finally, the concrete class Flute extends WindInstrument (which in turn extended Instrument). So the guitar is an instrument, and the flute is a wind instrument and therefore also an instrument. We cannot instantiate objects from the two abstract classes, and this is consistent with their abstraction. In fact, how could we implement the play method of the WindInstrument class? A generic wind instrument does not exist in the real world: there are harmonics, trumpets, flutes, clarinets etc., so in the context of our program a wind instrument is too generic to be defined concretely.
Polymorphism
However, abstract classes allow us to avoid duplication (note that there are variables that are defined in abstract classes) and above all to exploit polymorphism. For example, our program could define a Performer class that takes care of playing the notes of any instrument:
public class Perfomer {
public void perform(Instrument instrument, String note) {
instrument.play(note);
}
// Rest of code omitted
}
Note that the perform method will be able to play any note of any instrument. And this greatly facilitates interaction with our software, because we will always use the same method to play any instrument.
Interface definition
An interface, like a class, is one of the 5 types defined by the Java language (the other types are enumerations, annotations and records from version 14 onwards). The interface definition has been significantly changed starting with version 8 of Java. In fact, up to version 7, the interface concept was very simple and clear, since all methods were implicitly public and abstract. Today, however, an interface is defined by the following characteristics:
- It is declared using the
interfacekeyword - It cannot be instantiated
- Can extend other interfaces
- A class can implement multiple interfaces
- Can declare:
- Public abstract methods (it is not necessary to use the
publicandabstractmodifiers which will be implicitly added by the compiler) - Public default methods, i.e. concrete methods marked with the
defaultmodifier (it is not necessary to use thepublicmodifier which will be added implicitly by the compiler) - Concrete private methods (can only be invoked by default methods)
- Public or private static methods (a static method without access specifiers will be implicitly considered public by the compiler)
- Static and public constants (it is not necessary to use the
public,finalandstaticmodifiers and they will be added implicitly by the compiler)
- Public abstract methods (it is not necessary to use the
It is not possible to declare anything else within an interface.
Currently there are other advanced properties that characterize interfaces, such as being able to declare nested types and that they are always being implicitly static when declared as nested types, but these properties are of little interest to most, because they are useful only in very rare cases. If you are interested, find these and other advanced topics in my book “Java for Aliens“.
An example of an interface could be the following:
public interface Weighable{
public static final String UNIT_OF_MEASURE = "kg";
public abstract double getWeight();
}
which we can equivalently rewrite by omitting all modifiers:
public interface Weighable {
String UNIT_OF_MEASURE = "kg";
double getWeight();
}
However, classes cannot extend interfaces using the extends keyword, but they can implement them. In fact, the implements keyword is used in a very similar way to extends, and produces the same result: that of inheriting the members of the implemented interface. We could then use the interface of the example, implementing it in an Article class:
public class Item implements Weighable {
private double weight;
private String description;
public Item(String description, double weight) {
setDescription(description);
setWeight(weight);
}
public double getWeight() {
return weight;
}
// Rest of code omitted
}
From the design point of view, an interface is an evolution of the concept of abstract class. In fact, it is possible to force subclasses to implement abstract methods defined in interfaces, as and better than we can do with abstract classes.
From a code point of view, on the other hand, an interface resembles a class without its internal implementation. In fact, an interface wants to represent what with encapsulation we call its public interface, that is, that part of the object visible from the outside, which hides its internal implementation. Therefore, it is to be considered as the unimplemented half of an object.
Difference in the Declaration
The previous example is an excellent example of a pre-Java 8 interface. Today, however, the interface name has somehow lost its original meaning. In fact, although it is always possible to use interfaces by declaring only abstract methods, with the introduction of default methods and static methods, interfaces are now technically almost equivalent to abstract classes as regards the declaration. If we put aside the use of the interface keyword instead of abstract class, and the fact that for interfaces modifiers are often implicitly inferred by the compiler, the only important difference is that interfaces cannot declare instance variables and constructors. For the rest, technically they are two very similar concepts. In fact, it is not possible to instantiate neither abstract classes nor interfaces. Furthermore, the common advantage that both abstract classes and interfaces offer is that they can force subclasses to implement behaviors. In fact, a class that inherits an abstract method must override the inherited method or be declared abstract itself. From a design point of view, therefore, these tools support abstraction in a very similar way.
Conceptual difference
One of the most important difference, although too often ignored, is a conceptual difference. We have said that an abstract class should define an abstraction that is too generic to be instantiated in the context in which it is declared. A good example is the Vehicle class:
public abstract class Vehicle {
private String description;
public abstract void accelerate();
public abstract void decelerate();
// constructors and setters and getters methods omitted
}
We can therefore extend the Vehicle class for example with the Plane class, which obviously will have its own overridden implementation of the accelerate and decelerate abstract methods:
public class Plane extends Vehicle {
@Override
public void accelerate() {
// override of the inherited method
// implementation omitted
}
@Override
public void decelerate() {
// override of the inherited method
// implementation omitted
}
}
An interface should abstract a behavior that multiple classes could implement, and a behavior should not be instantiated. In fact, there should be no objects that represent a behavior. Indeed, interfaces often have adjectives and behaviors as names (Weighable, Comparable, Cloneable, etc.). If anything, there should be objects that implement one or more behaviors.
For example, we could introduce a Flying interface that will be implemented by classes that represent flying objects (note how the name suggests a behavior rather than an abstract object):
public interface Flying {
void land();
void takeOff();
}
each class that has to abstract a concept of a flying object (such as a plane, a drone or even a bird), must implement the Flying interface. So, we can rewrite the Plane class in the following way:
public class Plane extends Vehicle implements Flying {
@Override
public void land() {
// overrides the method of the Flying interface
}
@Override
public void takeOff() {
// overrides the method of the Flying interface
}
@Override
public void accelerate() {
// overrides the method of the Vehicle abstract class
}
@Override
public void decelerate() {
// overrides the method of the Vehicle abstract class
}
}
In figure 1 you will find the corresponding class diagram.

Figure 1 – Class diagram.
We could then create polymorphic parameters to take advantage of the Flying interface:
public class ControlTower {
public void authorizeLanding(Flying v) {
v.land();
}
public void authorizeTakeOff(Flying v) {
v.takeOff();
}
}
In this way we can pass to these methods flying objects created by classes that implement the Flying interface.
Multiple inheritance
The most famous and important difference, however, concerns inheritance. In fact, while it is possible to extend only one class at a time, it is instead possible to implement any number of interfaces. With the introduction of default methods in Java 8, it is important to point out that a simplified version of that feature called multiple inheritance has also been finally introduced in the language.
We are talking about simplification because classes can inherit only their functional part (methods) from interfaces and not the data (apart from any static constants that an interface can declare). In other languages, however, complete multiple inheritance is defined, which brings with it very complicated rules to manage the problems arising from its implementation.
It was possible to implement multiple interfaces even before Java 8, but the inherited abstract methods always had to be overridden. With the advent of default methods, multiple inheritance has taken on a different meaning than in the past. So, if we consider the following interfaces:
public interface Reader {
default void read(Book book) {
System.out.println("I'm reading: " + book.getTitle() + " by "
+ book.getAuthor());
}
}
public interface Programmer {
default void program(String language) {
System.out.println("I'm programming with " + language);
}
}
we can create the following class that implements both interfaces and inherits their methods:
public class WhosReading implements Reader, Programmer {
}
And here is an example of use:
public class MultipleInheritanceTest {
public static void main(String args[]) {
var you = new WhosReading();
var javaForAliens = new Book("Java for Aliens","Claudio De Sio Cesari");
you.read(javaForAliens);
you.program("Java");
}
}
obviously, it is possible to implement multiple inheritance in Java also by simultaneously extending a class (abstract or not) and one or more interfaces.
Inheritance Applicability
Another lesser known but important difference, concerns the applicability of inheritance. In fact, only a class can extend another class, not other types of Java can do it. Interfaces, on the other hand, can extend other interfaces, but they cannot extend classes (abstract or concrete). Furthermore, interfaces can be implemented by classes, enumerations and records, which therefore can also take advantage of the inherited default methods. In particular the records (introduced in Java 14 as feature preview), allow to define classes that represent immutable data with a minimal syntax. They are not extensible and cannot extend classes. This is because at compile time the records are transformed into final classes, which extend the class from the java.lang.Record, and therefore cannot extend other classes. Fortunately, they can implement interfaces. For example, considering the Designer interface:
public interface Designer {
default void design(String tool) {
System.out.println("I am designing software with " + tool);
}
}
we can create the Employee record in the following way:
public record Employee (String name, int id) implements Designer { }
e usarlo con un codice come il seguente:
Employee serj = new Employee("Serj", 10);
serj.design("UML");
obtaining the output:
I am designing software with UML
Also the enumerations can implement interfaces but not extend classes, since at compile time they are transformed into classes that extend the java.lang.Enum class.
Conclusion
With the evolution of interfaces starting from Java 8, the technical differences with abstract classes have become less. Neither abstract classes nor interfaces can be instantiated. Furthermore, the common advantage that both abstract classes and interfaces offer is that they can force subclasses to implement abstract methods. However, we could summarize the main differences simplistically in this way:
- Interfaces cannot declare data (apart from static and public constants).
- Abstract classes should abstract objects that are too generic to instantiate, while interfaces should abstract behaviors that different objects could implement.
- Only one class can be extended at a time, but multiple interfaces can be implemented.
- Classes can only be extended by other classes, while interfaces can also be implemented by enumerations and records.
Author’s Notes
This article is based on a few paragraphs from my English book “Java for Aliens“.
