Mastering Java Interfaces: A Comprehensive Guide for Developers
Java interfaces are an essential aspect of the Java programming language. They provide a way to achieve abstraction, multiple inheritance, and polymorphism in Java. In this comprehensive guide, we will delve into the intricacies of Java interfaces, covering their definition, purpose, usage, best practices, and examples.
What is a Java Interface?
Any Service Requirement Specification (SRS) is considered an interface. For example, the JDBC API acts as a requirement specification to develop database drivers, and database vendors are responsible for implementing this JDBC API. Servlet API is an SRS, and web server vendors implement it.
From the client’s point of view, an interface is a set of services that are expected. From the service provider’s perspective, an interface is the set of services offered to the client. Hence, an interface is a contract between the client and the service provider. For instance, the Bank ATM GUI Screen serves as a contract between the bank and the customer, with services like withdrawal, mini-statement, and balance inquiry.
Inside an interface, every method is always abstract whether we declare it explicitly or not. Therefore, an interface is considered a 100% pure abstract class. Whenever we implement an interface, we must provide implementations for every method in the java interface. If we are unable to do so, we declare the implementing class as abstract since it doesn’t contain all method implementations. Additionally, we need to define methods as public so that the next level child class can provide implementations for those methods.
Defining a Java Interface
Java interfaces are defined using the interface keyword followed by the interface name. Here’s an example of a simple interface declaration:
interface Animal {
void eat();
void sleep();
}
In this example, the Animal interface defines two abstract methods: eat() and sleep(). Any class that implements the Animal interface must provide implementations for these methods.
Implementing Java Interface
To implement an interface in Java, a class uses the implements keyword followed by the name of the interface. Here’s an example of a class implementing the Animal interface:
class Dog implements Animal {
public void eat() {
System.out.println("Dog is eating");
}
public void sleep() {
System.out.println("Dog is sleeping");
}
}
In this example, the Dog class implements the Animal interface by providing implementations for the eat() and sleep() methods.
When comparing “extends” and “implements”:
a) A class can extend only one class at a time, but an interface can extend any number of interfaces. For example:
interface A {}
interface B {}
interface C extends A, B {}
b) A class can extend another class and implement any number of interfaces. For instance:
class A extends B implements C, D, E {}
c) An interface never implements another interface or interfaces; it only extends interfaces. Therefore, the statement “interface A implements B” is incorrect.
Consider the following scenarios:
- X extends Y: Both X and Y can be either classes or interfaces.
- X extends Y, Z: X, Y, and Z should be interfaces.
- X implements Y, Z: X should be a class, and Y, Z should be interfaces.
- X extends Y implements Z: X and Y are classes, and Z is an interface.
- X implements Y extends Z: This would result in a compile error because “extends” must be specified before “implements.”
Java Interface Methods
Every method present in java interface is always public and abstract, whether declared explicitly or not. Hence, inside an interface, the following method declarations are equivalent:
void m1();
public void m1();
abstract void m1();
public abstract void m1();
abstract public void m1();
In an interface Interf
, the method declaration would be:
interface Interf {
public abstract void m1();
}
Here the use of public
and abstract
:
public
: This modifier is used to make the method available to every implementation class.
abstract
: This modifier signifies that the implementation class is responsible for providing the implementation.
Since every method in an interface is public
and abstract
, the following modifiers cannot be used in an interface:
private
, protected
, static
, final
, synchronized
, strictfp
, native
.
Java Interface Variables
To define constant values at the requirement level, we use variables inside an interface. Every variable inside an interface is always public
, static
, and final
, regardless of whether we explicitly declare these modifiers.
public
: This modifier is used to make the variable available to every implementation class.
static
: This modifier allows access to the variable without needing an existing object of the implementing class.
final
: This modifier ensures that if one implementation class changes the value, all other implementation classes will be affected. To prevent this, every interface variable is declared as final
.
Hence, the following declarations are equivalent inside an interface:
int x = 10;
public int x = 10;
static int x = 10;
final int x = 10;
public static int x = 10;
public final int x = 10;
static final int x = 10;
public static final int x = 10;
As every interface variable is public
, static
, and final
, we cannot declare interface variables with the following modifiers:
private
, protected
, transient
, volatile
.
It is mandatory to initialize interface variables at the time of declaration; otherwise, we will encounter a compilation error. For example:
interface Interf {
int x; // CE: = expected.
}
Inside a class implementing an interface, we can access interface variables, but we cannot modify them. Attempting to do so will result in a compilation error. For example:
interface Interf {
int x = 10;
}
class Test implements Interf {
public static void main(String args[]) {
x = 777; // CE: cannot assign a value to final variable x
System.out.println(x);
}
}
However, if we declare int x = 777;
instead of x = 777;
, it is perfectly valid because it acts like a local variable and not an interface variable.
Java Interface Naming Conflicts
Method Naming Conflicts:
When dealing with method naming conflicts in interfaces, the resolution depends on various factors including method signature, return type, and argument types.
Same Signature, Same Return Type
If two interfaces contain methods with the same signature and return type, then in the implementing class, we have to provide implementation for only one method.
interface Left {
public void m1();
}
interface Right {
public void m1();
}
class Test implements Left, Right {
public void m1() {}
}
Same Name, Different Argument Types (Overloaded Methods)
If two interfaces contain methods with the same name but different argument types, then in the implementation class, we have to provide implementation for both methods, and these methods act as overloaded methods.
interface Left {
public void m1();
}
interface Right {
public void m1(int i);
}
class Test implements Left, Right {
public void m1() {
// Implementation
}
public void m1(int i) {
// Implementation
}
}
Same Signature, Different Return Types
If two interfaces contain methods with the same method signature but different return types, then it is impossible to implement both interfaces simultaneously. However, in the case of general or covariant return types, it is possible.
interface Left {
public void m1();
}
interface Right {
public int m1();
}
// It is impossible to implement both interfaces having the same method signature but different return types.
// In the case of general or covariant return types, it is possible.
class Test implements Left, Right {
public void m1() {} // Impossible
public int m1() {} // Impossible
}
When facing method naming conflicts in interface implementation, understanding the differences in method signatures, return types, and argument types helps determine the appropriate approach for implementation.
Java Interface Variable Naming Conflicts
When dealing with variable naming conflicts in java interfaces, if two interfaces contain variables with the same names, attempting to use the variable directly inside an implementing class can lead to a naming conflict error, resulting in a compilation error.
interface Left {
int x = 777;
}
interface Right {
int x = 888;
}
class Test implements Left, Right {
public static void main(String[] args) {
System.out.println(x); // Compilation Error: reference to x is ambiguous
// Solution: Resolve naming conflict using interface names
System.out.println(Left.x); // Output: 777
System.out.println(Right.x); // Output: 888
}
}
To resolve this issue, you can specify the interface name along with the variable name to uniquely identify which interface’s variable you are referring to. By doing so, you can access the variables without ambiguity and prevent compilation errors.
Marker Interface
Definition and Purpose
A marker interface, also known as an ability interface or tag interface, is an interface that does not contain any methods. Instead, it serves as a marker or tag to indicate that objects implementing the interface possess certain abilities or characteristics.
Examples of marker interfaces in Java include Serializable
, Cloneable
, RandomAccess
, and SingleThreadModel
. These interfaces indicate that objects implementing them have specific abilities or properties related to serialization, cloning, random access, or single-threaded access.
interface Serializable {
}
Mechanism (Without having any methods how marker interface provide some ability?)
Even though marker interfaces do not declare any methods, they provide some ability to objects by relying on the internal mechanisms of the Java Virtual Machine (JVM). When an object implements a marker interface, the JVM recognizes this during runtime and provides the associated ability or behavior to that object.
Role of JVM (Why does the JVM provide this ability?)
The JVM is responsible for providing or enabling the abilities associated with marker interfaces. This is done to simplify programming and make Java language usage more straightforward for developers. By delegating certain responsibilities to the JVM, Java programs become more concise, easier to maintain, and less prone to errors.
Custom Marker Interfaces
Yes, we can create our own marker interface, but to do that, we would need to create our own JVM that supports all existing JVM features as well as any additional features we want to implement.
Adapter Class
An adapter class is a simple Java class that implements an java interface but provides empty implementations for all the methods in the interface.
interface X {
void m1();
void m2();
void m3();
// ...
void m1000();
}
abstract class AdapterX implements X {
public void m1() {}
public void m2() {}
public void m3() {}
// ...
public void m1000() {}
}
When we implement an interface, we are required to provide implementations for every method in the interface, whether it’s required or not. This can increase the length of code unnecessarily.
class Test implements X {
public void m3() {
// We only want to implement this method
}
// However, we need to implement all methods in the interface,
// which increases the length of code unnecessarily
public void m1() {}
public void m2() {}
public void m4() {}
// ...
public void m1000() {}
}
The problem with the above approach is that it increases the length of code and reduces readability. We can solve this problem by extending the Adapter class. This way, we only need to provide implementations for the required methods, and we are not responsible for implementing every method present in the interface, reducing the length of code.
class Test extends AdapterX {
public void m3() {
// Provide implementation for only this method
}
}
class Sample extends AdapterX {
public void m7() {
// Provide implementation for only this method
}
}
class Demo extends AdapterX {
public void m1000() {
// Provide implementation for only this method
}
}
The concept of marker interfaces and adapter classes simplifies the complexity of programming. They are powerful utilities for programmers, making their lives simpler.
Java Interface vs Abstract Class vs Concrete Class
Java Interface:
- Definition: An interface in Java is a blueprint of a class that defines a set of abstract methods without providing any implementation.
- Usage: Java Interfaces are used when we have a requirement specification without any knowledge of implementation details. For example, in Java servlets, the
Servlet
interface is used to define the methods that all servlets must implement. - Characteristics:
- Contains only method declarations, without any implementation.
- Provides a contract that concrete classes must adhere to.
- Supports multiple inheritance.
- Cannot contain constructors.
Abstract Class:
- Definition: An abstract class in Java is a class that cannot be instantiated and may contain both abstract and concrete methods.
- Usage: Abstract classes are used when we have some knowledge about implementation, but the implementation is not complete. They serve as a partial blueprint for concrete classes. For Example,
GenericServlet
andHttpServlet
in Java servlets. These classes provide partial implementations of theServlet
interface, leaving some methods abstract for subclasses to implement. - Characteristics:
- Can contain both abstract and concrete methods.
- May include instance variables.
- Cannot be instantiated directly, but can be subclassed.
- Used to define common behavior for subclasses.
Concrete Class:
- Definition: A concrete class in Java is a class that provides complete implementation for all its methods and can be instantiated directly.
- Usage: Concrete classes are used when we have a complete understanding of implementation details and are ready to use them in the application.For Example, A custom servlet class (
MyOwnServlet
) that extendsHttpServlet
. This class provides complete implementations for all methods required by the servlet interface and can be directly instantiated for use in a web application. - Characteristics:
- Provides complete implementation for all methods defined in its parent classes or interfaces.
- Can be instantiated directly using the
new
keyword. - Can be subclassed further.
- Typically used to create objects and perform specific tasks in the application.
Difference Between Interface and Abstract Class
Java Interface:
- If we lack knowledge about implementation and possess only the requirement specification, we opt for an interface.
- Inside an interface, every method is inherently public and abstract, whether explicitly declared or not. Hence, an interface is considered a 100% pure abstract class.
- Since every interface method is always public and abstract, we cannot declare them with the modifiers private, protected, final, static, synchronized, native, and strictfp.
- Every variable inside an interface is always public, static, and final, whether explicitly declared as such or not.
- As every interface variable is always public, static, and final, we cannot declare them with the modifiers private, protected, volatile, and transient.
- For interface variables, it is compulsory to perform initialization at the time of variable declaration; otherwise, a compilation error will occur.
- Inside an interface, we cannot declare static and instance blocks.
- Inside an interface, we cannot declare constructors.
Abstract class:
- If we discuss implementation but not completely (partial implementation), we should use an abstract class.
- Not every method inside an abstract class needs to be public and abstract; concrete methods are also allowed.
- There are no restrictions on the modifiers of methods in an abstract class.
- Not every variable inside an abstract class needs to be public, static, and final.
- There are no restrictions on the modifiers of variables in an abstract class.
- For variables in an abstract class, there’s no requirement to perform initialization at the time of declaration, so there are no restrictions on variable declaration.
- Inside an abstract class, we can declare static and instance blocks.
- Inside an abstract class, we can declare constructors.
Loopholes
As an abstract class cannot create an object and still contains a constructor, what is the need for a constructor in an abstract class?
The constructor in an abstract class will be executed whenever a child class object is created to perform initialization of child class objects only. Some properties inherited from the parent abstract class require initialization, necessitating the presence of an abstract class constructor. Its main advantage lies in writing less code and achieving code reusability. Although we cannot create an object directly or indirectly for an abstract class, we can still perform initialization of variables.
Anyway, we can’t create objects for abstract classes and interfaces. However, an abstract class can contain a constructor, whereas an interface does not. What is the reason for this?
The main purpose of a constructor is to perform initialization of instance variables. An abstract class can contain instance variables that are required for child objects, necessitating the presence of a constructor for the abstract class. However, every variable present inside an interface is always public static final, whether explicitly declared or not. Therefore, there is no chance of existing instance variables inside an interface, and the concept of a constructor is not required.
Whenever we create a child class object, the parent class object won’t be created; instead, the parent class constructor will be executed for the child object’s purpose only.
As an interface contains only abstract methods, and even in an abstract class, we can have only abstract methods, what is the difference between them? Can we replace an interface with an abstract class?
Yes, we can replace an interface with an abstract class, but it is not considered good programming practice. This is akin to recruiting an IAS officer for a sweeping activity. However, if everything is abstract, it is highly recommended to use an interface instead. Why?
Approach 1: Abstract Class
- Inheritance Constraint: While extending an abstract class, it’s not possible to extend any other class, limiting the inheritance concept.
- Object Creation Overhead: Object creation for classes extending an abstract class might be relatively more time-consuming due to the complexity of initialization logic.
// Abstract class approach
abstract class X {
}
// 1) While extending an abstract class, we are unable to extend any other class, thus missing out on the inheritance concept.
class Test extends X {
}
// 2) In this case, object creation is costly.
Test t = new Test(); // Takes 2 minutes to create an object
Approach 2: Interface
- Inheritance Flexibility: Implementing an interface doesn’t restrict the ability to extend other classes, allowing more flexibility in inheritance.
- Object Creation Efficiency: Object creation for classes implementing an interface tends to be more efficient, as there is no overhead of initializing shared properties.
// Interface approach
interface X {
}
// 1) While implementing an interface, we are able to extend some other class happily, thus not missing out on the inheritance concept.
class Test implements X {
}
// 2) In this case, object creation is not costly.
Test t = new Test(); // Takes 2 seconds to create an object
In short:
- When using an abstract class, we cannot extend any other class while extending the abstract class itself, leading to a limitation in inheritance.
- Object creation with abstract classes might be costly.
- Conversely, with interfaces, we can happily implement them along with extending other classes, maintaining the inheritance concept intact.
- Object creation with interfaces is typically not costly.
Conclusion
Java interfaces are a powerful feature of the Java programming language, allowing for abstraction, multiple inheritance, and polymorphism. By defining contracts that classes must adhere to, interfaces enable flexible and modular code design. Understanding how to define, implement, and use interfaces is essential for Java developers to write clean, maintainable, and extensible code.
In this guide, we’ve covered the basics of Java interfaces, including their definition, purpose, usage, best practices, and examples. With this knowledge, you should be well-equipped to leverage interfaces effectively in your Java projects.