Lambda expression


what is lambda expression?

In programming, a Lambda expression (or function) is just an anonymous function, i.e., a function with no name and without being bounded to an identifier. In other words, lambda expressions are nameless functions given as constant values, and written exactly in the place where it’s needed, typically as a parameter to some other function.

lambda expression <- functional interface
                           |      \     \
                           |       \     \
                        forEach  runable  sort

What's the relationship between lambda expression and functional interface?

Single Abstract Method interfaces (SAM Interfaces) is not a new concept. It means interfaces with only one single method. In java, we already have many examples of such SAM interfaces. From java 8, they will also be referred as functional interfaces as well. Java 8, enforces the rule of single responsibility by marking these interfaces with a new annotation i.e. @FunctionalInterface.

For example, new definition of Runnable interface is like this:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Another example, Comparator interface has two abstract method, equals() and compare(), but actually equals does count towards the interface's abstract method count. because this abstract method overrides one of the public methods of java.lang.Object. refer to: https://tinyurl.com/y9pujo6a other notable method to which this rule applies are toString() and hashCode.

We know that Lambda expressions are anonymous functions with no name and they are passed (mostly) to other functions as parameters. Well, in java method parameters always have a type and this type information is looked for to determine which method needs to be called in case of method overloading or even simple method calling. So, basically every lambda expression also must be convertible to some type to be accepted as method parameters. Well, that type in which lambda expressions are converted, are always of functional interface type.

Let’s understand it with an example. If we have to write a thread which will print “howtodoinjava” in console then simplest code will be:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("howtodoinjava");
    }
}).start();

If we use the lambda expression for this task then code will be :

new Thread(
            () ->   {
                        System.out.println("My Runnable");
                    }
         ).start();

We have also see that Runnable is an functional interface with single method run(). So, when you pass lambda expression to constructor of Thread class, compiler tries to convert the expression into equivalent Runnable code as shown in first code sample. If compiler succeed then everything runs fine, if compiler is not able to convert the expression into equivalent implementation code, it will complain. Here, in above example, lambda expression is converted to type Runnable. In simple words, a lambda expression is an instance of a functional interface. But a lambda expression itself does not contain the information about which functional interface it is implementing; that information is deduced from the context in which it is used.

some examples:

1) Iterating over a List and perform some operations

List<String> pointList = new ArrayList();
pointList.add("1");
pointList.add("2");

pointList.forEach(p ->  {
                            System.out.println(p);
                            //Do more work
                        }
                 );

note:

2) Create a new runnable and pass it to thread

new Thread(
    () -> System.out.println("My Runnable");
).start();

3) Sorting employees objects by their name

public class LambdaIntroduction {

  public static void main (String[] ar){
          Employee[] employees  = {
              new Employee("David"),
              new Employee("Naveen"),
              new Employee("Alex"),
              new Employee("Richard")};

          System.out.println("Before Sorting Names: "+Arrays.toString(employees));
          Arrays.sort(employees, Employee::nameCompare);
          System.out.println("After Sorting Names "+Arrays.toString(employees));
      }
}

class Employee {
  String name;

  Employee(String name) {
    this.name = name;
  }

  public static int nameCompare(Employee a1, Employee a2) {
    return a1.name.compareTo(a2.name);
  }

  public String toString() {
    return name;
  }
}

Output:

Before Sorting Names: [David, Naveen, Alex, Richard]
After Sorting Names [Alex, David, Naveen, Richard]

forEach

the java docs: public void forEach(Consumer<? super E> action)

forEach stats that it “performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.” And so, with forEach, we can iterate over a collection and perform a given action on each element – by just passing a class that implements the Consumer interface.

The Consumer interface is a functional interface. It accepts an input and returns no result.

@FunctionalInterface
public interface Consumer {
    void accept(T t);
}

with that in mind, we have following example:

List<String> names = new ArrayList<>();
names.add("Larry");
names.add("Steve");
names.add("James");
names.add("Conan");
names.add("Ellen");

names.forEach(new Consumer<String>() {
    public void accept(String name) {
        System.out.println(name);
    }
});

We can create an anonymous inner class that implements the Consumer interface and then define the action to perform on each element in the collection (in this case just printing out the name). This works well but if we analyze at the example above we’ll see that the actual part that is of use is the code inside the accept() method.When we use lambda expression, we can simplify the code as :

names.forEach(name -> System.out.println(name));

Method Reference: In a case where a method already exists to perform an operation on the class, this syntax can be used instead of the normal lambda expression syntax:

names.forEach(System.out::println);

