Interface in Java
In Java, an interface is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. (Since Java 8) Method bodies exist only for default methods and static methods. Interfaces cannot be instantiated, they can only be implemented by classes or extended by other interfaces.
Interface is used to achieve abstraction and multiple inheritance in Java.
Multiple Inheritance Problem
Java support multi-level inheritance
class GrandParent {
// GrandParent members
}
class Father extends GrandParent {
// Father members
}
//Multi Inheritance
class Child extends Father {
// Child members
}
but not multiple-inheritance
class GrandParent {
void print() {
System.out.println("Grandparent");
}
}
class Father extends GrandParent {
@Override
void print() {
System.out.println("Father");
}
}
class Mother extends GrandParent {
@Override
void print() {
System.out.println("Mother");
}
}
// Multi Inheritance
class Child extends Father, Mother {
// Child members
}
class TestChild {
void test() {
Child c = new Child();
c.print();
}
}
/*
compilation error: '{' expected
*/
From the code, we see that: On calling the method print() using Child object will cause complications such as whether to call Father’s print() or Mother’s print() method. This problem is known as the diamond problem.
The Diamond Problem
The Diamond Problem
The diamond problem (or sometimes referred too as the "deadly diamond of death") is the generally used term for an ambiguity that arises when two classes B and C inherit from a superclass A, and another class D inherits from both B and C. If there is a method "m" in A that B or C (or even both of them) has overridden, and furthermore, if does not override this method, then the question is which version of the method does D inherit? It could be the one from A, B or C .
In our case both Father (B) and Mother (C) are sub classes of GrandParent (A), and override print() (the m) method from GrandParent. When Child (D) inherits from Father (B) and Mother (C), which print() method (m) Child inherits?
Therefore, in order to avoid such complications Java does not support multiple inheritance of classes.
Why Interface?
Interfaces are great for putting together plug-n-play like architectures where components can be interchanged at will. Since all interchangeable components implement the same interface, they can be used without any extra programming. The interface forces each component to expose specific public members that will be used in a certain way.
Because interfaces must be implemented by derived classes, they define a contract.
Let's check our example:
public interface Loggable {
void log(String message);
}
Different classes can implement Loggable by providing an implementation of the log() method, for example, the ConsoleLog class logs the message on the console, FileLog logs the message into a log file, and DBLog class logs the message a record in the database.
public class ConsoleLog implements Loggable {
@Override
public void log(String message) {
System.out.println(message);
}
}
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
public class FileLog implements Loggable {
@Override
public void log(String message) {
try {
final Path path = Paths.get("path/to/filename.log");
Files.write(path, Arrays.asList(message), StandardCharsets.UTF_8,
Files.exists(path) ? StandardOpenOption.APPEND : StandardOpenOption.CREATE);
} catch (final IOException iox) {
// Add exception handling here
}
}
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DBLog implements Loggable {
private Connection connect() {
String url = "jdbc:sqlite:log.db";
Connection conn = null;
try {
conn = DriverManager.getConnection(url);
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return conn;
}
@Override
public void log(String message) {
String sql = "INSERT INTO tb_log(message) VALUES(?)";
try (Connection conn = this.connect();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, message);
pstmt.executeUpdate();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}
Now, you can instantiate an object. It's depends on the situation, you want to use ConsoleLog, FileLog, or DBLog class:
Loggable consolelog = new ConsoleLog();
consolelog.log("Log into console");
// or
Loggable filelog = new FileLog();
filelog.log("Log into a file");
// or
Loggable dblog = new DBLog();
dblog.log("Log into a database");
Implement Multiple Interfaces
Conceptual Example
A cat can eat, so the Cat class may implement Eatable Interface.
A cat can make sound (such as meow, yowling, hissing, and growling... OK, we not talking about cat), so the Cat class may implement Soundable Interface.
Because an instance of the class could have different behaviors, we could have different corresponding interfaces.
public interface Eatable {
void eat();
}
public interface Soundable {
void sound();
}
public class Cat implements Eatable, Soundable {
@Override
public void eat() {
System.out.println("A cat able to eat");
}
@Override
public void sound() {
System.out.println("A cat able to make sound");
}
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
cat.sound();
}
}
/*
Output:
------
A cat able to eat
A cat able to make sound
*/
Real-life Example
Class ArrayList (in java.util package), implements following interfaces:
- List
- RandomAccess
- Cloneable
- Serializable
extends AbstractList which extends AbstractCollection which implements:
- Collection which extends from interface Iterable
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable