DEV Community

Cover image for Abstraction: Decoding Interfaces in Java
Arshi Saxena
Arshi Saxena

Posted on

Abstraction: Decoding Interfaces in Java

In Java, Interfaces serve as contracts that classes must adhere to. Since an interface only provides the knowledge of what can be done (via method signatures) and hides how it is done (by leaving the implementation to the classes that implement the interface), it achieves abstraction. This separation of what from how is the core idea behind abstraction.

With Java 8, interfaces evolved beyond purely abstract behavior, supporting default and static methods to improve flexibility and backward compatibility.

This post dives into interfaces, their key features, and the distinctions between interfaces and abstract classes, with code examples to help you grasp the concepts.


What is an Interface?

An interface in Java specifies a set of behaviors (methods) that the implementing class must follow. It only contains the method signatures and constants. In contrast to abstract classes, interfaces allow multiple inheritance by enabling a class to implement more than one interface.

Key Features of Interfaces:

  • Variables in interfaces are implicitly public, static, and final.

  • All methods are implicitly public and abstract (before Java 8).

  • A class can implement multiple interfaces, overcoming the single inheritance limitation of classes.

  • From Java 8 onwards, interfaces can also contain default and static methods, enhancing backward compatibility.


The Basics: Interface Syntax

package oops.interfaces;

public interface InterfaceBasics {

    // Variables are public, static, and final by default
    // Initialization can only be done with declaration (No Static Blocks)
    // Compiler Interpretation: public static final int id = 90;
    int id = 90;

    // Abstract method (public and abstract by default)
    // Compiler Interpretation: public abstract void abstractMethod();
    void abstractMethod();

    // Default method - Introduced in Java 8 (public by default)
    // Compiler Interpretation: public default void concreteMethod()
    default void concreteMethod() {
        System.out.println("Concrete Method Called");
    }

