Implementing Interfaces in Java: Syntax, Examples, Rules & Best Practices

0

Implementing Interfaces in Java

Defining an interface is only the beginning. The real value appears when classes implement that interface and provide the behavior promised by its contract.

Think of an interface as a specification handed to a development team: “Any class that claims to support this capability must provide these operations.” The implementing class is responsible for turning that specification into working code.

A class implements an interface using the implements keyword. If the class is concrete, it must provide implementations for all inherited abstract interface methods.

Basic Syntax

The basic syntax for implementing an interface is:

class ClassName implements InterfaceName
{
    // Implement interface methods
}

For example, suppose we define a simple interface for a payment operation.

interface Payment
{
    void pay();
}

A class can implement this interface as follows:

class CreditCardPayment implements Payment
{
    @Override
    public void pay()
    {
        System.out.println("Payment made using credit card");
    }
}

The CreditCardPayment class has now fulfilled the contract defined by Payment.

Why Is public Required?

Methods declared directly in an interface are public by default when they are abstract methods. Therefore, the implementing method cannot reduce the visibility of that method.

interface Vehicle
{
    void start();
}

class Car implements Vehicle
{
    @Override
    public void start()
    {
        System.out.println("Car started");
    }
}

The method in Vehicle is effectively public, so the implementation in Car must also be public.

Trying to make it package-private, protected, or private would reduce its visibility and cause a compilation error.

class Car implements Vehicle
{
    void start()
    {
        System.out.println("Car started");
    }
}

When implementing an abstract interface method, the implementation must provide an access level that is at least as accessible as the interface method. For normal interface abstract methods, that means public.

Implementing Multiple Methods

An interface can declare several methods. A concrete implementing class must provide all of them.

interface Vehicle
{
    void start();

    void accelerate();

    void stop();
}

class Car implements Vehicle
{
    @Override
    public void start()
    {
        System.out.println("Car started");
    }

    @Override
    public void accelerate()
    {
        System.out.println("Car accelerating");
    }

    @Override
    public void stop()
    {
        System.out.println("Car stopped");
    }
}

The interface defines the required operations, while Car supplies the actual implementation of each operation.

What Happens If a Method Is Not Implemented?

Suppose the interface contains three abstract methods, but the class implements only two.

interface Vehicle
{
    void start();

    void accelerate();

    void stop();
}

class Car implements Vehicle
{
    @Override
    public void start()
    {
        System.out.println("Car started");
    }

    @Override
    public void stop()
    {
        System.out.println("Car stopped");
    }

    // accelerate() is missing
}

Because Car is a concrete class, the compiler reports an error. It has not fulfilled the complete interface contract.

There are two possible solutions: implement the missing method or declare the class abstract.

abstract class Car implements Vehicle
{
    @Override
    public void start()
    {
        System.out.println("Car started");
    }

    @Override
    public void stop()
    {
        System.out.println("Car stopped");
    }

    // accelerate() can remain unimplemented
}

An abstract implementing class can postpone the implementation to one of its subclasses.

Using @Override

The @Override annotation is strongly recommended when implementing interface methods.

interface Employee
{
    void work();
}

class Developer implements Employee
{
    @Override
    public void work()
    {
        System.out.println("Developer writes code");
    }
}

Besides making the code easier to read, @Override allows the compiler to verify that the method actually overrides or implements a parent declaration.

Why @Override Helps Catch Mistakes

Consider a simple spelling mistake in a method name.

interface Employee
{
    void work();
}

class Developer implements Employee
{
    @Override
    public void works()
    {
        System.out.println("Developer writes code");
    }
}

The compiler immediately identifies the problem because works() does not implement work(). Without @Override, the mistake can be harder to spot during development.

Interface Reference with Implementing Object

Once a class implements an interface, an interface reference can point to an object of that class.

interface Payment
{
    void pay();
}

class UPIPayment implements Payment
{
    @Override
    public void pay()
    {
        System.out.println("Payment made using UPI");
    }
}

class Main
{
    public static void main(String[] args)
    {
        Payment payment = new UPIPayment();

        payment.pay();
    }
}

This is an important design technique. The variable depends on the Payment abstraction rather than the concrete UPIPayment implementation.

Multiple Classes Can Implement the Same Interface

One of the strongest features of interfaces is that many unrelated classes can implement the same contract.

interface Payment
{
    void pay();
}

class CreditCardPayment implements Payment
{
    @Override
    public void pay()
    {
        System.out.println("Credit card payment");
    }
}

class UPIPayment implements Payment
{
    @Override
    public void pay()
    {
        System.out.println("UPI payment");
    }
}

class CashPayment implements Payment
{
    @Override
    public void pay()
    {
        System.out.println("Cash payment");
    }
}

The three classes are independent implementations of the same contract. The code using them can work with the common Payment type.

Practical Example: Notification System

Let's build a small notification system. The application should support email and SMS notifications.

interface Notification
{
    void send(String message);
}

class EmailNotification implements Notification
{
    @Override
    public void send(String message)
    {
        System.out.println("Email: " + message);
    }
}

class SMSNotification implements Notification
{
    @Override
    public void send(String message)
    {
        System.out.println("SMS: " + message);
    }
}

class Main
{
    public static void main(String[] args)
    {
        Notification email = new EmailNotification();
        Notification sms = new SMSNotification();

        email.send("Welcome to our application");
        sms.send("Your OTP is 1234");
    }
}

The important design decision is that the application can work with Notification instead of depending on a specific notification class.

Passing an Interface to a Method

An interface becomes even more useful when methods accept interface types as parameters.

interface Notification
{
    void send(String message);
}