forEach vs for-loop

  • Internal Iterator
    • This type of iterator manage the iteration in the background and leaves the programmer to just code what is meant to be done with the elements of the collection, rather than managing the iteration and making sure that all the elements are processed one-by-one.
    • e.g names.forEach(name -> System.out.println(name));
    • In the forEach method above, we can see that the argument provided is a lambda expression. This means that the method only needs to know what is to be done and all the work of iterating will be taken care of internally.
  • External Iterator
    • External iterators mix the what and the how the loop is to be done. Enumerations, Iterators and enhanced for-loop are all external iterators (remember the methods iterator(), next() or hasNext() ? ). In all these iterators it’s our job to specify how the iteration will be performed.
    • e.g. for (String name : names) { System.out.println(name); }

Comparator

Comparator itself is functional interface because it only has one abstract method,

also, Comparator.comparing(function<>...) the parameter is an abstract method,

So we can have two ways to implement customized comparator.

//Compare by Id
Comparator<Employee> compareById_1 = Comparator.comparing(e -> e.getId());

Comparator<Employee> compareById_2 = (Employee o1, Employee o2) -> o1.getId().compareTo( o2.getId() );

//Compare by firstname
Comparator<Employee> compareByFirstName = Comparator.comparing(e -> e.getFirstName());

//how to use comparator
Collections.sort(employees, compareById);

Some particular functional interfaces

refer to: https://www.baeldung.com/java-8-functional-interfaces

Functions

The most simple and general case of a lambda is a functional interface with a method that receives one value and returns another. This function of a single argument is represented by the Function interface which is parameterized by the types of its argument and a return value:

public interface Function<T, R> { … }

One of the usages of the Function type in the standard library is the Map.computeIfAbsent method that returns a value from a map by key but calculates a value if a key is not already present in a map. To calculate a value, it uses the passed Function implementation:

Map<String, Integer> nameMap = new HashMap<>();
Integer value = nameMap.computeIfAbsent("John", s -> s.length());

note: HashMap.computeIfAbsent() how to use that?

V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
 If the specified key is not already associated with a value \(or is mapped to null\), attempts to compute its value using the given    mapping function and enters it into this map unless null.

Remember that an object on which the method is invoked is, in fact, the implicit first argument of a method, which allows casting an instance method length reference to a Function interface:

Integer value = nameMap.computeIfAbsent("John", String::length);

Two-Arity Function Specializations

To define lambdas with two arguments, we have to use additional interfaces that contain “Bi” keyword in their names: BiFunction, ToDoubleBiFunction, ToIntBiFunction, and ToLongBiFunction. BiFunction has both arguments and a return type generified, while ToDoubleBiFunction and others allow you to return a primitive value. One of the typical examples of using this interface in the standard API is in the Map.replaceAll method, which allows replacing all values in a map with some computed value. Let’s use a BiFunction implementation that receives a key and an old value to calculate a new value for the salary and return it.

Map<String, Integer> salaries = new HashMap<>();
salaries.put("John", 40000);
salaries.put("Freddy", 30000);
salaries.put("Samuel", 50000);

salaries.replaceAll((name, oldValue) -> 
  name.equals("Freddy") ? oldValue : oldValue + 10000);

note: how to use replace() and replaceAll()?

for the String class, 
    String replace(char oldChar, char newChar)      //  replace('.', '\\');
    String replaceAll(String regex, String replacement)      //  replaceAll("\\.", "\\\\");

for the HashMap class, 
    V replace(K key, V value)    
    void replaceAll(BiFunction<? super K,? super V,? extends V> function)

Suppliers

The Supplier functional interface is yet another Function specialization that does not take any arguments. It is typically used for lazy generation of values. For instance, let’s define a function that squares a double value. It will receive not a value itself, but a Supplier of this value:...

...

Consumers

...

Predicates

...

Default method

what are default methods in java 8?

Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces. As name implies, default methods in java 8 are simply default. If you do not override them, they are the methods which will be invoked by caller classes. They are defined in interfaces.

public interface Moveable {
    default void move(){
        System.out.println("I am moving");
    }
}

public class Animal implements Moveable{
    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();
    }
}

Output: I am moving

Why default methods were needed in java 8?

This is a good candidate for your next interview question. Simplest answer is to enable the functionality of lambda expression in java. 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 for backwards compatibility refer to: https://medium.com/@benweidig/java-8-interfaces-default-methods-for-backwards-compatibility-2767a6a70947

How conflicts are resolved while calling default methods?

Rules for this conflict resolution are as follows:

1) Most preferred are the overridden methods in classes. They will be matched and called if found before matching anything.

2) The method with the same signature in the “most specific default-providing interface” is selected. This means if class Animal implements two interfaces i.e. Moveable and Walkable such that Walkable extends Moveable. Then Walkable is here most specific interface and default method will be chosen from here if method signature is matched.

3) If Moveable and Walkable are independent interfaces then a serious conflict condition happen, and compiler will complain then it is unable to decide. The you have to help compiler by providing extra info that from which interface the default method should be called. e.g.

results matching ""

    No results matching ""