Thursday, May 25, 2017

Functional Interfaces - Default and static methods

A cheat sheet is all that is needed to start using Java Lambdas. But here I will start with the design changes done in Java8 in order to accommodate Lambdas.

Most of the interfaces in the JDK have only method. (Comparator, Runnable, Callable, etc) These are Single Abstract Method interfaces. These SAM concepts are recreated in Java8 using Functional Interfaces. Since a lambda function can only provide implementation for one method, it is mandatory for your functional interface to have ONLY ONE interface

Functional Interface

Interface with only one abstract method.

Methods from the Object class do not count. i.e. The interface can also declare the abstract methods from the java.lang.Object class, but still the interface can be called as a Functional Interface. Example 

@FunctionalInterface

public interface SimpleFunctionalInterface {

  public void doWork();

  public boolean equals(Object o);

}


It can also have
- static methods 
- default methods

Note that the @FunctionalInterface is optional. It is just for convenience - the compiler can warn if it is not really a functional interface.

So what is the type of lambda expression? - Functional Interface
Is lambda expression an object? Well, it can be moved around like an object. An anonymous class is created with new. So I am explicitly asking JVM to create a new object (which is an overhead). but when I ask for a lamba, it is still recorded as an object in the JVM. But it is an object of a new kind in java8 - an object without an identity. I know this sounds complex. I will have a separate write-up on this. For now, the answer is no.

Default methods

In order to introduce lambda to Java 8, I need to add a forEach method to Iterable interface. Which will result in compilation error since all the classes that extends Iterable should provide an implementation for forEach. So I will be breaking the backward compatibility. 

Hence the default methods. A new Java8 concept to change the old interfaces without breaking the existing implementations. It provides flexibility to allow interface define implementation which will use as default in the situation where a concrete class fails to provide an implementation for that method. Meaning, they can be overridden. 

So what happens when the classes is implementing 2 interfaces with same default method signatures?

public interface InterfaceA {

    default void defaultMethod(){

        System.out.println("Interface A default method");

    }

}

public interface InterfaceB {

    default void defaultMethod(){

        System.out.println("Interface B default method");

    }

}

public class Impl implements InterfaceA, InterfaceB  {

}


So in these cases, to avoid compilation error, you have to provide your own implementation.
You can even choose to call either of the default methods like this:


public class Impl implements InterfaceA, InterfaceB {

    public void defaultMethod(){

        // existing code here..

        InterfaceA.super.defaultMethod();

    }

}

Summary:


When we extend an interface that contains a default method, we can perform following,
  • Override the default method similar to other methods we override in subclass..
  • Not override the default method and will inherit the default method.
  • Redeclare default method as abstract, which force subclass to override it.

Static Methods

The static methods in interfaces are good for providing utility methods.
Eg : isEquals in java.util.function.Predicate & identity in java.util.function.Function
We can’t define interface static method for Object class methods, we will get compiler error
as “This static method cannot hide the instance method from Object”. This is because
it’s not allowed in java, since Object is the base class for all the classes and we can’t have
one class level static method and another instance method with same signature.
These cannot be not overriden. if you think about it, it makes sense. Say I am providing a isNull
utility in my interface so that the implementations do it right, then I will prevent that
to be overridden
Now let us talk about the inheritance part a bit more. How about static methods in class?
public class SuperClass {
 public static void foo()
 {
  System.out.println("super class static");
 }
}

public class SubClass extends SuperClass{
 public static void foo()
 {
  System.out.println("sub class foo");
 }
}

public class App {
 public static void main(String[] args) {
  SuperClass.foo();
  SubClass.foo();
 }
}
This would have printed :
super class static sub class foo
What if there is no foo() in SubClass
public class SubClass extends SuperClass{
}
It still works :
super class static super class static
Now let us talk about static methods in interface
public interface SuperInterface {
 public static void foo()
 {
  System.out.println("super interface static");
 }
}
public interface SubInterface extends SuperInterface{
 public static void foo()
 {
  System.out.println("sub interface static");
 }
}

public class App {
 public static void main(String[] args) {
  SuperInterface.foo();
  SubInterface.foo();
 }
} 
Prints :
super interface static sub interface static
What if there is not foo in SubInterface? 
public interface SubInterface extends SuperInterface{
}

public class App {
 public static void main(String[] args) {
  SuperInterface.foo();
  SubInterface.foo(); -> compilation error }
}
Similarly, the following will work fine. Both class and interface can have static methods with same names, and neither overrides other
public class SomeClass implements SuperInterface{
 public static void foo()
 {
  SuperInterface.foo();
 }
}
public class App {
 public static void main(String[] args) {
  SomeClass.foo();
 }
}
but the moment you remove foo from SomeClass, it will result in compilation error
public class SomeClass implements SuperInterface{
}

public class App {
 public static void main(String[] args) {
  SomeClass.foo(); -> compilation error }
}
So why default can be inherited and static cannot? These are my favorite answer from stackoverflow.
1) A static method is a method that's associated with the class in which it's defined, rather than with any object created from that class. Every instance of the class shares the static methods of the class. Java 8 also lets static methods be defined in interfaces where they can assist default methods.
When you implement an interface that contains a static method, the static method is still part of the interface and not part of the implementing class. For this reason, you cannot prefix the method with the class name. Instead, you must prefix the method with the interface name
2) Static methods in interfaces could create a diamond of death if they were being inherited. So, calling a static method from the appropriate interface is good enough compared to the risk of calling it from a concrete class that may implement multiple interfaces that contain static methods of the same name.
Why are static methods any different?
Static methods are just functions unrelated to the objects. Instead of placing them in utility abstract classes (like calling Collections.sort() ) we move those functions (static methods) to their appropriate interfaces. They could be bound to the inherited objects like the default methods do, but that is not their job. Static methods provide functionality which is unrelated to the instances of the class.
Example:
interface Floatable {

    default void float() {
        // implementation
    }

    static boolean checkIfItCanFloat(Object fl) {
         // some physics here
    } 
}

class Duck implements Floatable { }
So, the point is that a Duck may float but the function that checks if an Object really floats is not something that a Duck can do. It is an irrelevant functionallity that we could pass to our Floatable interface instead of having it sit inside some utility class.

References :

No comments:

Post a Comment