What Are Interfaces and Abstract Classes?
Interfaces and abstract classes are two mechanisms Java provides for achieving abstraction—the ability to define what something does without specifying how it does it. Both allow you to create contracts that other classes must fulfill, but they serve different purposes and have different capabilities. Understanding when to use each is crucial for good object-oriented design.
At their core, both interfaces and abstract classes let you define method signatures without implementations (abstract methods), and both prevent direct instantiation—you can't create an object of an interface or abstract class directly. However, they differ fundamentally in their intent: interfaces define capabilities (what an object can do), while abstract classes define identities (what an object is).
This distinction matters because it affects how classes relate to each other and how flexible your design becomes. A class can implement many interfaces (gaining multiple capabilities), but can only extend one abstract class (having one identity). This single inheritance limitation for classes but multiple inheritance for interfaces is Java's solution to the "diamond problem" that plagued languages like C++.
Historical Context: The Evolution of Interfaces
Java's interface concept was revolutionary when the language was introduced in 1995. While abstract classes existed in earlier languages like C++, Java's interfaces provided a cleaner way to achieve multiple inheritance of type without the complexity and ambiguity of multiple inheritance of implementation.
Interfaces remained purely abstract (no implementation allowed) until Java 8 (2014), which introduced default methods and static methods in interfaces. This was a major evolution driven by the need to add methods to existing interfaces (like adding forEach() to Iterable) without breaking millions of existing implementations. Java 9 added private methods in interfaces, allowing code reuse within default methods. These changes blurred the line between interfaces and abstract classes, making the choice between them more nuanced.
The Big Picture: Where They Fit in Java Design
Interfaces and abstract classes are foundational to nearly every Java framework and library:
- Java Collections Framework: Uses interfaces (
List,Set,Map) for contracts and abstract classes (AbstractList,AbstractSet) for partial implementations - JDBC: Database connectivity through interfaces like
Connection,Statement,ResultSet - Spring Framework: Heavy use of interfaces for dependency injection and loose coupling
- Java I/O: Abstract classes like
InputStreamandOutputStreamdefine the stream hierarchy
/*
* Interfaces vs Abstract Classes: The Decision Tree
* ==================================================
*
* Need to define behavior for UNRELATED classes?
* │
* ├── YES → Use INTERFACE
* │ Example: Comparable, Serializable, Runnable
* │ (A String, Integer, and your custom class can all be Comparable)
* │
* └── NO → Classes are RELATED (share "is-a" relationship)?
* │
* ├── YES → Need to share CODE (fields, constructors, methods)?
* │ │
* │ ├── YES → Use ABSTRACT CLASS
* │ │ Example: Animal → Dog, Cat (share name, age, eat())
* │ │
* │ └── NO → Use INTERFACE
* │ Example: Shape interface with area(), perimeter()
* │
* └── NO → Rethink your design!
*/
Real-World Analogies
Interface = Job Description / Contract / Certification
- A job posting says: "Must be able to type, answer phones, file documents"
- It doesn't tell you HOW to do these things, just that you MUST be able to do them
- A person can have multiple certifications (implements multiple interfaces)
- Very different people (classes) can share the same certification
Abstract Class = Partial Template / Family Heritage
- Think of a house blueprint that has some rooms finished (kitchen with cabinets) but others are blank (you decide bedroom layout)
- Or family traits: all members of the Smith family share certain characteristics (last name, family history) but have individual traits too
- You can only belong to ONE family (single inheritance)
| Feature | Interface | Abstract Class |
|---|---|---|
| Purpose | Defines a CONTRACT (what to do) | Provides a TEMPLATE (what you are) |
| Inheritance | Can implement MANY interfaces | Can extend only ONE abstract class |
| Methods (pre-Java 8) | Only abstract (no body) | Both abstract and concrete |
| Methods (Java 8+) | Abstract, default, static, private (9+) | Any type of method |
| Fields | Only public static final constants |
Any type of field |
| Constructors | Not allowed | Allowed (called via super) |
| Keyword | implements |
extends |
| Access Modifiers | Methods implicitly public | Any access modifier |
Interfaces In Depth
An interface is a completely abstract type that defines a set of methods (and constants) that implementing classes must provide. Interfaces establish a contract: "any class that implements me guarantees to have these capabilities."
Interface Syntax Evolution
// ═══════════════════════════════════════════════════════════════════════════
// CLASSIC INTERFACE (Pre-Java 8) - Pure abstraction
// ═══════════════════════════════════════════════════════════════════════════
public interface Drawable {
// Constants (implicitly public static final)
int DEFAULT_COLOR = 0x000000; // Black
// Abstract methods (implicitly public abstract)
void draw();
void resize(int width, int height);
}
// ═══════════════════════════════════════════════════════════════════════════
// MODERN INTERFACE (Java 8+) - With default and static methods
// ═══════════════════════════════════════════════════════════════════════════
public interface Drawable {
// Constants
int DEFAULT_COLOR = 0x000000;
// Abstract method - implementors MUST provide
void draw();
// Default method (Java 8+) - provides default implementation
// Implementors CAN override, but don't have to
default void display() {
System.out.println("Displaying drawable...");
draw();
}
// Default method with common behavior
default void hide() {
System.out.println("Hiding drawable");
}
// Static method (Java 8+) - utility method on the interface itself
static Drawable createEmpty() {
return () -> {}; // Lambda for functional interface
}
// Static utility method
static boolean isVisible(Drawable d) {
return d != null;
}
}
// ═══════════════════════════════════════════════════════════════════════════
// JAVA 9+ INTERFACE - With private methods for code reuse
// ═══════════════════════════════════════════════════════════════════════════
public interface Logger {
void log(String message);
default void logInfo(String message) {
log(formatMessage("INFO", message)); // Uses private method
}
default void logError(String message) {
log(formatMessage("ERROR", message)); // Uses private method
}
default void logWarning(String message) {
log(formatMessage("WARNING", message));
}
// Private method (Java 9+) - for code reuse within default methods
// NOT visible to implementing classes
private String formatMessage(String level, String message) {
return "[" + level + "] " + java.time.LocalDateTime.now() + ": " + message;
}
// Private static method (Java 9+)
private static String getTimestamp() {
return java.time.Instant.now().toString();
}
}
Multiple Interface Implementation
// A class can implement MULTIPLE interfaces - this is Java's answer to
// multiple inheritance (of type, not implementation)
interface Flyable {
void fly();
default int getMaxAltitude() { return 10000; }
}
interface Swimmable {
void swim();
default int getMaxDepth() { return 100; }
}
interface Walkable {
void walk();
}
// Duck can fly, swim, AND walk!
public class Duck implements Flyable, Swimmable, Walkable {
private String name;
public Duck(String name) {
this.name = name;
}
@Override
public void fly() {
System.out.println(name + " is flying");
}
@Override
public void swim() {
System.out.println(name + " is swimming");
}
@Override
public void walk() {
System.out.println(name + " is walking");
}
}
// Penguin can swim and walk, but NOT fly
public class Penguin implements Swimmable, Walkable {
@Override
public void swim() {
System.out.println("Penguin swimming gracefully");
}
@Override
public void walk() {
System.out.println("Penguin waddling");
}
@Override
public int getMaxDepth() {
return 500; // Penguins can dive deep!
}
}
// Airplane can fly but is NOT an animal
public class Airplane implements Flyable {
@Override
public void fly() {
System.out.println("Airplane taking off");
}
@Override
public int getMaxAltitude() {
return 40000; // Commercial jets fly high
}
}
// The beauty: process ANY flyable thing uniformly
public void takeOff(Flyable f) {
f.fly(); // Works with Duck, Airplane, or any future Flyable
}
Functional Interfaces and Lambdas
// A functional interface has exactly ONE abstract method
// Can be used with lambda expressions
@FunctionalInterface // Optional but recommended - compiler enforces single abstract method
public interface Processor<T> {
T process(T input);
// Default and static methods don't count against "single abstract method" rule
default Processor<T> andThen(Processor<T> after) {
return input -> after.process(this.process(input));
}
}
// Usage with lambda
Processor<String> trimmer = s -> s.trim();
Processor<String> upper = s -> s.toUpperCase();
Processor<String> combined = trimmer.andThen(upper);
String result = combined.process(" hello world "); // "HELLO WORLD"
// Built-in functional interfaces in java.util.function:
// - Function<T,R> : T -> R
// - Consumer<T> : T -> void
// - Supplier<T> : () -> T
// - Predicate<T> : T -> boolean
// - BiFunction<T,U,R>: (T, U) -> R
- Defining capabilities: Comparable, Serializable, Runnable
- Unrelated classes need same behavior: Both Duck and Airplane can Fly
- Multiple inheritance of type: Duck implements Flyable, Swimmable, Walkable
- API contracts: List, Set, Map define what collections can do
- Dependency injection: Program to interface, inject implementation
- Callback/strategy patterns: Pass behavior as parameter
Abstract Classes In Depth
An abstract class is a class that cannot be instantiated and may contain both abstract methods (without implementation) and concrete methods (with implementation). Abstract classes serve as templates for related classes, providing shared code while requiring subclasses to implement specific behaviors.
Abstract Class Syntax
public abstract class Animal {
// ═══════════════════════════════════════════════════════════════════════
// INSTANCE FIELDS - Can have state (interfaces cannot)
// ═══════════════════════════════════════════════════════════════════════
protected String name;
protected int age;
private String id; // Can have private fields
// ═══════════════════════════════════════════════════════════════════════
// CONSTRUCTORS - Can have constructors (interfaces cannot)
// ═══════════════════════════════════════════════════════════════════════
public Animal(String name, int age) {
this.name = name;
this.age = age;
this.id = generateId();
}
// Overloaded constructor
public Animal(String name) {
this(name, 0); // Constructor chaining
}
// ═══════════════════════════════════════════════════════════════════════
// ABSTRACT METHODS - Must be implemented by concrete subclasses
// ═══════════════════════════════════════════════════════════════════════
public abstract void makeSound(); // Each animal sounds different
public abstract void move(); // Each animal moves differently
protected abstract String getSpecies(); // Can have non-public abstract methods
// ═══════════════════════════════════════════════════════════════════════
// CONCRETE METHODS - Shared implementation inherited by all subclasses
// ═══════════════════════════════════════════════════════════════════════
public void eat() {
System.out.println(name + " is eating");
}
public void sleep() {
System.out.println(name + " is sleeping for 8 hours");
}
public void displayInfo() {
System.out.println("Animal: " + name + ", Age: " + age + ", Species: " + getSpecies());
}
// ═══════════════════════════════════════════════════════════════════════
// FINAL METHODS - Cannot be overridden by subclasses
// ═══════════════════════════════════════════════════════════════════════
public final String getId() {
return id; // ID generation is fixed, subclasses can't change it
}
// ═══════════════════════════════════════════════════════════════════════
// PRIVATE METHODS - Internal helpers not visible to subclasses
// ═══════════════════════════════════════════════════════════════════════
private String generateId() {
return "ANIMAL-" + System.currentTimeMillis();
}
// ═══════════════════════════════════════════════════════════════════════
// STATIC METHODS - Belong to class, not instances
// ═══════════════════════════════════════════════════════════════════════
public static boolean isAdult(Animal animal) {
return animal.age >= 2;
}
// Getters
public String getName() { return name; }
public int getAge() { return age; }
}
// ═══════════════════════════════════════════════════════════════════════════
// CONCRETE SUBCLASS - Implements all abstract methods
// ═══════════════════════════════════════════════════════════════════════════
public class Dog extends Animal {
private String breed;
public Dog(String name, int age, String breed) {
super(name, age); // MUST call parent constructor
this.breed = breed;
}
@Override
public void makeSound() {
System.out.println(name + " barks: Woof! Woof!");
}
@Override
public void move() {
System.out.println(name + " runs on four legs");
}
@Override
protected String getSpecies() {
return "Canis familiaris";
}
// Dog-specific method
public void fetch() {
System.out.println(name + " fetches the ball!");
}
}
public class Bird extends Animal {
private double wingspan;
public Bird(String name, int age, double wingspan) {
super(name, age);
this.wingspan = wingspan;
}
@Override
public void makeSound() {
System.out.println(name + " chirps: Tweet tweet!");
}
@Override
public void move() {
System.out.println(name + " flies with " + wingspan + "m wingspan");
}
@Override
protected String getSpecies() {
return "Aves";
}
@Override
public void sleep() {
// Override to customize
System.out.println(name + " sleeps in a nest");
}
}
Template Method Pattern
// Abstract classes are perfect for the Template Method pattern
// Define the skeleton of an algorithm, let subclasses fill in the steps
public abstract class DataProcessor {
// TEMPLATE METHOD - final so subclasses can't change the algorithm
public final void process() {
openConnection();
readData();
processData(); // Abstract - subclasses customize
writeResults(); // Abstract - subclasses customize
closeConnection();
}
// Concrete methods - same for all subclasses
private void openConnection() {
System.out.println("Opening connection...");
}
private void closeConnection() {
System.out.println("Closing connection...");
}
protected void readData() {
System.out.println("Reading data...");
}
// Abstract methods - subclasses MUST implement
protected abstract void processData();
protected abstract void writeResults();
}
public class CSVProcessor extends DataProcessor {
@Override
protected void processData() {
System.out.println("Parsing CSV rows...");
}
@Override
protected void writeResults() {
System.out.println("Writing CSV output...");
}
}
public class JSONProcessor extends DataProcessor {
@Override
protected void processData() {
System.out.println("Parsing JSON objects...");
}
@Override
protected void writeResults() {
System.out.println("Writing JSON output...");
}
}
- Sharing code: Common implementation among related classes
- Non-static/non-final fields: Need instance variables
- Constructors: Complex initialization logic
- Template Method pattern: Define algorithm skeleton
- Protected members: Need package/subclass access
- "Is-a" relationship: Dog IS-A Animal, not just "can do animal things"
Combining Interfaces and Abstract Classes
The most powerful designs often combine interfaces and abstract classes. This is exactly what the Java Collections Framework does.
/*
* Real-World Pattern: Collections Framework Design
* =================================================
*
* INTERFACE (contract) ABSTRACT CLASS (partial impl) CONCRETE (full impl)
* ================== ========================= ===================
*
* List<E> AbstractList<E> ArrayList<E>
* │ │ LinkedList<E>
* │ │
* └──────────────────────────────┴──────────────────────────────────┘
*
* Why this design?
* - Interface: defines what ALL lists must do (add, get, remove, etc.)
* - Abstract class: provides common implementation (indexOf, equals, hashCode)
* - Concrete classes: provide specific storage mechanisms
*/
// Let's recreate this pattern for a messaging system
// STEP 1: Define the capability (interface)
public interface MessageSender {
void send(String recipient, String message);
boolean isAvailable();
default void sendIfAvailable(String recipient, String message) {
if (isAvailable()) {
send(recipient, message);
} else {
System.out.println("Service unavailable");
}
}
}
// STEP 2: Provide shared implementation (abstract class)
public abstract class AbstractMessageSender implements MessageSender {
protected String senderName;
protected List<String> sentMessages = new ArrayList<>();
public AbstractMessageSender(String senderName) {
this.senderName = senderName;
}
// Template method
@Override
public final void send(String recipient, String message) {
validateRecipient(recipient);
String formattedMessage = formatMessage(message);
doSend(recipient, formattedMessage); // Abstract - subclass provides
logMessage(recipient, formattedMessage);
}
// Shared implementation
protected void validateRecipient(String recipient) {
if (recipient == null || recipient.isEmpty()) {
throw new IllegalArgumentException("Invalid recipient");
}
}
protected String formatMessage(String message) {
return "[" + senderName + "] " + message;
}
protected void logMessage(String recipient, String message) {
sentMessages.add(recipient + ": " + message);
}
// Abstract method - subclasses implement actual sending
protected abstract void doSend(String recipient, String message);
public List<String> getSentMessages() {
return new ArrayList<>(sentMessages); // Defensive copy
}
}
// STEP 3: Concrete implementations
public class EmailSender extends AbstractMessageSender {
private String smtpServer;
public EmailSender(String senderName, String smtpServer) {
super(senderName);
this.smtpServer = smtpServer;
}
@Override
protected void doSend(String recipient, String message) {
System.out.println("Sending email via " + smtpServer + " to " + recipient);
System.out.println("Content: " + message);
}
@Override
public boolean isAvailable() {
// Check SMTP server connectivity
return true;
}
@Override
protected void validateRecipient(String recipient) {
super.validateRecipient(recipient);
if (!recipient.contains("@")) {
throw new IllegalArgumentException("Invalid email address");
}
}
}
public class SMSSender extends AbstractMessageSender {
private String apiKey;
public SMSSender(String senderName, String apiKey) {
super(senderName);
this.apiKey = apiKey;
}
@Override
protected void doSend(String recipient, String message) {
System.out.println("Sending SMS to " + recipient);
System.out.println("Content: " + message.substring(0, Math.min(message.length(), 160)));
}
@Override
public boolean isAvailable() {
// Check SMS API availability
return apiKey != null;
}
}
// Usage - polymorphism in action
MessageSender email = new EmailSender("System", "smtp.example.com");
MessageSender sms = new SMSSender("System", "api-key-123");
// Same code works with both!
for (MessageSender sender : Arrays.asList(email, sms)) {
sender.sendIfAvailable("user@example.com", "Hello!");
}
The Diamond Problem and Default Methods
Java 8's default methods introduced potential ambiguity when a class implements multiple interfaces with the same default method. Java has specific rules to resolve this.
interface A {
default void hello() {
System.out.println("Hello from A");
}
}
interface B {
default void hello() {
System.out.println("Hello from B");
}
}
// Class implements both - which hello() is used?
public class C implements A, B {
// MUST override to resolve ambiguity
@Override
public void hello() {
// Option 1: Provide your own implementation
System.out.println("Hello from C");
// Option 2: Explicitly choose one interface's version
A.super.hello(); // Calls A's default
// Option 3: Call both
A.super.hello();
B.super.hello();
}
}
/*
* Diamond Problem Resolution Rules:
* =================================
*
* 1. Class wins over interface
* - If a superclass provides a method, it beats interface defaults
*
* 2. Sub-interface wins over super-interface
* - More specific interface wins
*
* 3. No winner = Must override
* - If rules 1 and 2 don't resolve, class must explicitly override
*/
// Example: Class wins over interface
class Parent {
public void hello() {
System.out.println("Hello from Parent class");
}
}
class Child extends Parent implements A {
// No override needed - Parent.hello() wins over A.hello()
}
new Child().hello(); // "Hello from Parent class"
Best Practices
Interface Design Guidelines
- Keep interfaces small and focused: Interface Segregation Principle
- Use
@FunctionalInterface: For single-method interfaces used with lambdas - Use default methods sparingly: For optional methods or gradual API evolution
- Don't use interfaces for constants: Use a final class or enum instead
- Document contracts clearly: What preconditions, postconditions, invariants?
Abstract Class Design Guidelines
- Design for inheritance: Document what can/should be overridden
- Use
finalfor non-overridable methods: Especially template methods - Prefer protected over public: For internal API used by subclasses
- Call abstract methods from constructor carefully: Object isn't fully initialized
- Keep hierarchies shallow: Prefer composition over deep inheritance
// ❌ BAD: Interface used for constants (anti-pattern)
public interface Constants {
int MAX_SIZE = 100;
String PREFIX = "APP_";
}
class MyClass implements Constants { ... } // Pollutes class hierarchy
// ✅ GOOD: Use final class for constants
public final class Constants {
public static final int MAX_SIZE = 100;
public static final String PREFIX = "APP_";
private Constants() {} // Prevent instantiation
}
// ❌ BAD: Calling abstract method from constructor
public abstract class Dangerous {
public Dangerous() {
initialize(); // DANGER: Calls subclass method before subclass is initialized!
}
protected abstract void initialize();
}
// ✅ GOOD: Use factory method or separate initialization
public abstract class Safe {
public static Safe create() {
Safe instance = new ConcreteImplementation();
instance.initialize();
return instance;
}
protected abstract void initialize();
}
Common Pitfalls
// ❌ ERROR: Cannot instantiate
Animal animal = new Animal("Rex", 5); // Abstract class
Drawable d = new Drawable(); // Interface
// ✅ CORRECT: Instantiate concrete implementations
Animal dog = new Dog("Rex", 5, "Labrador");
Drawable circle = new Circle();
// ✅ CORRECT: Anonymous class (creates unnamed subclass)
Drawable anonymous = new Drawable() {
@Override
public void draw() {
System.out.println("Drawing");
}
};
public abstract class Vehicle {
protected String brand;
public Vehicle(String brand) { // No default constructor!
this.brand = brand;
}
}
// ❌ ERROR: No default constructor in Vehicle
public class Car extends Vehicle {
public Car() {
// Implicit super() call fails - Vehicle has no no-arg constructor
}
}
// ✅ CORRECT: Explicitly call super with required argument
public class Car extends Vehicle {
public Car(String brand) {
super(brand); // Must be first statement
}
}
public abstract class Shape {
public abstract double area();
public abstract double perimeter();
}
// ❌ ERROR: Circle is not abstract and does not override perimeter()
public class Circle extends Shape {
private double radius;
@Override
public double area() {
return Math.PI * radius * radius;
}
// Forgot perimeter()!
}
// ✅ CORRECT: Implement ALL abstract methods
public class Circle extends Shape {
private double radius;
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}
interface Service {
void execute(); // Implicitly public
}
// ❌ ERROR: Cannot reduce visibility
class MyService implements Service {
void execute() { // Package-private - more restrictive than public!
System.out.println("Executing");
}
}
// ✅ CORRECT: Must be public (same or more accessible)
class MyService implements Service {
@Override
public void execute() {
System.out.println("Executing");
}
}
Troubleshooting Guide
Error: "Class is not abstract and does not override abstract method"
// Problem: Concrete class doesn't implement all abstract methods
// Solution: Either implement the method OR make the class abstract
// Option 1: Implement the missing method
public class MyClass implements MyInterface {
@Override
public void missingMethod() {
// Implementation here
}
}
// Option 2: Make the class abstract (defer implementation to subclasses)
public abstract class MyClass implements MyInterface {
// missingMethod() stays abstract
}
Error: "Constructor call must be first statement in constructor"
// Problem: super() or this() not first
public Child(String name) {
validate(name); // ERROR: Can't do this before super()
super(name);
}
// Solution: Put super() first, validate after
public Child(String name) {
super(name); // First!
validate(name);
}
// Or use static validation in super() call
public Child(String name) {
super(validateAndReturn(name)); // Static method call is OK
}
private static String validateAndReturn(String name) {
if (name == null) throw new IllegalArgumentException();
return name;
}
Error: "Duplicate default methods"
// Problem: Multiple interfaces have same default method
interface A { default void foo() { } }
interface B { default void foo() { } }
class C implements A, B { } // ERROR: inherits unrelated defaults
// Solution: Override and choose which to use
class C implements A, B {
@Override
public void foo() {
A.super.foo(); // Choose A's version
// OR: B.super.foo();
// OR: your own implementation
}
}
Interview Questions
Answer: Key differences: (1) A class can implement multiple interfaces but extend only one abstract class. (2) Interfaces can only have constants; abstract classes can have any fields. (3) Interface methods are implicitly public; abstract classes can have any access modifier. (4) Interfaces define capabilities (CAN-DO); abstract classes define identity (IS-A). (5) Pre-Java 8, interfaces had no implementation; now they can have default/static/private methods. Use interfaces for contracts across unrelated classes; use abstract classes for sharing code among related classes.
Answer: Yes, an interface can extend one or MORE interfaces (using extends, not implements). This creates an interface that inherits all methods from parent interfaces. Example: interface C extends A, B { }. This is different from classes, which can only extend one class.
Answer: Default methods (Java 8+) allow interfaces to have method implementations. They were added primarily to enable API evolution - adding methods to interfaces (like forEach() to Iterable) without breaking existing implementations. Without default methods, adding any method to an interface would break all implementing classes.
Answer: Yes, since Java 8. Static methods in interfaces are useful for utility methods related to the interface. They're called using the interface name: InterfaceName.staticMethod(). Unlike default methods, static methods are not inherited by implementing classes or sub-interfaces.
Answer: The compiler reports an error - the class must override the method to resolve ambiguity. In the override, you can: (1) provide your own implementation, (2) call one interface's version using InterfaceName.super.method(), or (3) call both versions.
Answer: Yes. Although you cannot instantiate an abstract class directly, its constructors are called when concrete subclasses are instantiated (via super()). Constructors initialize fields and enforce invariants. This is a key difference from interfaces, which cannot have constructors.
Answer: Rarely now that interfaces have default methods. However, you might use an abstract class over an interface when you need: (1) constructors for initialization, (2) non-public methods, (3) non-static/non-final fields, or (4) to prevent multiple inheritance (Java classes can extend only one class). Before Java 8, this pattern was more common.
Answer: A functional interface has exactly one abstract method (SAM - Single Abstract Method). It can have any number of default and static methods. Functional interfaces can be implemented using lambda expressions or method references. Examples: Runnable, Comparator, Function. The @FunctionalInterface annotation is optional but recommended as it enables compiler checking.
See Also
- OOP Principles – Understanding abstraction and inheritance
- Access Modifiers – Visibility in interfaces vs classes
- Lambda & Streams – Using functional interfaces
- Design Patterns – Strategy, Template Method patterns
- SOLID Principles – Interface Segregation Principle