Interface¶
1. What is an Interface¶
An interface is a "contract" — it defines what a class must do without caring how it does it. Any class that commits to implementing an interface must provide implementations for all the methods the interface requires.
public interface Drawable {
void draw(); // abstract method — no body
}
public class Circle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
public class Square implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}
Drawable d = new Circle();
d.draw(); // Drawing a circle
d = new Square();
d.draw(); // Drawing a square
The code calling d.draw() doesn't need to know whether d is a Circle or a Square — it only needs to know d is Drawable. This is loose coupling — the core strength of interfaces.
2. Why It Matters¶
Interfaces are the foundation of many patterns and features in Java:
- Polymorphism — one variable can point to many different types of objects
- Loose coupling — code depends on abstractions, not on concrete implementations
- Dependency Injection — Spring Boot injects implementations based on interfaces (Phase 05)
- Testing — easy to mock interfaces for isolated unit tests
- Multiple behavior — a class can implement many interfaces (Java doesn't allow extending multiple classes)
Most of the JDK is built on interfaces: List, Map, Set, Comparable, Runnable, Iterable...
3. Declaring an Interface¶
public interface Flyable {
// Constant — implicitly public static final
double MAX_ALTITUDE = 12_000; // equivalent to: public static final double MAX_ALTITUDE = 12_000;
// Abstract method — implicitly public abstract
void fly(); // equivalent to: public abstract void fly();
void land(String location);
}
Interface naming convention
Interfaces are typically named after an ability: Runnable, Comparable, Serializable, Drawable, Flyable. This distinguishes them from classes (nouns: Dog, Circle). Some JDK interfaces use noun names: List, Map, Collection.
4. Implementing an Interface¶
public class Airplane implements Flyable {
@Override
public void fly() {
System.out.println("Airplane takes off");
}
@Override
public void land(String location) {
System.out.println("Landing at " + location);
}
}
A class must implement all abstract methods of the interface — any missing method is a compile error.
5. Multiple Interfaces — the Biggest Advantage¶
A class can only extend one superclass, but it can implement multiple interfaces:
public interface Swimable {
void swim();
}
public interface Flyable {
void fly();
}
public interface Runnable {
void run();
}
// Duck can do all three
public class Duck implements Flyable, Swimable, Runnable {
@Override public void fly() { System.out.println("Duck flies"); }
@Override public void swim() { System.out.println("Duck swims"); }
@Override public void run() { System.out.println("Duck runs"); }
}
Duck duck = new Duck();
Flyable f = duck; f.fly(); // Duck flies
Swimable s = duck; s.swim(); // Duck swims
Runnable r = duck; r.run(); // Duck runs
This lets a single object be viewed from different "angles" depending on context — not possible with extends.
6. Default Methods (Java 8+)¶
Before Java 8, interfaces could only have abstract methods. Since Java 8, interfaces can have default methods — methods with a body that implementing classes can use directly or override.
public interface Greeting {
String getName();
// Default method — has a body, ready to use
default String greet() {
return "Hello, " + getName() + "!";
}
default String greetFormal() {
return "Good day, " + getName() + ". Pleased to meet you.";
}
}
public class User implements Greeting {
private String name;
User(String name) { this.name = name; }
@Override
public String getName() { return name; }
// greet() and greetFormal() are inherited — no override needed
}
public class VIPUser implements Greeting {
private String name;
VIPUser(String name) { this.name = name; }
@Override
public String getName() { return name; }
@Override
public String greet() { // override when different behavior is needed
return "✨ Special greeting, " + getName() + "!";
}
}
User u = new User("Alice");
VIPUser v = new VIPUser("Bob");
System.out.println(u.greet()); // Hello, Alice!
System.out.println(u.greetFormal()); // Good day, Alice. Pleased to meet you.
System.out.println(v.greet()); // ✨ Special greeting, Bob!
Why default methods?
Default methods solve the backward compatibility problem: adding an abstract method to an existing interface would break every class that already implements it. Default methods let you add new methods to an interface without breaking old code. Examples: List.sort(), Map.getOrDefault() were added in Java 8 via default methods.
7. Static Methods in Interfaces (Java 8+)¶
Interfaces can have static methods — utility methods related to the interface, not tied to any instance.
public interface MathUtils {
static int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
}
System.out.println(MathUtils.clamp(150, 0, 100)); // 100
System.out.println(MathUtils.clamp(-5, 0, 100)); // 0
8. Private Methods in Interfaces (Java 9+)¶
Interfaces can have private methods — used to share code between default methods without exposing them externally.
public interface Logger {
default void logInfo(String msg) {
log("INFO", msg);
}
default void logError(String msg) {
log("ERROR", msg);
}
// Private — only visible within this interface, not to implementing classes
private void log(String level, String msg) {
System.out.printf("[%s] %s%n", level, msg);
}
}
9. Interface vs Abstract Class¶
| Interface | Abstract Class | |
|---|---|---|
| Keyword | implements |
extends |
| How many | Multiple interfaces | Only one class |
| Constructor | No | Yes |
| Fields | public static final only |
Any kind |
| Methods | abstract, default, static, private | Any kind |
| Instance state | None | Yes |
| Use when | Defining capabilities (can-do) | Sharing common code + is-a |
Decision rule:
Need to share code (fields, constructors, concrete methods)?
→ Abstract Class
Need multiple "sources" of behavior? Or defining a capability, not an identity?
→ Interface
Not sure?
→ Interface — more flexible, fewer constraints
10. Important JDK Interfaces¶
Comparable\<T> — natural ordering¶
public class Student implements Comparable<Student> {
String name;
double gpa;
Student(String name, double gpa) {
this.name = name;
this.gpa = gpa;
}
@Override
public int compareTo(Student other) {
return Double.compare(other.gpa, this.gpa); // sort descending by GPA
}
}
List<Student> list = new ArrayList<>();
list.add(new Student("Alice", 8.5));
list.add(new Student("Bob", 9.2));
list.add(new Student("Carol", 7.8));
Collections.sort(list); // uses compareTo()
list.forEach(s -> System.out.println(s.name + ": " + s.gpa));
// Bob: 9.2
// Alice: 8.5
// Carol: 7.8
Iterable\<T> — for-each support¶
public class NumberRange implements Iterable<Integer> {
private final int start, end;
NumberRange(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public java.util.Iterator<Integer> iterator() {
return new java.util.Iterator<>() {
int current = start;
@Override public boolean hasNext() { return current <= end; }
@Override public Integer next() { return current++; }
};
}
}
AutoCloseable — try-with-resources¶
public class DatabaseConnection implements AutoCloseable {
DatabaseConnection() { System.out.println("Opening DB connection"); }
public void query(String sql) { System.out.println("Query: " + sql); }
@Override
public void close() { System.out.println("Closing DB connection"); }
}
try (DatabaseConnection conn = new DatabaseConnection()) {
conn.query("SELECT * FROM users");
} // close() called automatically
// Output:
// Opening DB connection
// Query: SELECT * FROM users
// Closing DB connection
11. Functional Interface — Brief Introduction¶
A functional interface has exactly one abstract method. It is the foundation for Lambda expressions (covered in depth in Phase 02).
@FunctionalInterface // optional annotation — compiler enforces the single-abstract-method rule
public interface Calculator {
int calculate(int a, int b);
}
// Traditional — anonymous class
Calculator add = new Calculator() {
@Override
public int calculate(int a, int b) { return a + b; }
};
// Modern — Lambda (Phase 02)
Calculator add = (a, b) -> a + b;
Calculator mul = (a, b) -> a * b;
System.out.println(add.calculate(3, 4)); // 7
System.out.println(mul.calculate(3, 4)); // 12
Built-in functional interfaces in the JDK: Runnable, Callable, Comparator, Predicate<T>, Function<T,R>, Consumer<T>, Supplier<T> — covered in detail in Phase 02 alongside the Stream API.
12. Full Example¶
Verified
Full compilable source: InterfaceDemo.java
- The default method
describe()is defined once in the interface and inherited by all implementing classes (Circle,Rectangle) with no override needed — this is the power of default methods. Rectangle implements Comparable<Rectangle>enablesCollections.sort()without passing a separateComparator— Java knows how to compare twoRectangleinstances viacompareTo().
Output:
Circle | Area: 78.54 | Perimeter: 31.42
Rectangle | Area: 24.00 | Perimeter: 20.00
Circle | Area: 28.27 | Perimeter: 18.85
Before: Circle | Area: 78.54 | Perimeter: 31.42
After: Circle | Area: 314.16 | Perimeter: 62.83
1x2 = 2
3x4 = 12
5x6 = 30
13. Common Mistakes¶
Mistake 1 — Forgetting to implement a method¶
interface Animal {
void eat();
void sleep();
}
// ❌ compile error: Dog hasn't implemented sleep()
class Dog implements Animal {
@Override public void eat() { System.out.println("Dog eats"); }
// sleep() forgotten — compiler catches it immediately
}
// ✅ Implement all methods
class Dog implements Animal {
@Override public void eat() { System.out.println("Dog eats"); }
@Override public void sleep() { System.out.println("Dog sleeps"); }
}
Mistake 2 — Conflict when two interfaces share a default method name¶
interface A { default void hello() { System.out.println("A"); } }
interface B { default void hello() { System.out.println("B"); } }
// ❌ compile error: class must specify which default method to use
class C implements A, B { }
// ✅ Override and specify explicitly
class C implements A, B {
@Override
public void hello() {
A.super.hello(); // call A's default method
}
}
Mistake 3 — Expecting instance state in an interface¶
// ❌ Interface fields are implicitly public static final — not instance fields
interface Counter {
int count = 0; // this is a constant shared by all, NOT per-instance state
void increment();
}
// ✅ State belongs in the implementing class
class SimpleCounter implements Counter {
private int count = 0; // instance field in the class
@Override
public void increment() { count++; }
public int getCount() { return count; }
}
Mistake 4 — Using interface when you need a constructor or shared state¶
// ❌ Interfaces have no constructors — can't share initialization logic
interface Vehicle {
// Can't write: Vehicle(String brand) { this.brand = brand; }
}
// ✅ Use an abstract class when you need a constructor or shared instance fields
abstract class Vehicle {
protected final String brand;
Vehicle(String brand) { this.brand = brand; }
abstract void start();
}
14. Interview Questions¶
Q1: What is the difference between Interface and Abstract Class? When do you choose each?
Interface defines capabilities (can-do) — a class can implement many interfaces, no constructor, no instance state, fields are implicitly
public static final. Abstract class shares code and state between subclasses — has constructors, instance fields, and concrete methods, but can only be extended once. Choose interface when defining a contract or behavior that multiple unrelated classes should share. Choose abstract class when there is genuinely shared code or state to reuse and the subclasses have a clear is-a relationship.
Q2: What problem do default methods solve?
Default methods (Java 8+) solve backward compatibility: before Java 8, adding an abstract method to an existing interface would break every class that implemented it. Default methods let you add new behavior to an interface without breaking any existing implementations — old classes inherit the default, new classes can override it. Examples:
List.sort(),Map.forEach()were added to Java 8 this way.
Q3: What is a functional interface and why does it matter?
A functional interface has exactly one abstract method (it may have multiple default or static methods). It is the foundation for Lambda expressions — instead of anonymous class boilerplate, behavior can be passed as a concise expression. The
@FunctionalInterfaceannotation is optional but lets the compiler enforce the constraint. Examples:Runnable,Comparator,Predicate,Functionare all functional interfaces.
Q4: If two interfaces have a default method with the same name, what must the implementing class do?
The class must override the method and explicitly specify which interface's default to use via
InterfaceName.super.methodName(). Without the override, the compiler raises an error because the ambiguity must be resolved explicitly. This is the "explicit wins" rule — conflicts must be resolved in code, not left to chance.
Q5: Why does Spring Boot inject interfaces instead of concrete classes?
This follows the Dependency Inversion principle (the D in SOLID): code should depend on abstractions, not on concrete implementations. When Spring injects
UserService(interface) rather thanUserServiceImpl(class), the calling code is not locked into one implementation — easy to swap implementations, easy to mock in tests, open to extension without modifying existing code. This is also why Spring AOP (transactions, security, caching) works: Spring creates a proxy that implements the same interface and wraps around the real implementation transparently.
15. References¶
| Resource | Content |
|---|---|
| JLS §9 — Interfaces | Official specification |
| Oracle Tutorial — Interfaces | Official guide |
| Baeldung — Java Interface | Practical walkthrough |
| Effective Java — Joshua Bloch | Item 20: Prefer interfaces to abstract classes · Item 21: Design interfaces for posterity |