The latest version of Java, namely Java 8, has created
quite a stir. Not just because it brings in an enhancement over the previous
versions with its new Date API, repeating annotations, Java FX etc. but more so because it makes room for some
interesting features of functional programming paradigm in its otherwise truly
object oriented kingdom. Two of these features are Lambda and the Streams API.
In this article we are going to talk about Lambda.
Historically, the concept of Lambda traces back to Lambda
calculus formulated by Alonzo Church in the 1930s. In a crude interpretation a
Lambda can be thought of as representing a function, most often an anonymous
function. The concept slowly streamed into programming. Coming to think of it,
the advent of computers and programming languages to communicate with them was
paved by the need for performing complex mathematical calculations correctly
and quickly. Hence the conceptual bonding between mathematics and computer
programming. Anyways, it is useful to think of Lambdas as anonymous functions
that can be assigned to variables, passed as an argument to functions and can
be returned from functions. Many programming languages like Ruby, Python,
Scala, JavaScript etc. already have built-in Lambda functionality. And now,
Java 8 has accommodated it too. But Java, an inherently Objected Oriented
language did some very clever tricks to retain backward compatibility and make
this accommodation.
In the kingdom of Java everything is an object. But
Lambdas are functions. How do they get ‘citizenship’? Hence, the introduction
of the concept of a Functional Interface under which Lambdas could be classed. But before delving into its nitty-gritty there
is one important feature to take note of. Java 8 allows interfaces to have
implementation of methods, typed as “default” and “static” methods. These
implemented methods can be inherited and overridden just like the methods of
“Abstract” classes. Yes, that brings interfaces closer to “Abstract” classes,
but they are still interfaces. They still support “multiple-inheritance”,
“Abstract” classes don’t.
Example of default and static methods in Java 8
interfaces.
public interface myFunctionalInterface{
default void
method1(){}
static String doSomething(){ System.out.println("Do something");}
}
Now, a Functional Interface is a normal Java 8 interface
that has exactly one abstract method. Sometimes they are tagged as SAM, Single
Abstract Method. An annotation, namely “@FunctionalInterface” has also been
introduced to mark such interfaces. And this would prompt a compile time error
if the agreement is not adhered to, that is if more than one abstract method is
introduced in the interface.
@FunctionalInterface
public interface myFunctionalInterface2{
public void myAbstractMethod();
default void myDefaultMethod(){}
//public void mySecAbstractMethod(); throws compile time error
}
So the type of a Lambda maps to the signature of
the abstract method defined in the functional interface.This has been made possible because of the clever exploitation of InvokeDynamics, a feature introduced in Java 7.
Now that we are clear on the basics, let's get to know Lambdas through some short and simple sample programs to enable a stronger grip on the concept.
Case: Existing SAM Interfaces
There are interfaces in Java written prior to Java 8 that have only one abstract method. And guess what, a Lambda can be supplied to such functional interfaces.
Example: Runnable r = () -> System.out.println("Lambda for runnable interface");
r.run(); //prints "Lambda for runnable interface"
Case: Overriding default method
Example: @FunctionalInterface
public interface myInterface1(){
default void methodTest(){ System.out.println("Test Method");}
public void abstractMethod();
}
public interface myInterface2 extends myInterface1(){
default void methodTest(){
System.out.println("Overridden Test Method");
}
}
Case: Conflict scenario with default method
Example: @FunctionalInterface @FunctionalInterface
interface A{ interface B{
default void someMethod(){}; default void someMethod(){};
public void abstractMethodA(); public void abstractMethodB();
} }
Class AB implements A,B{
.............}
//A compile error would be thrown if class AB does not explicitly specify which 'someMethod()' it refers to OR provides its own implementation of 'someMethod()'
Case: Multiple-inheritance with default methods
Example: @FunctionalInterface
interface Parent{
default void testOne() {
System.out.println("Test Parent");
}
public void abstractMethod();
}
@FunctionalInterface
interface Child extends Parent{
@Override
default void testOne(){
System.out.println("Test Child");
}
public void abstractChildMethod();
}
class Test implements Child{
...........}
Invoking testOne() on an instance of Test class would print "Test Child"
Case: Local Variable access within Lambda
Local variable which are declared final or effectively final, meaning their values are not modified are accessible within Lambda.
Example: public void someMethod(){
int x = 10;
Runnable r = () -> System.out.println("The variable is: "+x);
r.run();
}
Case: Using one of the functional interfaces provided in
java.util.* package
There are some 43 functional interfaces provided in Java 8. Supplier, Consumer, Predicate etc. for ease of use by the developers. One can simply provide a Lambda that matches the signature of the abstract method defined in these interfaces.
Example: Consumer<Integer> consumer = x -> System.out.println("Printing x: "+ x);
List<Integer> list = Arrays.asList(34,22,12,456);
list.forEach(consumer);
List<Integer> list = Arrays.asList(34,22,12,456);
list.forEach(consumer);
Case: Using existing custom methods as Lambda
Example: @FunctionalInterface
public interface FuncInterface1 {
public String encodePassword(String passwd, int id);
}
public interface FuncInterface1 {
public String encodePassword(String passwd, int id);
}
public class SampleFunctionalIf{
public static FuncInterface1 sampleEncode(){
return (password,i) -> password.toLowerCase(); **
}
public static void doSomething(FuncInterface1 func){
String str = func.encodePassword("PassWord", 123);
System.out.println(str);
}
public static void main(String[] args){
doSomething((password,i) -> password.toUpperCase()); **
FuncInterface1 f = sampleEncode();
doSomething(f);
}
}
public static FuncInterface1 sampleEncode(){
return (password,i) -> password.toLowerCase(); **
}
public static void doSomething(FuncInterface1 func){
String str = func.encodePassword("PassWord", 123);
System.out.println(str);
}
public static void main(String[] args){
doSomething((password,i) -> password.toUpperCase()); **
FuncInterface1 f = sampleEncode();
doSomething(f);
}
}
Note: ** The underlined Lambdas map to the encodePassword() in the functional interface. Note that the signature of the Lambda has to match the signature of the abstract method.
Case: Method references
Lambdas also allow referencing methods of classes or class instances using the "::" <colon> operator.
Example: public class Person {
String firstName, lastName;
public Person(){
System.out.println("New Person created from default constructor");
String firstName, lastName;
public Person(){
System.out.println("New Person created from default constructor");
}
}
public interface PersonFactory<P extends Person> {
P create(String fname, String lname);
}
P create(String fname, String lname);
}
public class MethodRefSample1 {
public static void main(String[] args){
PersonFactory<Person> p = Person::new;
p.create("Carlie", "Hebdo");
}
}
public static void main(String[] args){
PersonFactory<Person> p = Person::new;
p.create("Carlie", "Hebdo");
}
}
So, that’s how Lambdas make programming much more
succinct, quick and easy. And of course, it could be used to write pretty
complex codes as well.
In no way do the above examples cover the length and breadth of Lambdas, but they do provide a quick peek at what Lambda in Java 8 is all about and what it is capable of.
The Streams API exploits Lambdas to make it much easier
to perform operations on Java Collections both sequentially and in parallel.
Will talk more on Streams in another article.
No comments:
Post a Comment