Interface
An interface in Java is a fully abstract "contract" that a class can agree to follow. It defines a set of method signatures as a blueprint for behavior, it doesn't provide the implementation for those methods. The class that implements the interface must must implement these methods.
An interface defines what a class can do, without specifying how it does it. This allows different classes to implement the same interface in their own unique ways, enabling to write flexible code that works with any object that adheres to the contract.
Its primary significance is to achieve abstraction and polymorphism.
Defining and Implementing an Interface
Interface is defined using the interface
keyword. A class then uses the implements
keyword to adopt the contract and must provide a concrete implementation for all of the interface's methods.
Defining an Interface:
interface Drivable {
void startEngine();
void stopEngine();
void turn(String direction);
}
Implementing the Interface:
class Car implements Drivable {
@Override
public void startEngine() {
System.out.println("Car engine started.");
}
@Override
public void stopEngine() {
System.out.println("Car engine stopped.");
}
@Override
public void turn(String direction) {
System.out.println("Car is turning "
+ direction);
}
}
Implementing Multiple Interfaces
Java allows a class to implement multiple interfaces. This is how Java supports a form of multiple inheritance (multiple inheritance of type, not state).
A single class can agree to multiple contracts, combining behaviors from different sources. List the interfaces after the implements
keyword, separated by commas.
A Smartphone can act as both a Phone and a Camera.
interface Phone {
void makeCall(String number);
}
interface Camera {
void takePicture();
}
// This class implements both interfaces
class Smartphone implements Phone, Camera {
@Override
public void makeCall(String number) {
System.out.println("Calling " + number + "...");
}
@Override
public void takePicture() {
System.out.println("Click! Picture taken.");
}
}
public class MultiInterfaceDemo {
public static void main(String[] args) {
Smartphone myPhone = new Smartphone();
myPhone.makeCall("123-456-7890");
myPhone.takePicture();
}
}
Classes vs. Interfaces
The key difference is that a class describes both attributes and behaviors, while an interface only describes behaviors.
Feature | Class | Interface |
---|---|---|
Purpose | To be a blueprint for objects. | To be a contract for what a class can do. |
Instantiation | You can create an instance (object) of a class. | You cannot create an instance of an interface. |
Methods | Can have both abstract and concrete methods. | Traditionally, only abstract methods. (Java 8+ allows default and static methods). |
Variables | Can have instance variables of any kind. | Can only have public static final constants. |
Constructors | Can have constructors. | Cannot have constructors. |
Inheritance | A class can extend only one superclass. | A class can implement multiple interfaces. |
Keyword | class | interface |
extends
vs. implements
extends
and implements
are both used for inheritance, but they define different kinds of relationships.
extends
: Creates a parent-child relationship between two classes. The subclass inherits the state (fields) and behavior (methods) from its one and only superclass. It represents an "is-a" relationship (e.g., aDog
is anAnimal
).implements
: Creates a contract relationship between a class and one or more interfaces. The class agrees to provide the implementation for the methods defined in the interface. It represents a "can-do" relationship (e.g., aBird
canFly
).
A Parrot is an Animal (inheritance) and can Fly (behavior).
// Superclass
class Animal {
void eat() {
System.out.println("This animal eats.");
}
}
// Interface
interface Flyable {
void fly();
}
// Class that extends one class and implements one interface
class Parrot extends Animal implements Flyable {
@Override
public void fly() {
System.out.println("The parrot is flying.");
}
}
public class ExtendsVsImplementsDemo {
public static void main(String[] args) {
Parrot polly = new Parrot();
polly.eat(); // Inherited from Animal
polly.fly(); // Implemented from Flyable
}
}
Default Interface Methods
A default method is a method in an interface that has a body. It was introduced in Java 8 to allow new methods to be added to existing interfaces without breaking the classes that already implement them.
If a class implements an interface with a default method, it can either use the default
implementation directly or override it with its own specific logic.
interface Alarm {
void setAlarm();
// A default method with a pre-defined implementation
default void snooze() {
System.out.println("Snoozing for 5 minutes.");
}
}
class MyClock implements Alarm {
@Override
public void setAlarm() {
System.out.println("Alarm is set!");
}
// This class does not override snooze(),
// so it will use the default one.
}
Interface Programs
1. Stack Implementation Using an Interface
Defining Stack
interface and then provides a concrete implementation using an ArrayList
or LinkedList
. The main class then demonstrates the Last-In, First-Out (LIFO) behavior of the stack.
import java.util.LinkedList;
interface IStack {
void push(int item); // Add an item to the top
int pop(); // Remove and return the top item
int peek(); // Return the top item without removing
boolean isEmpty();
}
// class that implements the Stack interface using a LinkedList
class LinkedStack implements IStack {
private LinkedList<Integer> stackList;
public LinkedStack() {
stackList = new LinkedList<>();
}
@Override
public boolean isEmpty() {
return stackList.isEmpty();
}
@Override
public void push(int item) {
stackList.addFirst(item);
System.out.println("Pushed: " + item);
}
@Override
public int pop() {
if (!isEmpty()) {
int item = stackList.removeFirst();
System.out.println("Popped: " + item);
return item;
} else {
System.out.println("Stack is empty. Cannot pop.");
return -1; // Or throw an exception
}
}
@Override
public int peek() {
if (!isEmpty()) {
return stackList.getFirst();
}
return -1; // Or throw an exception
}
}
public class StackDemo {
public static void main(String[] args) {
IStack myStack = new LinkedStack();
myStack.push(10);
myStack.push(20);
myStack.push(30);
System.out.println("Top element is: "
+ myStack.peek());
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop(); // Attempt to pop from an empty stack
}
}
2. Stack interface using an ArrayList
import java.util.ArrayList;
interface IStack {
void push(int item); // Add an item to the top
int pop(); // Remove and return the top item
int peek(); // Return the top item without removing
boolean isEmpty();
}
class ArrayStack implements IStack {
private ArrayList<Integer> stackList;
public ArrayStack() {
stackList = new ArrayList<>();
}
@Override
public void push(int item) {
stackList.add(item);
System.out.println("Pushed: " + item);
}
@Override
public int pop() {
if (!isEmpty()) {
int lastIndex = stackList.size() - 1;
int item = stackList.remove(lastIndex);
System.out.println("Popped: " + item);
return item;
} else {
System.out.println("Stack is empty. Cannot pop.");
return -1; // Or throw an exception
}
}
@Override
public int peek() {
if (!isEmpty()) {
int lastIndex = stackList.size() - 1;
return stackList.get(lastIndex);
}
return -1; // Or throw an exception
}
@Override
public boolean isEmpty() {
return stackList.isEmpty();
}
}
public class StackDemo {
public static void main(String[] args) {
IStack myStack = new ArrayStack();
myStack.push(10);
myStack.push(20);
myStack.push(30);
System.out.println("Top element is: " + myStack.peek());
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop(); // Attempt to pop from an empty stack
}
}
3. Queue Implementation Using an Interface
Defining a Queue
interface and implements it using a LinkedList
. The demo shows the First-In, First-Out (FIFO) behavior.
import java.util.LinkedList;
interface IQueue {
void enqueue(String item); // Add an item to the end
String dequeue(); // Remove, return item from the front
String peek(); // Return front item without removing
boolean isEmpty();
}
class LinkedQueue implements IQueue {
private LinkedList<String> queueList;
public LinkedQueue() {
queueList = new LinkedList<>();
}
@Override
public void enqueue(String item) {
queueList.addLast(item);
System.out.println("Enqueued: " + item);
}
@Override
public String dequeue() {
if (!isEmpty()) {
String item = queueList.removeFirst();
System.out.println("Dequeued: " + item);
return item;
} else {
System.out.println("Queue is empty. Cannot dequeue.");
return null; // Or throw an exception
}
}
@Override
public String peek() {
if (!isEmpty()) {
return queueList.getFirst();
}
return null;
}
@Override
public boolean isEmpty() {
return queueList.isEmpty();
}
}
public class QueueDemo {
public static void main(String[] args) {
IQueue myQueue = new LinkedQueue();
myQueue.enqueue("Alice");
myQueue.enqueue("Bob");
myQueue.enqueue("Charlie");
System.out.println("Front of queue is: "
+ myQueue.peek());
myQueue.dequeue();
myQueue.dequeue();
myQueue.dequeue();
myQueue.dequeue(); // Attempt to dequeue from an empty queue
}
}
4. Searchable
Interface with Multiple Implementations
The Searchable
interface defines a contract for searching, which is then implemented using two different algorithms: linear search and binary search.
import java.util.Arrays;
interface Searchable {
int search(int[] arr, int key);
}
class LinearSearch implements Searchable {
@Override
public int search(int[] arr, int key) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == key) {
return i; // Found it
}
}
return -1; // Not found
}
}
class BinarySearch implements Searchable {
@Override
public int search(int[] arr, int key) {
// Note: Binary search requires the array to be sorted.
int low = 0;
int high = arr.length - 1;
while (low <= high) {
int mid = (high + low) / 2;
if (arr[mid] < key) {
low = mid + 1;
} else if (arr[mid] > key) {
high = mid - 1;
} else {
return mid; // Found it
}
}
return -1; // Not found
}
}
public class SearchDemo {
public static void main(String[] args) {
int[] numbers = { 5, 2, 8, 1, 9, 4 };
int key = 9;
// Use Linear Search
Searchable linear = new LinearSearch();
int index1 = linear.search(numbers, key);
System.out.println("Linear Search: Key "
+ key + " found at index " + index1);
// For Binary Search, the array must be sorted first.
Arrays.sort(numbers);
System.out.println("Sorted array for Binary Search: "
+ Arrays.toString(numbers));
// Use Binary Search
Searchable binary = new BinarySearch();
int index2 = binary.search(numbers, key);
System.out.println("Binary Search: Key "
+ key + " found at index " + index2);
}
}
5. Shape
Interface with Multiple Implementations
Defining a Shape
interface with a calculateArea()
method. Three different classes—Circle
, Square
, and Triangle
—implement this interface, each providing the correct formula for its area.
import java.util.ArrayList;
import java.util.List;
interface Shape {
// public, static, and final by default
double PI = 3.14159;
double calculateArea(); // An abstract method
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return PI * radius * radius;
}
}
class Square implements Shape {
private double side;
public Square(double side) {
this.side = side;
}
@Override
public double calculateArea() {
return side * side;
}
}
class Triangle implements Shape {
private double base;
private double height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
@Override
public double calculateArea() {
return 0.5 * base * height;
}
}
public class ShapeInterfaceDemo {
public static void main(String[] args) {
// Create a list of Shape objects
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(5.0));
shapes.add(new Square(4.0));
shapes.add(new Triangle(6.0, 3.0));
// demonstrates polymorphism
for (Shape shape : shapes) {
System.out.printf("Area of %s is %.2f\n",
shape.getClass().getSimpleName(),
shape.calculateArea());
}
}
}
main function without using List and iteration to print
public class ShapeInterfaceDemo {
public static void main(String[] args) {
Shape myCircle = new Circle(5.0);
Shape mySquare = new Square(4.0);
Shape myTriangle = new Triangle(6.0, 3.0);
System.out.printf("Area of the Circle is %.2f\n",
myCircle.calculateArea());
System.out.printf("Area of the Square is %.2f\n",
mySquare.calculateArea());
System.out.printf("Area of the Triangle is %.2f\n",
myTriangle.calculateArea());
}
}