Interfaces
Explore Java interfaces to understand how they define method contracts enabling unrelated classes to share capabilities. Learn to implement multiple interfaces, use default methods for API evolution, and distinguish interfaces from abstract classes to design flexible, maintainable applications.
We can use abstract classes to share code and define templates for related objects. This works perfectly when objects share a strict is-a relationship, like a Dog being an Animal. But often we need to define a capability shared by objects that are completely unrelated. Consider a bird and an airplane: both can fly, but they do not belong to the same family tree. If we tried to force them under a common Flyer superclass, we would create a messy, confusing hierarchy.
Java solves this with interfaces, a mechanism that defines what an object can do, regardless of its type. By using interfaces, we create flexible contracts that decouple our code from specific implementation details.
Defining a contract with interfaces
An interface is a contract that specifies a set of methods that a class must implement. Unlike a class, an interface does not describe how behavior is performed; it only describes what that behavior looks like.
We define an interface using the interface keyword. Inside, we declare methods without bodies. By default, these methods are implicitly public and abstract. We do not need to type these keywords, though knowing they are there helps us understand the rules.
Interfaces can also contain constant values. By rule, any java interface variable you declare is implicitly public, static, and final.
Here is how we define a simple contract for objects that can be saved to a database:
Line 2: We use the
interfacekeyword to define a contract that specifies what a class must do, rather than how it does it.Line 4: We declare a constant field. Even without keywords, it is
staticandfinal.Lines 7 & 10: We declare method signatures ending with a semicolon. We provide no braces or logic.
Implementing interfaces
Once we define an interface, classes must sign the contract using the implements keyword. When a class implements an interface, it promises to provide an implementation for every abstract method defined in that interface.
If a concrete class fails to implement even one method from the interface, the Java compiler will generate an error. This ensures that any object treated as Persistable is guaranteed to have working save() and delete() methods.
Here, we see two unrelated classes implementing the same contract:
Lines 1–3: We define a functional contract
Persistablewith a single method.Line 5: The
Userclass signs the contract usingimplements Persistable.Lines 12–15:
Userprovides its own specific logic forsave().Line 18:
FileDocumentalso implementsPersistable. It shares no hierarchy withUser.Lines 25–28:
FileDocumentprovides totally different saving logic tailored to files.Lines 33–34: We can treat both objects as
Persistable, referencing them by their interface type rather than their class type.
Multiple inheritance of type
One of Java’s strict rules is that a class can extend only one superclass. This avoids the Diamond Problem, where a class inherits conflicting state (like two variables named x) from two different parents.
However, Java allows a class to implement multiple interfaces. Since interfaces generally define abstract behavior without state, there is no risk of conflicting variables. This allows us to combine capabilities. A SmartPhone might be a Phone, but it is also a Camera, a MusicPlayer, and GPS.
We separate interfaces with commas:
Lines 1–3: We define the
Camerainterface, which establishes a contract for any device capable of capturing images.Lines 5–7: We define the
GPSinterface, which establishes a contract for any device capable of providing geographic coordinates.Line 9: The
SmartPhoneclass uses theimplementskeyword followed by a comma-separated list to inherit the types of bothCameraandGPS.Lines 11–14: We implement the
takePhoto()method from theCamerainterface. This specific implementation simulates a sensor capture by printing a confirmation message to the console.Lines 16–19: We implement the
getLocation()method from theGPSinterface. This implementation simulates a coordinate lookup by printing specific latitude and longitude data.Line 24: We instantiate the
SmartPhoneobject. Because it implements both interfaces, themyPhonereference can access all methods defined in the hierarchy.Lines 26–27: We call the implemented methods to verify that the single
SmartPhoneobject successfully performs behaviors from two independent source interfaces.
Evolving interfaces with default methods
Historically, once an interface was published and used by other developers, we could never add a new method to it. Doing so would break every class implementing that interface, because those classes would suddenly be missing an implementation for the new method.
To solve this, modern Java allows interfaces to define default methods. A default method has a body and the default keyword. If an implementing class does not override this method, it simply uses the default version provided by the interface. This feature is primarily used for evolving APIs without breaking existing code.
Line 5: We define
checkBatterywith thedefaultkeyword and provide a method body.Line 10: The
Robotclass implementsChargeablebut ignorescheckBattery. It compiles successfully and inherits the default behavior.Line 24: The
ElectricCarclass overridescheckBatteryto provide a specialized implementation.Line 35: Calling the method on
robottriggers the fallback code defined in the interface.
Interfaces vs. abstract classes
Beginners often struggle to decide between an abstract class and an interface. The decision usually comes down to identity (“is-a”) versus capability (“can-do”).
Feature | Interface | Abstract class |
Purpose | Capability contract | Shared base with optional partial implementation |
Fields | Only | Can have instance fields |
Methods | Abstract methods + default methods | Abstract + concrete methods |
Inheritance | A class can implement many interfaces | A class can extend one abstract class |
Use an abstract class when:
You want to share code (state and methods) among closely related classes.
You need to define non-static and non-final fields (state).
The classes share a clear is-a relationship (e.g.,
Truckis aVehicle).
Use an interface when:
You want to define a capability for unrelated classes (e.g.,
BirdandAirplaneare bothFlyable).You need to take advantage of multiple inheritance (e.g., a class needs to be both
SortableandSerializable).You want to completely decouple your design from specific implementations.
In modern Java development, interfaces are preferred for defining API contracts because they offer greater flexibility.
We can now write code that depends on abstractions instead of concrete implementations. This decoupling enables modular and maintainable system design. By depending on interfaces or abstract contracts, we can replace one implementation with another, such as switching from file-based storage to a database, without changing the core business logic.