Sunday, 8 September 2019

new features added in java8

Question1 ➽ what are new features added in java8?


Java 8 has been released in March 2014, so it’s one of the hot topic in java interview questions. Java 8 has been one of the biggest release after Java 5 annotations and generics. Some of the important features of Java 8 are:
A.    Interface changes with default and static methods
B.     Functional interfaces and Lambda Expressions
C.     Java Stream API for collection classes
D.    Java Date Time API

Interface changes with default and static methods

One of the biggest design change in Java 8 is with the concept of interfaces. Prior to Java 7, we could have only method declarations in the interfaces. But from Java 8, we can have default methods and static methods in the interfaces.

Designing interfaces have always been a tough job because if we want to add additional methods in the interfaces, it will require change in all the implementing classes. As interface grows old, the number of classes implementing it might grow to an extent that it’s not possible to extend interfaces. That’s why when designing an application, most of the frameworks provide a base implementation class and then we extend it and override methods that are applicable for our application.
Let’s look into the default and static interface methods and the reasoning of their introduction.

Interface Default Method

For creating a default method in the interface, we need to use “default” keyword with the method signature. For example,
public interface Interface1 {

    void method1(String str);
     
    default void log(String str){
        System.out.println("I1 logging::"+str);
        print(str);
    }
}
Notice that log(String str) is the default method in the Interface1. Now when a class will implement Interface1, it is not mandatory to provide implementation for default methods. This feature will help us in extending interfaces with additional methods, all we need is to provide a default implementation.


Let’s say we have another interface with following methods:
package com.journaldev.java8.defaultmethod;

public interface lt {

    void method2();
     
    default void log(String str){
        System.out.println("I2 logging::"+str);
    }

}

We know that Java doesn’t allow us to extend multiple classes because it will result in the “Diamond Problem” where compiler can’t decide which superclass method to use. With the default methods, the diamond problem would arise for interfaces too. Because if a class is implementing both Interface1 andInterface2 and doesn’t implement the common default method, compiler can’t decide which one to chose.
Extending multiple interfaces are an integral part of Java, you will find it in the core java classes as well as in most of the enterprise application and frameworks. So to make sure, this problem won’t occur in interfaces, it’s made mandatory to provide implementation for common default methods. So if a class is implementing both the above interfaces, it will have to provide implementation for log() method otherwise compiler will throw error.


public class MyClass implements Interface1, Interface2 {

    @Override
    public void method2() {
    }

    @Override
    public void method1(String str) {
    }

    @Override
    public void log(String str){
        System.out.println("MyClass logging::"+str);
        Interface1.print("abc");
    }
}


Important points about interface default methods:

1.      Default methods will help us in extending interfaces without having the fear of breaking implementation classes.
2.      Default methods has bridge down the differences between interfaces and abstract classes.
3.      Default methods will help us in avoiding utility classes, such as all the Collections class method can be provided in the interfaces itself.
4.      Default methods will help us in removing base implementation classes, we can provide default implementation and the implementation classes can chose which one to override.
5.      One of the major reason for introducing default methods is to enhance the Collections API in Java 8 to support lambda expressions.
·         Lambda expression are essentially of type of functional interface. To support lambda expressions seamlessly, all core classes have to be modified. But these core classes like java.util.List are implemented not only in JDK classes, but also in thousands of client code as well. Any incompatible change in core classes will back fire for sure and will not be accepted at all.
·         Default methods break this deadlock and allow adding support for functional interface in core classes. 


Let’s see an example. Below is a method which has been added to java.lang.Iterable.
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}



Before java 8, if you had to iterate on a java collection then your would get an iterator instance and call it’s next method until hasNext() returns false. This is common code and have been used thousands of time in day to day programming by us. Syntax is also always same. So can we make it compact so that it takes only single line of code and still do the job for us as before. Above function does that.
public class Animal implements Moveable{
    public static void main(String[] args){
        List<Animal> list = new ArrayList();
        list.add(new Animal());
        list.add(new Animal());
        list.add(new Animal());
         
        //Iterator code reduced to one line
        list.forEach((Moveable p) -> p.move());
    }
}
public interface Moveable {
    default void move(){
        System.out.println("I am moving");
    }
}



