Introduction
Default methods (Java 8+) allow interfaces to have methods with implementation. Also called defender methods or virtual extension methods.
Basic Syntax
interface InterfaceName {
// Default method with body
default returnType methodName() {
// Method implementation
}
}
Simple Example
interface Vehicle {
// Abstract method
void start();
// Default method with implementation
default void stop() {
System.out.println("Vehicle stopped");
}
}
class Car implements Vehicle {
@Override
public void start() {
System.out.println("Car started");
}
// Can use default stop() or override it
}
public class Main {
public static void main(String[] args) {
Vehicle car = new Car();
car.start(); // Car started
car.stop(); // Vehicle stopped (using default)
}
}
Why Default Methods?
Problem before Java 8:
- Adding method to interface breaks all implementations
- Cannot evolve interfaces without breaking code
Solution:
- Default methods provide implementation
- Old classes work without changes
- New classes can override if needed
Key Features
- Keyword:
default - Has body (implementation)
- Can be overridden by implementing class
- Backward compatible - old code works
- Optional override - use default or override
Using Default Method
interface Printable {
// Abstract method - must implement
void print();
// Default method - optional to override
default void display() {
System.out.println("Displaying...");
}
}
// Class using default method
class Document1 implements Printable {
@Override
public void print() {
System.out.println("Printing document1");
}
// Using default display()
}
// Class overriding default method
class Document2 implements Printable {
@Override
public void print() {
System.out.println("Printing document2");
}
@Override
public void display() {
System.out.println("Custom display for document2");
}
}
public class Main {
public static void main(String[] args) {
Printable d1 = new Document1();
d1.display(); // Displaying... (default)
Printable d2 = new Document2();
d2.display(); // Custom display for document2 (overridden)
}
}
Backward Compatibility Example
// Original interface
interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
// Old implementation (already exists)
class BasicCalculator implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
}
// NOW: Add new method to interface
interface CalculatorV2 {
int add(int a, int b);
int subtract(int a, int b);
// New default method - doesn't break BasicCalculator!
default int multiply(int a, int b) {
return a * b;
}
}
public class Main {
public static void main(String[] args) {
// Old class still works with new interface
Calculator calc = new BasicCalculator();
System.out.println(calc.add(10, 5)); // 15
System.out.println(calc.subtract(10, 5)); // 5
// calc.multiply(10, 5); // Also available if interface updated
}
}
Calling Other Interface Methods
interface MathOperations {
int add(int a, int b);
int subtract(int a, int b);
// Default method calling abstract methods
default int addAll(int... numbers) {
int sum = 0;
for (int num : numbers) {
sum = add(sum, num);
}
return sum;
}
// Default method calling another default method
default void printSum(int... numbers) {
System.out.println("Sum: " + addAll(numbers));
}
}
class Calculator implements MathOperations {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
}
public class Main {
public static void main(String[] args) {
MathOperations calc = new Calculator();
System.out.println(calc.addAll(1, 2, 3, 4, 5)); // 15
calc.printSum(10, 20, 30); // Sum: 60
}
}
Complete Example: List Interface Evolution
interface MyList<T> {
void add(T item);
T get(int index);
int size();
// Default method - new functionality without breaking existing code
default boolean isEmpty() {
return size() == 0;
}
default void addAll(T... items) {
for (T item : items) {
add(item);
}
}
default void printAll() {
System.out.println("List contents:");
for (int i = 0; i < size(); i++) {
System.out.println(get(i));
}
}
default boolean contains(T item) {
for (int i = 0; i < size(); i++) {
if (get(i).equals(item)) {
return true;
}
}
return false;
}
}
class ArrayList<T> implements MyList<T> {
private Object[] array = new Object[10];
private int count = 0;
@Override
public void add(T item) {
array[count++] = item;
}
@Override
@SuppressWarnings("unchecked")
public T get(int index) {
return (T) array[index];
}
@Override
public int size() {
return count;
}
// All default methods automatically available
}
public class Main {
public static void main(String[] args) {
MyList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.addAll("Cherry", "Date"); // Using default method
System.out.println("Empty: " + list.isEmpty()); // false
System.out.println("Contains Banana: " + list.contains("Banana")); // true
list.printAll(); // Using default method
}
}
Overriding Default Method
interface Logger {
default void log(String message) {
System.out.println("LOG: " + message);
}
}
class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("FILE: " + message);
}
}
class DatabaseLogger implements Logger {
@Override
public void log(String message) {
System.out.println("DB: " + message);
}
}
class ConsoleLogger implements Logger {
// Using default log method
}
public class Main {
public static void main(String[] args) {
Logger fileLog = new FileLogger();
fileLog.log("Error occurred"); // FILE: Error occurred
Logger dbLog = new DatabaseLogger();
dbLog.log("User logged in"); // DB: User logged in
Logger consoleLog = new ConsoleLogger();
consoleLog.log("Info message"); // LOG: Info message
}
}
Real-World Example: Shape Interface
interface Shape {
double area();
double perimeter();
// Default method for display
default void display() {
System.out.println("Shape Information:");
System.out.println("Area: " + area());
System.out.println("Perimeter: " + perimeter());
}
// Default method for comparison
default boolean isLargerThan(Shape other) {
return this.area() > other.area();
}
// Default method for scaling
default void printScaled(double factor) {
System.out.println("Original area: " + area());
System.out.println("Scaled area: " + (area() * factor * factor));
}
}
class Circle implements Shape {
private double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
// Override display for custom format
@Override
public void display() {
System.out.println("Circle with radius: " + radius);
System.out.println("Area: " + area());
System.out.println("Circumference: " + perimeter());
}
}
class Rectangle implements Shape {
private double length, width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
@Override
public double perimeter() {
return 2 * (length + width);
}
// Using default display method
}
public class Main {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
circle.display();
System.out.println();
rectangle.display(); // Using default
System.out.println();
System.out.println("Circle larger: " + circle.isLargerThan(rectangle));
rectangle.printScaled(2);
}
}
Default Methods with Fields
Note: Interfaces cannot have instance fields, but default methods can access abstract methods.
interface Identifiable {
int getId(); // Abstract getter
void setId(int id); // Abstract setter
// Default method using abstract methods
default void printId() {
System.out.println("ID: " + getId());
}
default boolean hasValidId() {
return getId() > 0;
}
}
class User implements Identifiable {
private int id;
@Override
public int getId() {
return id;
}
@Override
public void setId(int id) {
this.id = id;
}
}
public class Main {
public static void main(String[] args) {
User user = new User();
user.setId(101);
user.printId(); // ID: 101
System.out.println("Valid: " + user.hasValidId()); // true
}
}
Default vs Static Methods
| Feature | Default Method | Static Method |
|---|---|---|
| Keyword | default | static |
| Belongs to | Object | Interface |
| Access | Through object | Through interface name |
| Override | Yes | No |
| Call other methods | Abstract + default | Only static |
| Use case | Instance behavior | Utility methods |
interface Example {
// Default - belongs to object
default void defaultMethod() {
System.out.println("Default");
}
// Static - belongs to interface
static void staticMethod() {
System.out.println("Static");
}
}
class MyClass implements Example { }
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.defaultMethod(); // ✓ OK
Example.staticMethod(); // ✓ OK
// obj.staticMethod(); // ✗ Error
}
}
Benefits of Default Methods
- Backward compatibility - old code works
- Interface evolution - add methods without breaking
- Optional functionality - provide default behavior
- Code reuse - shared implementation
- Flexibility - override when needed
Common Use Cases
1. Iterator:
interface Iterator<T> {
boolean hasNext();
T next();
default void remove() {
throw new UnsupportedOperationException();
}
}
2. Comparator:
interface Comparator<T> {
int compare(T o1, T o2);
default Comparator<T> reversed() {
return (o1, o2) -> compare(o2, o1);
}
}
3. Collection:
interface Collection<T> {
boolean add(T element);
default boolean addAll(Collection<T> c) {
for (T element : c) {
add(element);
}
return true;
}
}
Quick Reference
interface MyInterface {
// Abstract method
void abstractMethod();
// Default method
default void defaultMethod() {
System.out.println("Default implementation");
}
// Default calling abstract
default void combinedMethod() {
abstractMethod(); // Call abstract
System.out.println("Additional logic");
}
}
// Using default
class Class1 implements MyInterface {
public void abstractMethod() { }
// Uses default methods as-is
}
// Overriding default
class Class2 implements MyInterface {
public void abstractMethod() { }
@Override
public void defaultMethod() {
System.out.println("Custom implementation");
}
}
Exam Tips
Remember:
- Default methods since Java 8
- Use default keyword
- Have method body
- Can be overridden
- Optional override - not mandatory
- Purpose: backward compatibility
- Can call abstract and default methods
- Cannot be static and default together
- Used for interface evolution
- Implementing class inherits default behavior
Common Questions:
- What are default methods?
- When were they introduced?
- Why use default methods?
- Can default methods be overridden?
- Default vs abstract methods?
- Default vs static methods?
- Benefits of default methods?
- Backward compatibility example?
- Can default method call abstract method?
- Is overriding default method mandatory?