Talking about interface, there are two types of implementations which exists, the pre Java 8 ones where interfaces could contain only abstract methods. But in the recent update of Java 8, where interfaces were allowed to have implementations of methods as well as abstract methods. Kotlin offers similar type of interfaces i.e. Interface in kotlin are allowed to have implemented methods as well as abstract methods.
Let’s have a look at traditional Interfaces or Interfaces in Java 6:
Older version of interfaces provided support have only method declarations, but could not provide body to these declarations. This approach was helpful in cases which offered common functionalities but attributed different way of implementation. Let’s see a small case study which has an interface as Area and the same is calculated in different ways to understand the area of Square is side * side whereas the area of circle is pie * r * r. Now we want to have a function which prints the value of area.
From the above concept, we have got the advantages of interfaces, now let’s have a look how Kotlin simplifies the use of interfaces.
Kotlin Interface
Kotlin has added a very small functionality which makes use of Interfaces more desirable. It allows the interfaces to have codes. To put it in context, it allows functionality to be derived from interfaces. Not it stops here, it allows classes to implement multiple interfaces. This will allow a class to implement multiple interfaces and inherit the behaviour of all of them.
Let’s have a look at how to declare and define functions inside the interface. We create an interface as Hello and another interface as World and both have functions hello() and world() respectively.
// Interface with definition of function interface Hello{ fun hello(){ print("Hello"); } }
The other one – Interface World
// Interface with definition of function interface World{ fun world(){ print("World"); } }
Lets create a class as Greeting which implements the two interfaces : Hello and World as below:
// Class which implements multiple inheritance class Greeting : Hello, World { fun greet() { Hello() world() } } fun main() { val greet = Greeting() greet.greet(); }
Output:
Hello World
The above use of interfaces along with classes allows us to reduce code redundancy and improve code readability.
Interfaces without States
Having gone through the above concepts we have understood that kotlin permits the definition of functions but it does not maintains the state. Any variables defined to maintain the state actually need to be overridden in the corresponding classes.
To put it in simple words, you can create (declare) a variable or property but any class which is implementing the interface needs to overwrite the state.
Let’s understand this with an example: We take an interface Area which has methods calcArea() and printArea() and a state variable as calArea. Here, calcArea() method would have only declaration in the interface and printArea() method would have code to print the area calculated.
interface Area{ val calArea: Integer fun calcArea(); fun printArea(){ println("Printing the calculated area.") } } class Square : Area() { override val calArea = this override fun calcArea(side : Integer) { calArea = $side * $side } } class Circle : Area() { override val calArea = this override fun calcArea(radius : Float) { calArea = 3.17 * $radius * $radius } }
Now, here if we instantiate the two classes and call the methods to calculate area, the function would be picked up on the basis of the class whereas the printArea() method would be called from the interface itself.
Ambiguity in Functions: Overriding Conflicts
In cases when there are more than one implementation of a function in the super list which the class inherits it would add ambiguity “Remember we had discussed about Why Java does not supports multiple inheritance”. In such cases Kotlin has a definitive way of handling conflicts.
Lets first understand the conflicts with an example:
interface One { fun A() { print("1-A") } fun B() } interface Two { fun A() { print("2-A") } fun B() { print("2-B") } } class Three : One { override fun B() { print("3-B") } } class Four : One,Two { override fun A() { super<One>.A() super<Two>.A() } override fun B() { super<Two>.B() } }
Both interfaces One and Two declare the functions A() and B() respectively. From the function definition both implement the function A(), however only Two implements the B() (Point to ponder here is the method B() is not declared as abstract in One, because that is default definition for the interfaces, if the function has no body). But, if class Three is derived from class One (Class Three implements the interface Two), we need to override the function Two() and give an implementation as per standard interface definition.
But, have a look at example above in class Four, which is implementing the two interfaces One and Two, in such cases we are supposed to implement all the methods which are inherited from multiple interfaces and specify the class Four should use them. This is done to avoid ambiguity to the compiler when it encounters the same functions twice. The above said rule applies to both the methods which have been inherited into a single implementation (B()) and multiple implementations (A()).
This in itself tries to sums up and handles the ambiguity issue known in case of multiple inheritance.
Interface Delegation
Kotlin provides an easy implementation of delegation using Interfaces. Let’s have a look at the program and then will discuss about facts and support of interface delegation in kotlin:
interface Pizza {// Interface with method fun printPizza() } class CheesePizza(val n:String): Pizza { // Cheese Pizza's implementation of pizza override fun printPizza() = println("${n} !!!") } class VegPizza(val n:String): Pizza {// Veg Pizza's implementation of pizza override fun printPizza() = println("The Ultimate Veg Pizza: $n") } // Class Dominos Pizza class DominosPizza(n:String): Pizza by CheesePizza(n) // The actual delegation, where the dominos pizza class gets the method form the object itself. // Class PizzaHut class PizzaHut(n:String): Pizza by VegPizza(n) // The actual delegation, where the pizza hut class gets the method form the object itself. fun main() { val domPizza = DominosPizza("Domino's Cheese Pizza") //The call is here delegated to the respective delegated object. domPizza.printPizza() val pizHut = PizzaHut("Pizza Hut's Veg Pizza") //The call is here delegated to the respective delegated object. pizHut.printPizza() }
Output:
Domino’s Cheese Pizza!!!
The Ultimate Veg Pizza: Pizza Hut’s Veg Pizza
The understanding of the delegation is as below:
- Create an interface with abstract method like “Pizza”.
- Implement the interface “Pizza” in 2 classes as “CheesePizza” and “Veg Pizza” as shown in the example above. Both the class provide their own implementation of the printPizza() method.
- We create 2 classes as “DominosPizza” and “PizzaHut” which implement the interface but don’t need to provide the implementation of the method. Instead their method is delegated to responsible class which in turns provides its own implementation. To refer the delegate object we provide the object using the “by” keyword. Thereby eliminating need of any boiler plate code.
- When the printPizza() method is called in the DominosPizza it points to the CheesePizza delegated by the object “CheesePizza” and PizzaHut which is delegated by the “VegPizza”. Hence when the methods are called the call is delegated to corresponding delegate object.
Keeping all these simples, interfaces reduce boiler plate code, reduce down the dependency and eventually increase code readability and simplicity. But not to forget Interfaces form an integral part of the inheritance.