Java 8–Virtual Extension Methods–Part1

Mar 23rd, 2013 | By | Category: Programming & Languages

Java 8 has really become interesting with addition of closures & Lambda expressions along with couple of other language enhancements.The one I started to play around with right now is the Virtual Extension Methods (also referred to as Defender Methods in early drafts).We will not get into definition, let’s start with a simple piece of code.

public interface A {
    public void m1()   ;
}
public class A1 implements A{
    public void m1(){
        System.out.println("A1.m1");
    }
}
public class Main {

    public static void main(String[] args) {
        A1 a1 = new A1();
        a1.m1();
    }
}

This is just a interface and it’s implementation. Now what happens if we add one more method to the interface A. The class A1 needs to provide an implementation for that or else, it breaks.So to add new stuff to an existing set of APIs we need to have a mechanism by which we are able to add new methods to the interfaces without the existing code breaking.This is where comes Virtual Extension Methods, they are like other virtual methods in an interface but with an ability to provide a default implementation.

public interface A {
    public void m1()   ;
    public default void m2(int i)  {X.xm2(this,i);}

}
public class X {
            public static void xm2(A a, int i){
                System.out.println("demo.X.xm2" + i)   ;
            }
}

In the above code of interface A we have a new method m2 with a default implementation provided by the static method X.xm2. Here, the default keyword tells the compiler that a default implementation is provided for this and classes implementing this interface need not always provide an implementation. The signature of the method providing needs to be compatible with the method signature of the interface and the first parameter should be the type of the interface e.g “A” in this example.

So the below code compiles without any issues:

public class A1 implements A {
    public void m1(){
        System.out.println("A1.m1");
    }
}

public class Main {

    public static void main(String[] args) {
        A1 a1 = new A1();
        a1.m1(); //Prints A1.m1
        a1.m2(10); //Prints demo.X.xm210
    }
}

The class A1 also provide it’s own implementation overriding the default as shown below.

public class A1 implements A {
    public void m1(){
        System.out.println("A1.m1");
    }

    public void m2(int i){
        System.out.println("A1.m2");
    }
}
public class Main {

    public static void main(String[] args) {
        A1 a1 = new A1();
        a1.m1();//Prints A1.m1
        a1.m2(10);//Prints A1.m2
    }
}

Now let’s try to see few interesting things related to method resolution.We need few more interfaces to create a inheritance chain as shown below:

public interface A {
    public void m1()   ;
    public default void m2(int i)  {X.xm2(this,i);}

}
public class X {
            public static void xm2(A a, int i){
                System.out.println("demo.X.xm2" + i)   ;
            }
}
public interface B extends A {
}
public interface C extends A {
}
public class D implements B,C {
    public void m1(){
        System.out.println("D.m1");
    }
}
public class Main {

    public static void main(String[] args) {
        D d = new D();
        d.m2(10);
    }
}

The program as expected invokes X.m2 because A is the only interface in the chain providing the default implementation of m2. Now let’s add a default implementation of m2 in B as shown below:

public interface B extends A {
    public default void m2(int i)  {X.xm2B(this,i);}
}

public class X {
            public static void xm2(A a, int i){
                System.out.println("demo.X.xm2" + i)   ;
            }
            public static void xm2B(B a, int i){
                System.out.println("demo.X.xm2B" + i)   ;
            }
}

In this case, both A & B provides default implementation of m2 but B is more specific and hence X.xm2B is invoked.

Let’s add a default implementation of m2 in C as well.

public interface C extends A {
    public default void m2(int i)  {X.xm2C(this,i);}
}

public class X {
            public static void xm2(A a, int i){
                System.out.println("demo.X.xm2" + i)   ;
            }
            public static void xm2B(B a, int i){
                System.out.println("demo.X.xm2B" + i)   ;
            }
            public static void xm2C(C a, int i){
                System.out.println("demo.X.xm2C" + i)   ;
            }
}

The method resolution for m2 will be ambiguous as both B&C are at same level in inheritance hierarchy and compilation will fail with the following error.

class demo.D inherits unrelated defaults for m2(int) from types demo.B and demo.C

These are some basics of Virtual Extension Methods and how they are resolved. We will continue with some more interesting examples in the next post.


Kick It on DotNetKicks.com
Tags: , ,