Java #15 - Interface

Introduction

An interface defines a contract that any implementing class must adhere to, specifying the method signatures that it must provide. An interface is a collection of method signatures (methods without any implementation) that any class implementing the interface must provide. Using the interface we can specify what a class must do, but not how it does it.

An interface is declared using the interface keyword, and its methods are by default public and abstract. In addition to method signatures, an interface can contain constants (displayed using the final keyword) and default methods (methods with a default implementation).

To implement an interface, a class must provide the complete set of methods required by the interface. Classes can implement one or multiple interfaces, and any implementing class must provide an implementation for all the methods declared in the interface.

If a class includes an interface but does not fully implement the methods required by that interface, then the class must be declared as abstract.

Defining an interface

access interface name{
    return-type method-name(parameter-list); // only declared not defined 
    type varname = value; // variable are implicitly final and static and must be initialized 
}

Implementing interface

One or more classes can implement the interface using the implements keyword.

class classname [implements interfacename] {
    //class-body
}

Example

public interface Callback {
    void callback(int param);
}
public class Client implements Callback{
    public void callback(int p){
        System.out.println("callback called with "+ p);
    }
}
public class ClientDemo {
    public static void main(String[] args) {
        Callback callbackClient = new Client();
        callbackClient.callback(20);
    }
}

Example - Implement Stack (Fixed and Dynamic)

Create an interface for an integer stack with two methods push() and pop()

public interface IntStack {
    void push(int item); //Add item on stack
    int pop(); // Remove item from stack
}

Create class FixedStack which implements IntStack and provides an implementation for push and pop method for fixed size

ublic class FixedStack implements IntStack{
    int[] stck;
    int top;

    public FixedStack(int size){
        stck = new int[size];
        top = -1;
    }

    public void push(int item){
        if(top == stck.length-1){
            System.out.println("Stack is full.");
        }
        else{
            stck[++top] = item;
        }
    }

    public int pop(){
        if(top < 0){
            System.out.println("Stack is empty");
            return 0;
        }
        else{
            return stck[top--];
        }
    }
}

Create class DynamicStack which implements IntStack and provides an implementation for push and pop method for dynamic size

public class DynamicStack implements IntStack{
    int[] stck;
    int top;

    public DynamicStack(int size){
        stck = new int[size];
        top = -1;
    }

    public void push(int item){
        if(top == stck.length-1){
            int[] temp = new int[stck.length*2];
            for(int i =0; i < stck.length; i++) temp[i] = stck[i];
            stck = temp;
            stck[++top] = item;
        }
        else{
            stck[++top] = item;
        }
    }

    public int pop(){
        if(top < 0){
            System.out.println("Stack is empty");
            return 0;
        }
        else{
            return stck[top--];
        }
    }
}

The below class is to demo the use of the above classes through the interface reference variable

public class StackDemo {
    public static void main(String[] args) {
        IntStack intStack;
        FixedStack fs = new FixedStack(10);
        DynamicStack ds = new DynamicStack(10);

        intStack = fs;
        intStack.push(10);
        intStack.push(20);
        intStack.push(30);
        intStack.push(40);
        intStack.push(50);

        intStack = ds;
        intStack.push(1);
        intStack.push(2);
        intStack.push(3);
        intStack.push(4);
        intStack.push(5);

        intStack = fs;
        System.out.println("Fixed Stack items");
        for(int i =0; i < 5; i++)
            System.out.println(intStack.pop());

        intStack = ds;
        System.out.println("Dynamic Stack items");
        for(int i =0; i < 5; i++)
            System.out.println(intStack.pop());
    }
}

New Features Added in JDK 8 & 9 For Interface

Below are three new features added in JDK 8 & 9 for the interface

  • Default Interface Method

  • Static Interface Method

  • Private Interface Method


Default Interface Methods

From JDK 8, we can define a default method in the interface i.e. we can define a default implementation for an interface method which can be overridden by implementing class if needed.

This allows interfaces to expand without breaking existing code as there must be implementations for all methods defined by the interface. The addition of a default method will not cause preexisting code to break if there is a new method added to the interface.

Note that the addition of a default method does not change the key aspect of the interface: its inability to maintain state information.

  • An interface still cannot have an instance variable. Thus the key difference between a class and an interface is that class can maintain state information but an interface cannot.

  • It is still not possible to create an instance of an interface.

Example

MyIF interface has 2 methods - one abstract and the other the default.

public interface MyIF {
    int getNumber();
    default String getString() {
        return "Default String";
    }
}

MyIFImp class only override the abstract method

public class MyIFImp implements MyIF{
    public int getNumber(){
        return 10;
    }
}

The object of MyIFImp class can call both methods

public class DefaultMethodDemo {
    public static void main(String[] args) {
        MyIFImp obj = new MyIFImp();
        System.out.println(obj.getNumber());
        System.out.println(obj.getString());
    }
}

The default method gives you

  • a way to gracefully evolve interfaces over time

  • a way to provide optional functionality without requiring that a class provide a placeholder implementation when that functionality is not needed.


Static Interface Method

From JDK 8. we can define static methods in the interface. As we know static method don't need implementation for access, so static method is called by specifying interface name.

static interface methods are not inherited either by class or a sub-interface.

MyIF has a static method called getDefaultNumber()

public interface MyIF {
    int getNumber();
    default String getString() {
        return "Default String";
    }
    static int getDefaultNumber(){
        return 0;
    }
}

Note that the above static interface method is only accessible by the interface not by the class which has implemented the interface.

public class DefaultMethodDemo {
    public static void main(String[] args) {
        MyIFImp obj = new MyIFImp();
        System.out.println(obj.getNumber());
        System.out.println(obj.getString());
        System.out.println(MyIF.getDefaultNumber());
    }
}

Private Interface Method

From JDK9, an interface can have a private method. A private interface method can be called only by a default method or another private method defined by the same interface.

This can help to avoid duplication of code between default methods.


Multiple Inheritance issue

Now that interface can have a default method and a class can implement multiple interfaces, the question arises Is Java now support multiple inheritances?

Consider that there are 2 interfaces and both have default method reset(), when a class implement both interface and implementing class does not override the reset() method then which version of the reset() method will be called? -> It will give an error due to name conflict so it will not lead to Multiple inheritance.

public interface InterfaceA {
    default String reset(){
        return "Reset method from interface A";
    }
}
public interface InterfaceB {
    default String reset(){
        return "Reset method from interface B";
    }
}

In such scenario still you can implement multiple interfaces if you override the reset() method in class.

public class MultipleInheritance implements InterfaceA,InterfaceB{
    public String reset(){
        return "Reset method from implementing class";
    }
}

In such cases, class implementation will take priority over interface default implementation.

public class MultipleInheritanceDemo {
    public static void main(String[] args) {
        MultipleInheritance demo = new MultipleInheritance();
        System.out.println(demo.reset()); // Output - Reset method from implementing class
    }
}

Did you find this article valuable?

Support subodh's blog by becoming a sponsor. Any amount is appreciated!