The java.util.Objects Class & Default Methods in Java

Default Method introduced in Java 8, allows developers to add new methods to an interface without breaking the existing implementations of this interface. It provides flexibility to allow the interface to define an implementation which will be used as default when a class which implements that interface fails to provide an implementation of
that method.

Basic use for object null check

For null check in method
Object nullableObject = methodReturnObject();
if (Objects.isNull(nullableObject)) {
      return;
}
For not null check in method
Object nullableObject = methodReturnObject();
if (Objects.nonNull(nullableObject)) {
     return;
}

Objects.nonNull() method reference use in stream api

In the old fashion way for collection null check

List<object> someObjects = metodGetList(); 
for (Object obj : someObjects) {
if (obj == null) {
continue;
}
doSomething(obj);
}

With the Objects.nonNull method and Java8 Stream API, we can do the above in this way:

List<Object> someObjecs = methodGetList();
someObjects.stream().filter(Objects::nonNull).forEach(this::doSomething);

Basic usage of default methods

/**
* Interface with default method
* /
public interface Printable {
           default void printString() {
                     System.out.println( "default implementation" );
           }
}

/**
* Class which falls back to default implementation of {@link #printString()}
*/
public class WithDefault
     implements Printable
{
}

/**
* Custom implementation of {@link #printString()}
* /
public class OverrideDefault
     implements Printable {
     @Override
     public void printString() {
           System.out.println( "overridden implementation" );
     }
}

The following statements

new WithDefault().printString();
new OverrideDefault().printString();

Will produce this output:
default implementation

overridden implementation

Accessing overridden default methods from implementing class

In classes, super.foo() will look in superclasses only. If you want to call a default implementation from a superinterface, you need to qualify super with the interface name: Fooable.super.foo().

public interface Fooable {
     default int foo() {return 3;}
}

public class A extends Object implements Fooable {
     @Override
     public int foo() {
          //return super.foo() + 1;        //error: no method foo() in java.lang.Object
           return Fooable.super.foo() + 1; //okay, returns 4
     }
}

Why use Default Methods?

The simple answer is that it allows you to evolve an existing interface without breaking existing implementations.

Related Article: Classes and Objects in Java

For example, you have Swim interface that you published 20 years ago.

public interface Swim {
   void backStroke();
}

We did a great job, our interface is very popular, there are many implementation on that around the world and you don’t have control over their source code.

public class FooSwimmer implements Swim {
    public void backStroke() {
         System.out.println("Do backstroke");
}
}

After 20 years, you’ve decided to add new functionality to the interface, but it looks like our interface is frozen because it will break existing implementations.

Luckily Java 8 introduces brand new feature called Default method.

We can now add new method to the Swim interface.

Now all existing implementations of our interface can still work. But most importantly they can implement the newly added method in their own time.

One of the biggest reasons for this change, and one of its biggest uses, is in the Java Collections framework. Oracle could not add a foreach method to the existing Iterable interface without breaking all existing code which
implemented Iterable. By adding default methods, existing Iterable implementation will inherit the default implementation.

Accessing other interface methods within default method

You can as well access other interface methods from within your default method.

public interface Summable {
     int getA();

     int getB();

     default int calculateSum() {
         return getA() + getB();
     }
}

public class Sum implements Summable {
     @Override
     public int getA() {
         return 1;
     }

     @Override
     public int getB() {
        return 2;
     }
}
The following statement will print 3:
System.out.println(new Sum().calculateSum());

Default methods could be used along with interface static methods as well:

public interface Summable {
     static int getA() {
           return 1;
     }
     static int getB() {
          return 2;
     }
     default int calculateSum() {
          return getA() + getB();
     }
}
public class Sum implements Summable {}

The following statement will also print 3:

System.out.println(new Sum().calculateSum());

Default method multiple inheritance collision

Consider next example:

public interface A {
      default void foo() { System.out.println("A.foo"); }
}
public interface B {
     default void foo() { System.out.println("B.foo"); }
}

Here are two interfaces declaring default method foo with the same signature.

If you will try to extend these both interfaces in the new interface you have to make choice of two, because Java forces you to resolve this collision explicitly.

First, you can declare method foo with the same signature as abstract, which will override A and B behaviour.

public interface ABExtendsAbstract extends A, B {
     @Override
         void foo();
}

And when you will implement ABExtendsAbstract in the class you will have to provide foo implementation:

public class ABExtendsAbstractImpl implements ABExtendsAbstract {
       @Override
       public void foo() { System.out.println("ABImpl.foo"); }
}

Or second, you can provide a completely new default implementation. You also may reuse code of A and B foo methods by Accessing overridden default methods from implementing class.

public interface ABExtends extends A, B {
      @Override
      default void foo() { System.out.println("ABExtends.foo"); }
}

And when you will implement ABExtends in the class you will not have to provide foo implementation:

public class ABExtendsImpl implements ABExtends {}

Class, Abstract class and Interface method precedence

Implementations in classes, including abstract declarations, take precedence over all interface defaults.

  • Abstract class method takes precedence over Interface Default Method.
public interface Swim {
     default void backStroke() {
         System.out.println("Swim.backStroke");
     }
}

public abstract class AbstractSwimmer implements Swim {
     public void backStroke() {
            System.out.println("AbstractSwimmer.backStroke");
     }
}
public class FooSwimmer extends AbstractSwimmer {
}

The following statement

new FooSwimmer().backStroke();

Will produce

AbstractSwimmer.backStroke

Class method takes precedence over Interface Default Method

public interface Swim {
     default void backStroke() {
          System.out.println("Swim.backStroke");
     }
}
public abstract class AbstractSwimmer implements Swim {
}
public class FooSwimmer extends AbstractSwimmer {
      public void backStroke() {
           System.out.println("FooSwimmer.backStroke");
      }
}

The following statement

new FooSwimmer().backStroke();

Will produce

FooSwimmer.backStroke

LEAVE A REPLY

Please enter your comment!
Please enter your name here