How to write Lambda expressions for functional interfaces

A lambda expression is basically implementation of a method written in shorthand. To begin with, assume that you have the following class and a method that uses java.util.function.Predicate
public class Data{
    int value;
    Data(int value){
        this.value = value;
    }
}

public void printUsefulData(ArrayList dataList, Predicate p){
   for(Data d: dataList){
        if(p.test(d)) System.out.println(d.value);
   }
}
Before Java 8, you would have to create a class that implements Predicate interface and pass an instance of that class to the printUsefulData method. Something like this -
printUsefulData(al,  new Predicate(){ 
                            public boolean test(Data d){ 
                                 return d.value>1; 
                             }   }   );
You can see that the above construct allows you to customize the logic of Predicate's test method anywhere you want to call printUsefulData method. The whole point of writing lambda expressions is to let you do the same thing but without typing too much code. So all you are doing with lambda expressions is removing the bits and pieces of the code that are redundant. Afterall, why type stuff that the compiler can figure out on its own, right?

If you look at the code above, you can see that "new Predicate()" part is actually redundant. The compiler already knows that Predicate is an interface and we can only pass an instance of some class that implements this interface. It also knows that this interface has only one method. That means, the only unknown information that we are giving to the compiler is the method implementation. The compiler can easily wrap up the method code into an anonymous class on its own.
Now, lets take a look at the test method that we wrote above -
public boolean test(Data d){
    return d.value>2;
}
The important elements of the above code are - return type, method name, parameter list, and the method body. When you write a lambda expression, you are essentially providing an implementation of the same method. Therefore, the lambda expression code that you need to write must contain all the pieces of the above code except the ones that the compiler can figure out on its own. For a functional interface such as Predicate, the compiler already knows the name of the method that you are going to implement because there is only one abstract method in the interface. This fact drives the basic syntax of a lambda expression -
(parameter list) -> { method body }
That's it. This is all that you need to provide to the compiler and it can create the whole implementation of the interface on its own. Notice that there is no method name here. Thus, a valid call using a lambda expression would be -
printUsefulData(al,  (Data d) -> { return d.value>2; } );
But there is even more that a compiler can figure out on its own. It also knows the parameter types of the method. Thus, you can also write -
printUsefulData(al,  (d) -> { return d.value>2; });
If your method body just returns the value of an expression, the compiler can put the return keyword on its own. Thus, you can also write -
printUsefulData(al, (d) -> d.value>2);
Notice that the curly braces, return keyword, and the semi-colon at the end of the expression are gone. If there is only one parameter, there is no need for ( ) in the parameter list either. Thus, you can write -
printUsefulData(al, d->d.value>2);
Let us see some examples of invalid lambda expressions -

printUsefulData(al, d->return d.value>2); INVALID - If you write return, the compiler assumes that you are writing the complete method body and so it expects curly braces and the semi-colon i.e. printUsefulData(al, d-> {return d.value>2;}).

printUsefulData(al, Data d->d.value>2); INVALID - If you write parameter type, the compiler assumes that you are writing the complete parameter list of the method and so it expects the brackets i.e. printUsefulData(al, (Data d) -> d.value>2);.

Lambda expressions for an interface with a method that takes multiple parameters can be written like this: someMethod(al, (Type1 a, Type2 b) -> methodbody ); For example, if you have,
interface I1{
  void m1(int a, int b);
}
and
public static void someMethod(ArrayList dataList, I1 p){
   for(Data d: dataList){
        p.m1(d.value, d.value);
   }
}    
You can call it like this - someMethod(al, (x, y) -> System.out.println(x*y));
But someMethod(al, x, y ->d.value>2); is INVALID because if you have multiple parameters, the compiler cannot associate the -> sign with both the parameters without brackets. You should now write some simple interfaces and methods and call them using lambda expressions. Try out various combinations to see what works and what doesn't. For the purpose of OCA-JP8 exam, this article is enough but you can go through Oracle's official tutorial on Lambda expressions to learn more.