Lesson 11 • Advanced
Interfaces
An interface is a contract: a list of methods a class promises to provide. Learn to declare and implement them, combine several at once, add default and static methods, and tell an interface from an abstract class.
What You'll Learn in This Lesson
- ✓You'll be able to declare an interface and implement it with implements
- ✓You'll be able to implement several interfaces in one class
- ✓You'll be able to add default and static methods to an interface
- ✓You'll be able to define and use interface constants
- ✓You'll be able to use a functional interface with a lambda
- ✓You'll be able to choose between an interface and an abstract class
📚 Before You Start
This lesson builds on classes and inheritance. Make sure you're comfortable with:
- Classes & Objects — defining a class and creating objects from it
- Inheritance — extending a class with
extendsand overriding methods
💡 The Big Idea — A Job Description
Analogy: An interface is a job description. It lists what the role must be able to do — "write reports, attend meetings, hit deadlines" — but it never says how. Two different people (classes) can both satisfy the same job description in completely different ways.
In code, the interface lists method names with no bodies. Any class that says implements the interface is signing the contract: it must supply a real version of every listed method. The payoff is that you can write one method that accepts the interface and it works with every class that implements it — present and future. That is what makes interfaces the backbone of flexible, testable Java.
1️⃣ Declaring and Implementing an Interface
You declare an interface with the interface keyword and list method signatures — a signature is just the return type, the name, and the parameters, with a semicolon instead of a body. These methods are automatically public and abstract, so you never write those words.
interface Drivable {
String drive(); // no body — just the promise
int getSpeed();
}A class then implements the interface and provides a body for every method. Because the contract methods are public, your implementations must be public too.
class Car implements Drivable {
private int speed = 0;
public String drive() { speed = 60; return "Driving"; }
public int getSpeed() { return speed; }
}Drivable d = new Car();. The variable only knows about the contract, so any class that fulfils it slots straight in. Calling the same method on different objects is called polymorphism.interface Drivable {
// Interface methods are public + abstract by default — just a signature, no body.
String drive();
String stop();
int getSpeed();
}
// "implements" = this class promises to provide EVERY method in the contract.
class Car implements Drivable {
private int speed = 0;
public String drive() { speed = 60; return "Car driving at " + speed + " mph"; }
public String stop() { speed = 0; return "Car stopped"; }
public int getSpeed() { return speed; } // must be public — matches the contract
}
class Bicycle implements Drivable {
private int speed = 0;
public String drive() { speed = 15; return "Bicycle pedalling at " + speed + " mph"; }
public String stop() { speed = 0; return "Bicycle stopped"; }
public int getSpeed() { return speed; }
}
public class Main {
// This method works with ANY Drivable — that is the whole point of an interface.
static void testDrive(Drivable vehicle) {
System.out.println(vehicle.drive());
System.out.println(" Speed: " + vehicle.getSpeed() + " mph");
System.out.println(" " + vehicle.stop());
}
public static void main(String[] args) {
testDrive(new Car()); // pass a Car
testDrive(new Bicycle()); // ...or a Bicycle — same method, no changes
}
}Car driving at 60 mph
Speed: 60 mph
Car stopped
Bicycle pedalling at 15 mph
Speed: 15 mph
Bicycle stopped🎯 Your Turn #1 — Implement a Shape
The Square class promises to be a Shape, but two methods are blank. Fill in the ___ blanks so it satisfies the contract. Run it and check it against the expected output in the comment.
interface Shape {
double area(); // 👉 you must give Square a body for this
String name();
}
class Square implements Shape {
private double side;
Square(double side) { this.side = side; }
// 🎯 YOUR TURN — finish the two methods this contract requires
// 1) Return the area of the square (side * side)
public double area() { return ___; } // 👉 replace ___ with side * side
// 2) Return the text "Square"
public String name() { return ___; } // 👉 replace ___ with "Square" in double quotes
}
public class Main {
public static void main(String[] args) {
Shape s = new Square(4);
System.out.println(s.name() + " area = " + s.area());
// ✅ Expected output: Square area = 16.0
}
}2️⃣ Multiple Interfaces
A class can only extends one parent class, but it can implements as many interfaces as you like — just separate them with commas. This is how Java gives you the flexibility of multiple inheritance without its headaches.
class Duck implements Flyable, Swimmable {
public String fly() { return "Flapping"; }
public String swim() { return "Paddling"; }
}A Duck now counts as a Flyable and a Swimmable. You can pass it to any method expecting either type. It just has to provide every method from every interface it signs up for.
3️⃣ Default, Static Methods, and Constants
Since Java 8 an interface can ship real behaviour, not just signatures:
defaultmethod — has a body. Every implementing class inherits it for free, but can override it. Use it to add a method to an interface without breaking existing implementers.staticmethod — belongs to the interface itself. You call it on the interface name, e.g.Flyable.info(), never on an instance.- Constants — any field you declare is automatically
public static final. Soint MAX_ALTITUDE = 10000;is a shared, unchangeable value.
interface Flyable {
int MAX_ALTITUDE = 10000; // a constant (public static final)
String fly(); // abstract — must be implemented
default String land() { return "Landing"; } // inherited body
static String info() { return "Has wings"; } // called as Flyable.info()
}MAX_ALTITUDE = 5000; anywhere is a compile error, because the field is implicitly final.interface Flyable {
// CONSTANT: interface fields are implicitly public static final.
int MAX_ALTITUDE = 10000;
String fly(); // abstract — every Flyable must say how it flies
// DEFAULT method: has a body, so implementers get it for free (Java 8+).
default String land() { return "Landing safely below " + MAX_ALTITUDE + " ft"; }
// STATIC method: belongs to the interface itself, called as Flyable.info().
static String info() { return "Flyable: anything with wings or rotors"; }
}
interface Swimmable {
String swim();
default String dive() { return "Diving deep"; }
}
// A class can implement MANY interfaces (Java's answer to multiple inheritance).
class Duck implements Flyable, Swimmable {
public String fly() { return "Duck flapping its wings"; }
public String swim() { return "Duck paddling along"; }
// Inherits default land() and dive() for free — no need to write them.
}
public class Main {
public static void main(String[] args) {
Duck d = new Duck();
System.out.println(d.fly()); // its own implementation
System.out.println(d.land()); // default from Flyable
System.out.println(d.swim()); // its own implementation
System.out.println(d.dive()); // default from Swimmable
System.out.println(Flyable.info()); // static, called on the interface
System.out.println("Limit: " + Flyable.MAX_ALTITUDE); // the constant
}
}Duck flapping its wings
Landing safely below 10000 ft
Duck paddling along
Diving deep
Flyable: anything with wings or rotors
Limit: 10000🎯 Your Turn #2 — A Document That Prints and Saves
A Document should be both Printable and Saveable. Fill the blanks so it implements both interfaces and provides the missing method. Notice you get preview() for free from the default method.
interface Printable {
String content();
// A default method — you DON'T rewrite this, you just call it.
default String preview() { return "Preview: " + content(); }
}
interface Saveable {
String save();
}
// 🎯 YOUR TURN — a Document is both Printable AND Saveable
class Document implements ___, ___ { // 👉 list both interfaces, separated by a comma
private String text;
Document(String text) { this.text = text; }
// 1) Return the stored text
public String content() { return text; }
// 2) Return a save message
public String save() { return ___; } // 👉 replace ___ with "Saved!" in double quotes
}
public class Main {
public static void main(String[] args) {
Document doc = new Document("Hello");
System.out.println(doc.preview()); // uses the default method for free
System.out.println(doc.save());
// ✅ Expected output:
// Preview: Hello
// Saved!
}
}4️⃣ Interface Inheritance & Functional Interfaces
Interfaces can extends other interfaces to build bigger contracts. A class that implements the child must satisfy the whole chain — every method, inherited or added.
interface Animal { String name(); }
interface Pet extends Animal { String owner(); } // Pet = name() + owner()
class Dog implements Pet {
public String name() { return "Rex"; } // from Animal
public String owner() { return "Sam"; } // from Pet
}When an interface has exactly one abstract method, it's a functional interface, and you can implement it with a one-line lambda instead of a whole class. Mark it with @FunctionalInterface and the compiler checks there's only one abstract method.
@FunctionalInterface
interface Greeter { String greet(String who); }
Greeter g = who -> "Hello, " + who + "!"; // the lambda IS the method body
System.out.println(g.greet("Ada")); // Hello, Ada!// Interfaces can extend OTHER interfaces (and even several at once).
interface Animal {
String name();
}
interface Pet extends Animal { // Pet inherits name() and adds owner()
String owner();
}
class Dog implements Pet {
// Must implement BOTH name() (inherited) and owner() (added) — the full chain.
public String name() { return "Rex"; }
public String owner() { return "Sam"; }
}
// A FUNCTIONAL interface has exactly one abstract method, so a lambda can fill it.
@FunctionalInterface
interface Greeter {
String greet(String who); // the single abstract method
}
public class Main {
public static void main(String[] args) {
Pet p = new Dog();
System.out.println(p.name() + " is owned by " + p.owner());
// A lambda IS the implementation of the single method.
Greeter g = who -> "Hello, " + who + "!";
System.out.println(g.greet("Ada"));
}
}Rex is owned by Sam
Hello, Ada!5️⃣ Interface vs Abstract Class
Both let you define methods a subclass must implement, so when do you reach for which? An abstract class is a partial class: it can hold normal fields, a constructor, and finished methods, but a class can extends only one. An interface is a pure contract a class can implement many of.
Analogy: an abstract class is a half-built house — the foundations and framing are already poured (shared code and state), you just finish the rooms. An interface is the building code — a list of requirements any house must meet, but it builds nothing itself.
Common Errors (and the Fix)
- ❌ Forgetting to implement every method:
Car is not abstract and does not override abstract method getSpeed() in Drivable. Fix: provide a body for every method in the interface (and any interfaces it extends), or declare the classabstracttoo. - ❌ Weaker access on an implementation:
attempting to assign weaker access privileges; was public. Interface methods arepublic, so your implementations must bepublic— leaving the keyword off makes them package-private and won't compile. Fix: addpublic. - ❌ The diamond problem with default methods: implement two interfaces that both supply a
defaultmethod of the same name and you getclass inherits unrelated defaults. Fix: override the method in your class and, if you want one parent's version, callInterfaceName.super.methodName(). - ❌ Trying to instantiate an interface:
Drivable d = new Drivable();givesDrivable is abstract; cannot be instantiated. An interface has no bodies to run. Fix: create an implementing class —Drivable d = new Car();— or use an anonymous class / lambda.
📋 Quick Reference — Interface vs Abstract Class
| Feature | Interface | Abstract Class |
|---|---|---|
| Keyword to use it | implements | extends |
| How many at once | Many (implements A, B) | One only |
| Methods | Abstract + default + static | Abstract + concrete |
| Fields | Constants only (public static final) | Any fields, incl. mutable state |
| Constructor | ❌ None | ✅ Yes |
| Can be instantiated? | ❌ No | ❌ No |
| Best for | Unrelated classes sharing a capability | Related classes sharing code + state |
❓ Frequently Asked Questions
What is the difference between an interface and an abstract class?
An interface is a pure contract: it lists method signatures (plus optional default/static methods and constants) and a class can implement many of them. An abstract class is a partial blueprint that can hold normal fields, constructors, and concrete methods, but a class can extend only one. Rule of thumb: start with an interface, and switch to an abstract class only when you need shared mutable state (fields) or a constructor.
Can an interface have method bodies?
Yes, since Java 8. A default method provides a body that every implementing class inherits unless it overrides it, and a static method belongs to the interface itself and is called like Flyable.info(). Java 9 also added private interface methods for sharing logic between default methods. The plain abstract methods still have no body — they are the contract each class must fulfil.
Why does my class fail to compile when I implement an interface?
The two usual causes are: you did not provide a body for every abstract method in the interface (and every interface it extends), or you implemented a method without the public modifier. Interface methods are implicitly public, and Java will not let you implement them with weaker access, so each one must be declared public in your class.
What is a functional interface and why does it matter?
A functional interface has exactly one abstract method, which lets you supply its implementation with a lambda expression instead of a whole class. Standard examples include Runnable, Comparator, and the java.util.function types like Function and Predicate. The optional @FunctionalInterface annotation makes the compiler check that the interface really has just one abstract method.
How does Java handle the diamond problem with default methods?
If a class implements two interfaces that both provide a default method with the same signature, the code will not compile until you resolve the clash. You override the method in your class and, if you want one of the originals, call it explicitly with InterfaceName.super.methodName(). This forces an intentional choice rather than letting Java guess.
🏆 Mini-Challenge — Build a Notifier
Time to write it from scratch. The starter below is just an outline of comments — design the interface and two implementing classes yourself, then loop over them polymorphically. Check your result against the expected output.
public class Main {
// 🎯 MINI-CHALLENGE: a notifier contract
//
// 1. Declare an interface "Notifier" with one method: String send(String message)
// 2. Make a class "EmailNotifier" that implements Notifier
// - send() returns: "Email: " + message
// 3. Make a class "SmsNotifier" that implements Notifier
// - send() returns: "SMS: " + message
// 4. In main, make a Notifier[] holding one of each, loop over it,
// and print send("Hi") for each.
//
// ✅ Expected output:
// Email: Hi
// SMS: Hi
public static void main(String[] args) {
// your code here
}
}🎉 Lesson Complete!
Great work! You can now declare an interface, implement it (even several at once), add default and static methods and constants, use a functional interface with a lambda, build contracts through interface inheritance, and pick an interface or an abstract class with confidence. These are the tools every Java framework — Spring, Android, Jakarta — is built on.
Next up: Exception Handling — catch and recover from errors with try-catch so your program never crashes unexpectedly.
Sign up for free to track which lessons you've completed and get learning reminders.