    // Static method - Introduced in Java 8 (public by default)
    // Compiler Interpretation: public static void staticMethod()
    static void staticMethod() {
        System.out.println("Static Method Called");
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation of Key Concepts:

1. Variables in Interfaces:

  • public: Accessible by any class that implements the interface.
  • static: Can be accessed directly using the interface name.
  • final: Prevents modification of the value once it is initialized to ensure consistency.

Note: static final variables can be initialized either at the time of declaration or within static blocks. However, since interfaces do not allow static blocks, these variables must be initialized during declaration.

2. Abstract Methods:

  • These are method signatures without any body or implementation.
  • They are public by default in an interface.
  • All abstract methods must be overridden by the implementing class.

3. Default Methods:

  • Introduced in Java 8 to provide a default implementation for methods in interfaces.
  • These methods are public by default.
  • Implementing classes can override them, but it is not mandatory.
  • Here, "Default" is not an access modifier; it simply indicates that the method is not abstract and can have a concrete implementation.
  • This feature helps developers extend existing interfaces without breaking backward compatibility.

4. Static Methods:

  • Static methods belong to the interface.
  • They can only be accessed using the interface name.
  • These methods are public by default.
  • They are not inherited by implementing classes.
  • Static methods cannot be overridden.

Implementing an Interface in Java

package oops.interfaces;

// A class implementing the InterfaceBasics interface
public class InterfaceBasicsImpl implements InterfaceBasics {

    // Mandatory: Override all abstract methods from the interface
    @Override
    public void abstractMethod() {
        System.out.println("Overridden Method Called");
    }

    public static void main(String[] args) {
        InterfaceBasics obj = new InterfaceBasicsImpl();

        // Calling interface's default and overridden methods
        obj.concreteMethod();  // Output: Default Method Called
        obj.abstractMethod();  // Output: Overridden Method Called

        // Accessing interface variables (static and final by default)
        // Interface variables are inherited
        // Possible with both interface name and implementing class name
        System.out.println(InterfaceBasics.id);        // Output: 90
        System.out.println(InterfaceBasicsImpl.id);    // Output: 90

        // Cannot assign a value to final variable 'id'
        InterfaceBasicsImpl.id = 100;  // --> Compile Error

        // Calling static method using interface name
        // Cannot access using implementing class name
        // Interface static methods are NOT inherited
        InterfaceBasics.staticMethod();  // Output: Static Method Called
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation of Key Concepts:

  1. Method Access:
    The default method (concreteMethod()) and the overridden method (abstractMethod()) are accessed using the class instance obj, demonstrating how both types of methods can be called.

  2. Accessing Interface Variables:
    The interface variable id can be accessed using both the interface name (InterfaceBasics.id) and the implementing class name (InterfaceBasicsImpl.id). This shows that static final variables in an interface are inherited, allowing the implementing class to refer to the variable.

  3. Static Method Access:
    The static method staticMethod() can only be called using the interface name (InterfaceBasics.staticMethod()). Attempting to access it through the implementing class (InterfaceBasicsImpl.staticMethod()) results in a compile-time error, as static methods in interfaces are not inherited.


Why Default and Static Methods?

1. Default Methods

  • Before Java 8, adding new methods to interfaces posed a significant challenge. Any new functionality required all implementing classes to be updated, which often led to breaking changes in large codebases.
  • With the introduction of default methods, interfaces can now provide a concrete implementation for new methods, ensuring backward compatibility. This means that existing classes can remain unchanged while still benefiting from new features.
  • This design choice also paved the way for adding streams and lambdas functionality to the Collections Framework.

2. Static Methods

  • Static methods provide utility functions relevant to the interface and do not need to be overridden by implementing classes.
  • By tying static methods to the interface itself and preventing inheritance, Java avoids potential ambiguity and confusion that could arise from method name collisions across multiple interfaces popularly known as the Diamond Problem.
  • Example Use Case: Here’s a real-world use case of an interface with a static method for logging configuration:
package oops.interfaces.example;

public interface Logger {

    // Using a variable to store the default log file name
    String DEFAULT_LOG_FILE_NAME = "application.log";

    // Static method to get the default log file name with configuration
    static String getDefaultLogFileName() {
        // Simulating configuration retrieval
        // Could be from a properties file or environment variable
        String logFileName = System.getenv("LOG_FILE_NAME");

        // If a log file name is set in the environment, return it;
        // Otherwise, return the default
        if (logFileName != null && !logFileName.isEmpty()) {
            return logFileName;
        } else {
            return DEFAULT_LOG_FILE_NAME;
        }
    }
}

public class FileLogger implements Logger {

    public static void main(String[] args) {
        // Using the interface variable
        String defaultLogFile = Logger.DEFAULT_LOG_FILE_NAME;

        // Using the static method
        if ("FILE".equals(System.getenv("LOG_TYPE"))) {
            defaultLogFile = Logger.getDefaultLogFileName();
        }

        System.out.println("Log file used: " + defaultLogFile);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • In this example, the static method getDefaultLogFileName() provides a way to retrieve the default log file name while keeping the implementation clean and encapsulated within the interface.

How Are Interfaces Still Different from Abstract Classes?

Even with default methods, interfaces remain distinct from abstract classes:

Aspect Interface Abstract Class
Methods Can have abstract, default, and static methods Can have abstract and non-abstract methods
Variables Only public, static, and final variables Can have any access modifier and instance variables
Inheritance Supports multiple inheritance Supports single inheritance
Constructors Cannot have constructors Can have constructors

Default methods should be used only to extend existing interfaces where backward compatibility is needed. They are not a replacement for abstract classes.


Common Interview Questions about Interfaces

1. Can an interface variable be modified?

No, interface variables are implicitly final, meaning their value cannot be changed once assigned.

   // Cannot assign a value to final variable 'id'
   InterfaceBasicsImpl.id = 100;  // --> Compile Error
Enter fullscreen mode Exit fullscreen mode

2. Can we declare a method both default and static?

No. A default method provides a concrete implementation that can be overridden by implementing classes, allowing flexibility. In contrast, a static method belongs to the interface itself, cannot be overridden, and offers utility functions. Thus, the two cannot be used together.

   public interface ExampleInterface {
       default void defaultMethod() {
           System.out.println("This is a default method.");
       }

       static void staticMethod() {
           System.out.println("This is a static method.");
       }

       // The following would result in a compile-time error
       default static void invalidMethod();
   }
Enter fullscreen mode Exit fullscreen mode

3. Why Can’t Static Methods in Interfaces Be Inherited?

Static methods are associated with the interface itself rather than any specific instance of a class, meaning they belong to the interface as a whole. If static methods were inherited by implementing classes, it could lead to ambiguity and confusion about which method is being called, especially if multiple interfaces define methods with the same name.

For example:

   interface InterfaceA {
       static void commonMethod() {
           System.out.println("Method from InterfaceA");
       }
   }

   interface InterfaceB {
       static void commonMethod() {
           System.out.println("Method from InterfaceB");
       }
   }

   class Implementation implements InterfaceA, InterfaceB {
       // This will not compile due to ambiguity.
       // Which commonMethod to implement?
       // Thus, not inherited, cannot be called by class name
       Implementation.commonMethod(); 

       // Correct Way
       InterfaceA.commonMethod();
       InterfaceB.commonMethod();
   }
Enter fullscreen mode Exit fullscreen mode

By keeping static methods tied only to the interface, Java maintains clarity and avoids potential conflicts in method resolution leading to the infamous Diamond Problem of Multiple Inheritance.


Conclusion

Interfaces in Java play a crucial role in achieving abstraction by defining the behavior that implementing classes must adhere to. With the introduction of default and static methods in Java 8, interfaces have become even more powerful, allowing backward compatibility and providing utility methods directly within interfaces.

However, interfaces are not a replacement for abstract classes. They should be used when you need to define a contract for behavior, especially when multiple inheritance is required.


Key Takeaways:

  • Interfaces provide abstraction by defining what a class should do, without specifying how.

  • Variables in interfaces are always public, static, and final.

  • Default and static methods, introduced in Java 8, allow backward compatibility and utility implementations within interfaces.

  • Static methods in interfaces are not inherited, ensuring clarity in their usage.

Understanding how and when to use interfaces will not only enhance your coding skills but also prepare you for interview questions around OOPs concepts and Java design patterns.


Related Posts

Happy Coding!

Top comments (0)