class EmailNotification implements Notification
{
    @Override
    public void send(String message)
    {
        System.out.println("Email: " + message);
    }
}

class NotificationService
{
    void sendNotification(Notification notification)
    {
        notification.send("Welcome!");
    }
}

class Main
{
    public static void main(String[] args)
    {
        NotificationService service = new NotificationService();

        service.sendNotification(new EmailNotification());
    }
}

The NotificationService does not need to know the concrete notification class. It only requires an object that satisfies the Notification contract.

This is a practical form of loose coupling: the consumer depends on an abstraction instead of a concrete implementation.

Returning an Interface from a Method

A method can also return an interface type.

interface Payment
{
    void pay();
}

class UPIPayment implements Payment
{
    @Override
    public void pay()
    {
        System.out.println("UPI payment");
    }
}

class PaymentFactory
{
    static Payment createPayment()
    {
        return new UPIPayment();
    }
}

class Main
{
    public static void main(String[] args)
    {
        Payment payment = PaymentFactory.createPayment();

        payment.pay();
    }
}

The caller knows that it receives a Payment, but does not need to know which concrete implementation the factory created.

Implementing an Interface with Fields

Interface fields are constants, so an implementing class can access them but cannot redefine them as instance state.

interface Application
{
    String VERSION = "1.0";
}

class MobileApplication implements Application
{
    void displayVersion()
    {
        System.out.println(VERSION);
    }
}

The field is inherited as a public static final constant. It belongs to the interface rather than to each individual object.

Implementing an Interface with Default Methods

Modern Java interfaces can contain default methods. A class implementing such an interface does not have to override the default method unless it needs different behavior.

interface Vehicle
{
    void start();

    default void stop()
    {
        System.out.println("Vehicle stopped");
    }
}

class Car implements Vehicle
{
    @Override
    public void start()
    {
        System.out.println("Car started");
    }
}

Here, Car implements the abstract start() method but inherits the default implementation of stop().

A class may override the default method if specialized behavior is required.

class Car implements Vehicle
{
    @Override
    public void start()
    {
        System.out.println("Car started");
    }

    @Override
    public void stop()
    {
        System.out.println("Car stopped safely");
    }
}

Implementing Multiple Interfaces

Java allows a class to implement multiple interfaces. This is particularly useful when a class has multiple independent capabilities.

interface Printable
{
    void print();
}

interface Scannable
{
    void scan();
}

class Printer implements Printable, Scannable
{
    @Override
    public void print()
    {
        System.out.println("Printing document");
    }

    @Override
    public void scan()
    {
        System.out.println("Scanning document");
    }
}

The Printer class now promises to provide both capabilities. Multiple interface implementation is covered in greater depth in the next chapter.

Interface Implementation and Dependency Injection

Interfaces are commonly used with dependency injection because they allow an application component to receive an abstraction instead of constructing a concrete dependency itself.

interface PaymentGateway
{
    void processPayment();
}

class StripeGateway implements PaymentGateway
{
    @Override
    public void processPayment()
    {
        System.out.println("Processing payment through gateway");
    }
}

class OrderService
{
    private PaymentGateway gateway;

    OrderService(PaymentGateway gateway)
    {
        this.gateway = gateway;
    }

    void checkout()
    {
        gateway.processPayment();
    }
}

class Main
{
    public static void main(String[] args)
    {
        PaymentGateway gateway = new StripeGateway();

        OrderService service = new OrderService(gateway);

        service.checkout();
    }
}

The OrderService depends on the interface, not on the concrete gateway. This design makes the component easier to replace, test, and maintain.

Common Beginner Mistakes

  • Using extends instead of implements when a class implements an interface.
  • Forgetting that implementations of normal abstract interface methods must be public.
  • Implementing only some abstract methods in a concrete class.
  • Accidentally changing the method signature and thinking the method has been implemented.
  • Ignoring the @Override annotation, making accidental signature mistakes harder to detect.
  • Creating unnecessary dependencies on concrete classes instead of accepting an interface.

Best Practices

  • Use @Override for interface method implementations.
  • Keep implementation classes focused on fulfilling the interface contract.
  • Prefer interface types for method parameters and return types when implementations can vary.
  • Use interfaces to reduce coupling between application components.
  • Do not force a class to implement unrelated responsibilities through an oversized interface.

Interview Insights

Question: Which keyword is used to implement an interface?

Answer: A class uses the implements keyword.

Question: Must a concrete class implement every abstract method of an interface?

Answer: Yes. If it does not, the class must be declared abstract.

Question: Why should interface implementations usually use @Override?

Answer: It clearly communicates the intent and allows the compiler to verify that the method correctly implements or overrides the declared contract.

Question: Can an interface reference refer to an implementing class object?

Answer: Yes. For example, Payment payment = new UPIPayment(); is valid when UPIPayment implements Payment.

Quick Revision

Concept Key Point
implements Keyword used by a class to implement an interface.
Concrete class Must implement all inherited abstract interface methods.
Abstract implementing class Can leave inherited abstract methods unimplemented.
Access level Normal abstract interface methods are public, so their implementations must be public.
@Override Helps document and verify interface method implementation.
Interface reference Can refer to an object of any compatible implementing class.
Default method Can provide inherited behavior that an implementing class may override.
Loose coupling Consumers can depend on the interface rather than a concrete implementation.

Final Takeaway

Implementing an interface means turning a contract into real behavior. The interface defines the capability, while the implementing class supplies the implementation. Once you start using interface references, method parameters, return types, and dependency injection, the design becomes significantly more flexible because your code depends on stable contracts rather than concrete classes. That is one of the key ideas behind maintainable and scalable Java applications.

Post a Comment

0Comments
Post a Comment (0)