6.      If any class in the hierarchy has a method with same signature, then default methods become irrelevant. A default method cannot override a method from java.lang.Object. The reasoning is very simple, it’s because Object is the base class for all the java classes. So even if we have Object class methods defined as default methods in interfaces, it will be useless because Object class method will always be used. That’s why to avoid confusion, we can’t have default methods that are overriding Object class methods.
7.      Default methods are also referred to as Defender Methods or Virtual extension methods.

Interface static methods


Static methods are similar to default methods except that we can’t override them in the implementation classes. This feature helps us in avoiding undesired results incase of poor implementation in child classes. Let’s look into this with a simple example.

public interface MyData {

    default void print(String str) {
        if (!isNull(str))
            System.out.println("MyData Print::" + str);
    }
    
    static boolean isNull(String str) {
        System.out.println("Interface Null Check");

        return str == null ? true : "".equals(str) ? true : false;
    }
}



public class MyDataImpl implements MyData {

    public boolean isNull(String str) {
        System.out.println("Impl Null Check");

        return str == null ? true : false;
    }
     
    public static void main(String args[]){
        MyDataImpl obj = new MyDataImpl();
        obj.print("");
        obj.isNull("abc");
    }
}
Note that isNull(String str) is a simple class method, it’s not overriding the interface method. For example, if we will add @Override annotation to the isNull() method, it will result in compiler error.
Now when we will run the application, we get following output
Interface Null Check
Impl Null Check
If we make the interface method from static to default, we will get following output.
1
2
3
Impl Null Check
MyData Print::
Impl Null Check
The static methods are visible to interface methods only, if we remove the isNull() method from theMyDataImpl class, we won’t be able to use it for the MyDataImpl object. However like other static methods, we can use interface static methods using class name. For example, a valid statement will be:
1
boolean result = MyData.isNull("abc");
Important points about interface static methods:
1.      Interface static methods are part of interface, we can’t use it for implementation class objects.
2.      Interface static methods are good for providing utility methods, for example null check, collection sorting etc.
3.      Interface static method helps us in providing security by not allowing implementation classes to override them.
4.      We can’t define static methods 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.
5.      We can use static interface methods to remove utility classes such as Collections and move all of it’s static methods to the corresponding interface, that would be easy to find and use.


Functional Interfaces

An interface with exactly one abstract method is known as Functional Interface.
A new annotation @FunctionalInterface has been introduced to mark an interface as Functional Interface. @FunctionalInterface annotation is a facility to avoid accidental addition of abstract methods in the functional interfaces. It’s optional but good practice to use it.
Functional interfaces are long awaited and much sought out feature of Java 8 because it enables us to uselambda expressions to instantiate them. A new package java.util.function with bunch of functional interfaces are added to provide target types for lambda expressions and method references.

Java has always been an Object Oriented Programming language. What is means that everything in java programming revolves around Objects (except some primitive types for simplicity). We don’t have only functions in java, they are part of Class and we need to use the class/object to invoke any function.
If we look into some other programming languages such as C++, JavaScript; they are called functional programming language because we can write functions and use them when required. Some of these languages support Object Oriented Programming as well as Functional Programming.
Being object oriented is not bad, but it brings a lot of verbosity to the program. For example, let’s say we have to create an instance of Runnable. Usually we do it using anonymous classes like below.

1
2
3
4
5
Runnable r = new Runnable(){
            @Override
            public void run() {
                System.out.println("My Runnable");
            }};
If you look at the above code, the actual part that is of use is the code inside run() method. Rest all of the code is because of the way java programs are structured.
Java 8 brings us the concept of Functional Interfaces and Lambda Expressions to avoid writing all the useless code that we can easily avoid by making our java compiler intelligent.

Java 8 has defined a lot of functional interfaces in java.util.function package, some of the useful ones are Consumer, Supplier, Function and Predicate.


Since there is only one abstract function in the functional interfaces, there is no confusion in applying the lambda expression to the method. Lambda Expressions syntax is (argument) -> (body). Now let’s see how we can write above anonymous Runnable using lambda expression.
1
Runnable r1 = () -> System.out.println("My Runnable");

 

