Java 8 – Tutorial

Java 8 is the most important releases that happened in java since the introduction of generics in Java 5. This tutorial is all you need to master Java 8 ( lambda expressions, functional interfaces, streams api , and more) to code like a power developer.

With its new features, Java 8, makes java language more richer in terms of functional programming style , better api’s and developer friendliness.


Why are we talking about java 8 even in 2019 ? The simple answer is, the features that got released in java 8 are those being widely used in any serious java development project nowadays. Hence mastering the java 8 features is a must for any java developer. This tutorial will help you in mastering the core of Java. Do comment and share the link if you find these lessons useful

Important Java 8 Features

The most important feature of java 8, is no doubt, the lambda expressions. That is not all, it has released many more features enriching the java language.

  • Lambda Expression
  • Functional Interfaces
  • Default Methods
  • Method References
  • Java Stream Processing APIs
  • New Date and Time API
  • Type Annotations
  • Parallel operations over collections
  • Introduction of Metaspace ( Memory management change)

Lambda Expression

Lambda expression known popularly as lambdas ( also called closures) is a function without a name i.e, an anonymous function that is not bound to a class.

Java makes a formal entry to the world of functional programming with this. Let us take an example and understand the code before lambdas and how to write better code with lambdas.

package com.stackrules.java;
/**
* An implementation of Runnable not using the Lambda syntax
*/
public class BeforeLambdas{
  public static void main(String[] args) {
    Runnable runnable = new Runnable() {
      public void run() {
        System.out.println(" All is well !");
      }
    };
    runnable.run();
  }
} 

The Runnable interface expects an implementation of the run method and for providing it , code above, uses the anonymous inner class syntax. Just to provide an implementation of run method, lots of boiler plate code is written. Yes, lots of code for ceremony.

Now, let us look how lambdas avoids this code clutter.

package com.stackrules.java;
/**
* An implementation of Runnable using the Lambda syntax
*/
public class UsingLambdas {
  public static void main(String[] args) {
    Runnable runnable = () -> System.out.println("All is well !");
    runnable.run();
  }
} 

This code is more compact, clean and is to the point. No unnecessary boiler plate code. You know now, how lambdas makes the code compact and easier to express in a functional way and not the least, with less lines of code. Let us explore the lambda expression written above in more detail.

Syntax of Lambda Expression

(parameters) -> expression

(parameters) -> { block of statements};

If parameters are empty ,

( ) -> expression or () -> { block of statements};

Rules of writing lambda expression

  • Lambda expression can have zero or more arguments and they are enclosed in () with , separator in case of multiple parameters – Example- (param) or (param, param) or () . If there is a single parameter, even the () can be omitted. For example -Runnable r = () -> System.out.println(“Hi”);
  • It is not required to explicitly declare the type of the parameter, compiler will automatically infer it if that can be understood from the context. If not, go ahead and specify the type
  • If the body of the lambda has more than one statement it needs to be enclosed in brackets { }. For example – Runnable r = () -> { int a = 0; System.out.println(“Hi “+a); };

There is more to lambda expression than generating anonymous classes automatically by the java system. It is to do with the invoke dynamic byte code introduced as part of java 7. What i meant is, lambdas are not just for convenience but they are handled in a better way by java.

Note: invokedynamic is used under the hood to implement lambda expressions and default methods

Functional interfaces needs to be looked along with lambda where it makes more sense. So let us explore it next.

Functional Interface

They are also called as Single Abstract Method Interfaces ( SAM Interfaces). They are meant to define only a single abstract method. You know already that an abstract method means just the method (function) declaration with out an implementation.

A functional interface representation.

@FunctionalInterface
interface Buy {
    Double price();
}

In java, there are many SAM /Functional interfaces. For instance, interface Runnable containing the lone method run(). Before lambdas, to implement these, we need to provide either anonymous classes or normal classes implementing the Runnable. Using lambdas, that is no longer the case.

Even if you don’t provide the @FunctionalInterface annotation , there is no problem. But, it is a good practice to do so. Because you will get errors during compilation time or in your IDE, if you accidentally violate the interface contract. For example, compiler will warn you if you add the second abstract method to the interface.

Java also allows providing default implementations of methods in interfaces using the default method syntax. Since default methods are not abstract, there is no problem in having default methods in a functional interface. We will look at default methods in later sections.

The confusing stuff

The Runnable is a functional interface as it has only one abstract method declaration. We also call the Comparator<T> interface as a FunctionalInterface. But if you look at declaration of Comparator, it is looks like this.

public interface Comparator<T> {
  int compare(T o1, T o2);
  boolean equals(Object obj);
}

Did you spot the problem ?. This interface declares two abstract functions and should have violated the SAM contract. But it is not the case, why ?

This is because the compiler is intelligent enough to know that the equals is overridden from the java.lang.Object and hence any class implementing the Comparator interface will provide an implementation of equals one way or other. Hence this method is NOT getting counted as an abstract method in the FunctionalInterface contract.

Any method of java.lang.Object that is overridden in the interface doesn’t count towards the interface’s abstract method count

package com.stackrules.java;
// The methods toString, equals , hashCode are overridden from 
//java.lang.Object and hence they don't count towards interfaces //abstract method count
@FunctionalInterface
public interface Buy {

    Double price();
    @Override
    String toString();
    @Override
    boolean equals(Object obj);
    @Override
    int hashCode();
}

Interface Buy is a valid FunctionalInterface and can be used in lambda expressions. Let us implement Buy and use it as a lambda.

package com.stackrules.java;

/**
 * Functional interface example
 */
public class FunctionalInterfaceExample {

    public static void main(String[] args) {
        Purchase purchase = new Purchase();
        Buy buy = () -> 10d;
        String response = purchase.completePurchase(buy);
        System.out.println(response);
        // You can also simplify it like this
        // String response = purchase.completePurchase(() -> 10d);
    }
}
class Purchase {
    String completePurchase(Buy buy) {
        return  "You bought product for -" +buy.price();
    }
}

See how easy it is with lambdas to implement the Comparator.

Comparator<String> c = (a,b) -> a.compareToIgnoreCase(b);
        System.out.println(c.compare("Hello","World"));
        System.out.println(c.compare("Hello","hello"));
        System.out.println(c.compare("Hello","Hello"));

References

Leave a Comment