Interfaces & Abstract Classes

Introducing Interfaces

15 min Lesson 2 of 14

Introducing Interfaces

An interface is one of Java's most powerful design tools. At its core it is a named contract: a list of method signatures that any class choosing to honour the contract must implement. The class provides the how; the interface defines the what.

You have already seen inheritance let one class reuse another's code. Interfaces take a different angle — they decouple capability from lineage. A class does not need to share a common ancestor with another class to be treated identically by the rest of the program; it just needs to fulfil the same interface.

The interface keyword

Declare an interface with the interface keyword instead of class. All method declarations inside are implicitly public abstract — you do not need to write those modifiers explicitly (though it is legal to do so).

public interface Drawable { void draw(); // implicitly public abstract double area(); // implicitly public abstract }

That is the entire definition. No constructor, no instance fields, no method bodies (we will cover default methods in a later lesson). Drawable simply states: anything that is Drawable must be able to draw itself and report its area.

Implementing an interface

A class signs the contract with the implements keyword. The compiler then enforces that every abstract method is provided.

public class Circle implements Drawable { private final double radius; public Circle(double radius) { this.radius = radius; } @Override public void draw() { System.out.println("Drawing circle with radius " + radius); } @Override public double area() { return Math.PI * radius * radius; } } public class Rectangle implements Drawable { private final double width; private final double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public void draw() { System.out.println("Drawing rectangle " + width + "x" + height); } @Override public double area() { return width * height; } }
The @Override annotation is not required, but always use it. It tells the compiler you intend to fulfil an interface method. If you misspell the name the compiler will catch it immediately instead of silently creating a new method that satisfies nothing.

Programming to a contract

Here is where the real payoff comes. Because both Circle and Rectangle implement Drawable, you can refer to either of them through a variable of type Drawable. The calling code does not need to know — or care — which concrete class it is talking to.

public class Canvas { private final List<Drawable> shapes = new ArrayList<>(); public void addShape(Drawable shape) { shapes.add(shape); } public void renderAll() { for (Drawable shape : shapes) { shape.draw(); System.out.println(" area = " + shape.area()); } } } // Usage Canvas canvas = new Canvas(); canvas.addShape(new Circle(5.0)); canvas.addShape(new Rectangle(3.0, 4.0)); canvas.renderAll(); // Drawing circle with radius 5.0 // area = 78.53981633974483 // Drawing rectangle 3.0x4.0 // area = 12.0

Canvas was written once, long before Circle or Rectangle existed. Tomorrow you can add a Triangle class that also implements Drawable and drop it into the same canvas without touching a single line of Canvas. That is the Open/Closed Principle in action: open for extension, closed for modification.

Declare variables and parameters using the interface type, not the concrete class. Write Drawable d = new Circle(5); rather than Circle d = new Circle(5); whenever the rest of the code only needs the Drawable capabilities. This keeps callers decoupled from the implementation and makes swapping classes trivial.

Interfaces and the type system

An interface is a full type in Java. You can use it everywhere a type is expected:

  • Variable declarations: Drawable d = new Circle(3);
  • Method parameters: void render(Drawable shape)
  • Return types: Drawable createShape(String kind)
  • Generic bounds: List<Drawable> shapes

The instanceof operator works just as it does with classes:

Drawable d = new Circle(2.0); System.out.println(d instanceof Drawable); // true System.out.println(d instanceof Circle); // true — the runtime type is still Circle

What happens if you forget a method?

If a non-abstract class implements an interface but does not provide all the required methods, the compiler refuses to compile it. This compile-time safety is one of the biggest advantages of interfaces over looser runtime conventions.

// This will NOT compile — area() is missing public class Triangle implements Drawable { @Override public void draw() { System.out.println("Drawing triangle"); } // ERROR: Triangle is not abstract and does not override abstract method area() in Drawable }
Do not confuse implementing an interface with extending a class. A class uses implements for interfaces and extends for a superclass. Mixing them up (class Foo extends Drawable) is a compile error because Drawable is an interface, not a class. The correct syntax is class Foo implements Drawable.

Why bother? The practical argument

Consider a payment system. You might start with credit cards but later need to support PayPal, bank transfers, and crypto wallets. If you define a PaymentProcessor interface up front, your checkout code talks only to that contract. Adding a new payment method means writing one new class — no changes to the checkout logic, no risk of breaking existing payment flows.

public interface PaymentProcessor { boolean charge(String accountId, double amount); void refund(String transactionId); } public class StripeProcessor implements PaymentProcessor { @Override public boolean charge(String accountId, double amount) { // call Stripe API ... return true; } @Override public void refund(String transactionId) { // call Stripe refund endpoint ... } } // Checkout never references StripeProcessor directly public class Checkout { private final PaymentProcessor processor; public Checkout(PaymentProcessor processor) { this.processor = processor; } public void complete(String accountId, double amount) { if (processor.charge(accountId, amount)) { System.out.println("Payment successful"); } } }

Swapping Stripe for PayPal tomorrow is a one-line change at the point where Checkout is constructed — everything else stays identical.

Summary

An interface declares a contract: a set of method signatures a class must honour. Use interface to define it and implements to sign the contract. Declare variables and parameters against the interface type — not the concrete class — so that callers are decoupled from the implementation. This pattern, often called programming to an abstraction, is the foundation of flexible, testable Java design.

In the next lesson we will compare interfaces with abstract classes and learn how to choose between them.