Abstract Classes vs. Interfaces in Java: What are the Differences?
Time to read: 5 minutes
Java was the third most-used programming language in 2022, according to the TIOBE Index. So, you can get an idea of the relevancy of Java in modern software engineering.
Interfaces and abstract classes are fundamental concepts for any Java developer. Mastering these concepts helps you develop more maintainable applications.
In this article, I'll show you how to use interfaces and abstract classes, their differences, and real-world applications.
Prerequisites
For this tutorial, you'll need a few things before starting:
- Java Development Kit (JDK) version 17.
- IntelliJ IDEA.
What is an Interface?
The interface keyword in Java defines a contract between the developer and an API. It’s an agreement on how the client code should use defined methods.
The main purpose of an interface is to implement subtype polymorphism. Therefore, an interface is a kind of type that can be replaced with any of its subtypes at any time. More informally, it provides a list of methods used as an entry point to concrete implementations.
There are two types of methods in interfaces. Default methods, which describe a behavior that you can opt to override. And abstract methods that describe no behavior, and you must always override them.
Ultimately, all interface fields are implicitly public, static, and final. That means three things.
- Firstly, you must always initialize all interface fields to some value.
- Secondly, all fields are visible through the interface name, not its instance.
- Thirdly, you can’t change the value of any field after initialization.
Interface’s implementations inherit all of its fields. However, you can override them by declaring the same field name in a subclass with another value. That technique is called field hiding: you effectively hide the original field with the new value.
All these concepts and terms will become clearer in the examples in the following section.
Create Interfaces and Multiple Implementations
You can create interfaces using the interface
keyword. I’ll use a zombie invasion game application to illustrate its usage.
First, create a Java package named zombieattack
in your application. Inside that package, create the Zombie
interface with the content below:
The Zombie
interface contains one default method and one abstract method. To create any concrete subclass of Zombie
, you must override the attack()
method. In contrast, you can override or not the walk()
method.
To illustrate method overrides, imagine two special types of zombies: the jumper and the warrior. The jumper walks two steps and attacks once. And the warrior walks 1 step and attacks twice.
You can define special jumper and warrior zombies as implementations of Zombie
. To do so, create the JumperZombie
file in the zombieattack
package and add the following content:
In that same package, create the WarriorZombie
class and add the following content:
The JumperZombie
class overrides both the attack()
and walk()
. Meanwhile, the WarriorZombie
class only overrides the attack()
method since the behavior of walk()
already achieves the desired result.
In the Zombie
interface, I’ve also defined two fields, STEP_NUMBER
and ATTACK_NUMBER
, initialized with 1. In its subclasses, the original values were hidden with new values. That means in the JumperZombie
class, the value of STEP_NUMBER
is now 2, and the inherited value of 1 is ignored. The same applies to the ATTACK_NUMBER
field in the WarriorZombie
class.
Implement multiple interfaces
One important point about method overriding is that a class can implement any number of interfaces. In that case, the subclass must implement all abstract methods from both interfaces like in the code below:
Note the read()
method at the bottom of the above class. That is an abstract method in the Readable
interface, so you must override in the JumperZombie
class.
Make an interface extend another interface
An interface can also extend methods from another interface. In that case, subclasses must implement the methods of the direct interface and the methods of the interface above it. So, for example, if you change the Zombie
class signature to the following:
Now both JumperZombie
and WarriorZombie
classes should also implement the compareTo()
method of Comparable
and the attack()
. With that said, the code for JumperZombie
looks like the following:
And the WarriorZombie
looks like following:
Real-world applications for interfaces
You can find good examples of interfaces at the JDK. For instance, the Comparable
interface provides a custom way to compare objects of its subclasses. Likewise, the Closeable
interface allows customization of how to close a connection to a file or a database.
Or, you can use interfaces to implement the Strategy Design Pattern. That pattern uses an interface as an entry point to find and use a specific algorithm in its subclasses based on an input field.
What is an Abstract Class?
Simply put, an abstract class is a class that uses the abstract
keyword. In those classes, you define abstract methods and override them in subclasses, just like interfaces. The non-abstract methods work exactly like methods in regular classes. Additionally, methods in abstract classes can have any Java modifier: public
, default
, protected
, or private
.
As with interfaces, you can instantiate an abstract class only via its subclasses, not directly.
Unlike interfaces, fields in abstract classes are mutable, and you can change its values at runtime.
Define Abstract Classes and Subclasses
In our zombie invasion game, different types of soldiers fight against zombies. So, I’ll illustrate abstract classes using different types of soldiers in the game. To do that, create the Soldier
abstract class in the zombieattack
package with the content below:
The Soldier
abstract class defines one abstract method, attack()
, and one non-abstract, isDead()
. So, attack()
defines the signature that any concrete implementation of Soldier
must override, similarly to interfaces. Whereas, with the isDead()
method, you can opt to override.
I’ve also defined two fields, life
and damage
initialized with 10 and 1, respectively. They are default fields that you can use in subclasses. Contrasting with interface field hiding, the state of these two fields is preserved. Thus, changing its values will effectively change them, not just hide them.
Now, create the class Shooter
with the content below:
And, create the class Fighter
with the content below:
Note that I’ve changed the life
and damage
fields to something else in the subclasses’ constructors. With abstract classes, you don’t need field hiding to achieve that. You can change the state of the inherited field with another value.
For instance, in the Shooter
class, the damage
is now 6, but life
is still 10. Whereas in the Fighter
class, life
is 25 and damage
is 2. However, the same fields defined in Soldier
were mutated without defining different life
and damage
fields.
Real-world applications for abstract classes
One good example of an abstract class found in the JDK is the InputStream
class. That class specifies the top class for all classes representing an input stream of bytes. It’s widely used to read and write byte files.
Abstract classes appear in the template method to create a kind of template for subclasses. The abstract class, called template, defines a skeleton of an algorithm as a set of fields and methods. That skeleton is common to all subclass implementations of that algorithm. Thus, when you override the template, you get that predefined logic. The main advantage of it is that you can reuse the skeleton code in many other classes without rewriting it all over.
Conclusion
You may note that interfaces and abstract classes are very similar. In both, you can define default and abstract methods. Also, you can define fields that subclasses inherit. Finally, you can only instantiate them via sub-classes, not directly.
The big difference between them is their state. Abstract classes are stateful, so you can mutate the values of their fields, as seen in the usage of this
in the subclasses of Soldier
. However, interfaces are stateless, so you need to specify a field again as a constant in subclasses if you want a new value for it, as seen in the field hiding in subclasses of Zombie
.
Therefore, each of them is suitable for different situations, as you’ve seen in the real-world applications section.
Pedro Lopes is a backend developer at MercadoLibre and an independent writer. He's a specialist and enthusiastic about distributed systems, big data, and high-performance computing. He can be found on LinkedIn, Github, and his Blog.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.