Lambda Expressions
Lambda Expressions are the way through which we can visualize functional programming in the java object oriented world. Objects are the base of java programming language and we can never have a function without an Object, that’s why Java language provide support for using lambda expressions only with functional interfaces.
Since there is only one abstract function in the functional interfaces, there is no confusion in applying the lambda expression to the method. Lambda Expressions syntax is (argument) -> (body). Now let’s see how we can write above anonymous Runnable using lambda expression.
1
Runnable r1 = () -> System.out.println("My Runnable");
Let’s try to understand what is happening in the lambda expression above.
·         Runnable is a functional interface, that’s why we can use lambda expression to create it’s instance.
·         Since run() method takes no argument, our lambda expression also have no argument.
·         Just like if-else blocks, we can avoid curly braces ({}) since we have a single statement in the method body. For multiple statements, we would have to use curly braces like any other methods.
Why do we need Lambda Expressions
1.      Reduced Lines of Code
One of the clear benefit of using lambda expression is that the amount of code is reduced, we have already seen that how easily we can create instance of a functional interface using lambda expression rather than using anonymous class.
2.      Sequential and Parallel Execution Support
Another benefit of using lambda expression is that we can benefit from the Stream API sequential and parallel operations support.
To explain this, let’s take a simple example where we need to write a method to test if a number passed is prime number or not.
Traditionally we would write it’s code like below. The code is not fully optimized but good for example purpose, so bear with me on this.
/Traditional approach
private static boolean isPrime(int number) {       
    if(number < 2) return false;
    for(int i=2; i<number; i++){
        if(number % i == 0) return false;
    }
    return true;
}
The problem with above code is that it’s sequential in nature, if the number is very huge then it will take significant amount of time. Another problem with code is that there are so many exit points and it’s not readable. Let’s see how we can write the same method using lambda expressions and stream API.

//Declarative approach
private static boolean isPrime(int number) {       
    return number > 1
            && IntStream.range(2, number).noneMatch(
                    index -> number % index == 0);
}
IntStream is a sequence of primitive int-valued elements supporting sequential and parallel aggregate operations. This is the int primitive specialization of Stream.

For more readability, we can also write the method like below.

private static boolean isPrime(int number) {
    IntPredicate isDivisible = index -> number % index == 0;
     
    return number > 1
            && IntStream.range(2, number).noneMatch(
                    isDivisible);
}

3. Higher Efficiency with Laziness

One more advantage of using lambda expression is the lazy evaluation, for example let’s say we need to write a method to find out the maximum odd number in the range 3 to 11 and return square of it.
Usually we will write code for this method like this:
private static int findSquareOfMaxOdd(List<Integer> numbers) {
        int max = 0;
        for (int i : numbers) {
            if (i % 2 != 0 && i > 3 && i < 11 && i > max) {
                max = i;
            }
        }
        return max * max;
    }
Above program will always run in sequential order but we can use Stream API to achieve this and get benefit of Laziness-seeking. Let’s see how we can rewrite this code in functional programming way using Stream API and lambda expressions.

public static int findSquareOfMaxOdd(List<Integer> numbers) {
        return numbers.stream()
                .filter(NumberTest::isOdd)      //Predicate is functional interface and
                .filter(NumberTest::isGreaterThan3) // we are using lambdas to initialize it
                .filter(NumberTest::isLessThan11)   // rather than anonymous inner classes
                .max(Comparator.naturalOrder())
                .map(i -> i * i)
                .get();
    }

    public static boolean isOdd(int i) {
        return i % 2 != 0;
    }

    public static boolean isGreaterThan3(int i){
        return i > 3;
    }
     
    public static boolean isLessThan11(int i){
        return i < 11;
    }

If you are surprised with the double colon (::) operator, it’s introduced in Java 8 and used for method references. Java Compiler takes care of mapping the arguments to the called method. It’s short form of lambda expressions i -> isGreaterThan3(i) or i -> NumberTest.isGreaterThan3(i).

4.Method and Constructor References

A method reference is used to refer to a method without invoking it; a constructor reference is similarly used to refer to a constructor without creating a new instance of the named class or array type.
Examples of method and constructor references:
System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new

No comments:

Post a Comment

Note: only a member of this blog may post a comment.