Saturday 19 November 2016

Java 8 : Default methods, Optional and Logging

Default methods

Shortcomings of Interface:

The biggest disadvantage of using an interface is suppose you want add an new functionality to it, then all the classes that implement this interface needs to update the new functionality and  hence to avoid this Java8 has allowed to add default methods to the interfaces. The default method looks like below.

default void sort(Comparator<? super E> c){
          Collections.sort(this,c);
}

Difference between Abstract Class and Java 8 interface:

  1. A Class can extend only one abstract class but whereas it can implement multiple interfaces.
  2. An abstract class can interface a common state throughout i.e it has the instance variables but whereas the interface does not.

Note : The default methods are like the abstract methods defined in the interface but with the implementation and these are not static.

As part of Java 8 we can now have static and default methods in the in interface. PFB the example.

public interface A {

       static void helloStatic(){
              System.out.println("Hello from Static A");
       }

       default void hello(){
              System.out.println("Hello from A");
       }
}

Important Points:

  1. All the methods defined in interface are public i.e the default and static methods both are public.
  2. The default is not a access modifier, but its a keyword which is used on a method in a interface which says that its the implemented method in the interface.Note: Even out of the 4 access modifiers public,default,protected and private we never write a method with the default access modifier, but we do not specify any access modifier and it means the default one.

Lets discuss on the different cases on multiple inheritance.

Case 1:

public interface A {
       default void hello(){
              System.out.println("Hello from A");
       }
}

public interface B extends A{
       default void hello(){
              System.out.println("Hello from B");
       }
}

public class C implements B , A {

       public static void main(String[] args) {
              new C().hello();
       }
}

O/P is :Hello from B

Case 2:

public class D implements A {
}

O/P is :Hello from B

Case 3:

public class D implements A {
       public void hello(){
              System.out.println("Hello from D");
       }
}

O/P is :Hello from D

Case 4:

public class C extends D implements B , A {

       public void hello(){
              System.out.println("Hello from C");
       }

       public static void main(String[] args) {
              new C().hello();
       }
}

O/P is :Hello from C

Case 5:

public interface A {
       default void hello(){
              System.out.println("Hello from A");
       }
}

public interface B {
       default void hello(){
              System.out.println("Hello from B");
       }
}

public class C implements B , A {

       public static void main(String[] args) {
              new C().hello();
       }
}

This would through an compile time error because at complie time it does not know which method it should override and hence we would need to add a overridden method is C.

public class C implements A , B {

       public void hello(){
              System.out.println("Hello from C");
       }

       public static void main(String[] args) {
              new C().hello();
       }
}

On adding this it would work fine.

Case 6:

public interface A {

      default void hello(){
              System.out.println("Hello from A");
       }
}

public interface B extends A{
       void hello();
}
public class implements A , B {

       public static void main(String[] args) {
              new C().hello();
       }
}

O/P is: This would throw an compilation error to implement the hello method is C. Always remember that whenever is define an abstract method in interface it needs to be implemented in its child classes.

Case 7:
public interface A {
       void hello();
}

public interface B extends A{
      default void hello(){
              System.out.println("Hello from A");
       }
}
public class implements A , B {

       public static void main(String[] args) {
              new C().hello();
       }
}

O/P is: This would compile and execute fine.

Based on the above cases, the working of the overridden method can be taught as below:

1.Methods defined in the class or one defined in the inherited class or the sub class gets the preferance first. Ex: Case 3
2.The sub-interfaces win and if they are methods with the same signature then the most default one is selected. Ex: Case 1
3.Finally if the choice is still ambiguous then the class needs to explicitly select the default method by overriding it and calling the desired method.

Optional:

One of the challenges with the code is to check a variable if it is null everytime we want to perform any operation on it in order to avoid the NullPointerException. To counter this Java 8 has introduced the Optional.

Creating an Optional Objects:

1.Empty Optional: You can get an empty Optional object using the below code.

Optional<Car> optCar = Optional.empty();

2.Optional from a non null value:

Optional<Car> optCar = Optional.of(car);

If the car is null, a nullpointer error is thrown instead of throwing at the end and failing.

3.Optional from null

Optional<Car> optCar = Optional.ofNullable(car);

If the car is null then the resulting Optional object would be empty.

4.Extracting values using a map:

public class Person {
       private Optional<Car> car;

       public Optional<Car> getCar() {
              return car;
       }
}

public class Car {

       private Optional<Insurance> insurance;

       public Optional<Insurance> getInsurance() {
              return insurance;
       }
}

public class Insurance {

       private String name;

       public String getName() {
              return name;
       }
}

Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);

This is similar to the Stream API but instead of many values, here it has a single element.

Chaining Optional Objects with flatMap:

We can get the values using the flatMap.The flatMap works same as it worked in stream.

       public static void main(String[] args) {
              Person person = new Person();
              Car car = new Car();
              Insurance insurance = new Insurance();
              insurance.setName("PQR");
              Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
              car.setInsurance(optInsurance);
              Optional<Car> optCar = Optional.ofNullable(car);
              person.setCar(optCar);
              Optional<Person> optPerson = Optional.ofNullable(person);
              System.out.println(optPerson.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName().get());
       }
We can also provide a default value using the orElse.

System.out.println(optPerson.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown"));

Serialization using Optional:

It does not support serialization and in order to use it for serialization we can use it as below.

public class Person {
       private Car car;

       public Optional<Car> getCar() {
              return Optional.ofNullable(car);
       }
}

Filtering using Optional:

Similar to stream we can filter the values from the Optional.

System.out.println(optInsurance.filter(insurance1 -> "PQR".equals(insurance1.getName()))

Summary : Important methods in Optional.

  1. empty : This returns an empty Optional instance.
  2. filter : If the value is present returns an Optional or else returns an empty.
  3. flatMap : This would flat the structure Optional<Optional<String>> to Optional<String>.
  4. get :  Returns the value in Optional instance or else gives a NosuchElementException and hence this should not be used v ery oftenly.
  5. ifPresent : if the value is present then invokes the Consumer that is provided to it otherwise does nothing.
  6. isPresent : Returns true if there is a value present else returns false.
  7. map : if the value is present applies the corresponding map function. 
  8. of : Returns an Optional wrapping the value or else throws a NullPointer Exception.
  9. ofNullable : Returns an Optional wrapping the value else empty Optional if the value is null.
  10. orElse : Returns the value if present or the given default values.
  11. orElseGet : This is the lazy counterpart of orElse.
  12. orElseThrow : Its similar to get but in case it allows you to select the type of exception you want to throw.
Logging:

 In Java 8 we can log the processing of the stream using the peek method.

public static void main(String[] args) {

List<Integer> result = Stream.of(2, 3, 4, 5)
.peek(x -> System.out.println("taking from stream: " + x)).map(x -> x + 17)
.peek(x -> System.out.println("after map: " + x)).filter(x -> x % 2 == 0)
.peek(x -> System.out.println("after filter: " + x)).limit(3)
.peek(x -> System.out.println("after limit: " + x)).collect(toList());
}



No comments:

Post a Comment