Lambda Expressions in Java Part 2

Constructor ReferenceVariable ScopeComparator

In the previous blog we learned about lambda expressions, its syntax, functional interfaces, etc. If you want to know more about lambda expressions visit the blog Lambda Expressions in Java In this blog we will go through the remaining topics of Lambda Expression.


Constructor Reference in Java 8

Constructor Reference behaves the same way as method reference but the difference is that rather than referencing the method it maps the functional interface with the constructor of the class. It is a mechanism used to refer to a constructor without creating the class. It is constructed using a class name and a new keyword.

Syntax

<ClassName classobj>:: new

Let’s go through the example it will give us a more clear idea of the Constructor Reference

e.g.

public class ConstructorReferenceEx{
  ConstructorReferenceEx(){
    System.out.println("Constructor Reference Example");
  }
  public static void main (String args[]){
    ConstructorReferenceEx constEx = ConstructorReferenceEx :: new;
  }
}

Output

Constructor Reference Example

In the above code we have implemented a class wherein we have defined Constructor followed by the display statement and have passed some text in the println() method. In main() we have constructed the object of the Constructor using a :: operator and a new keyword.


Variable Scope

Many times we wish to be able to access variables from an enclosing method or class in a lambda expression. For example:

e.g.

interface functionalInterface {
  int displayValues(int no1, int no2);
  public String toString();
}
public class VariableScopeEx {
  public static void main(String[] args){
	  VariableScopeEx variableScopeEx = new VariableScopeEx();
	  variableScopeEx.displayResult();
  }
  public void displayResult(){
    functionalInterface getValues = (no1, no2) -> {
      System.out.println("toString() method call: " +   this.toString());
      return no1 * no2;
    };
    System.out.println("Result is " +    getValues.displayValues(9, 8));
  }
  @Override
  public String toString() {
    System.out.println("In VariableScopeEx call");
	return super.toString();
  }
}

In the above example we have defined the interface wherein we have declared two methods: first is the displayValues() method and second is the toString() method. In the VariableScopeEx class, we have created an object of that class and with the help of that object, we have called displayResult which is defined in the same class. In the displayResult() we have a lambda expression defined which returns the multiplication of two numbers.

In the Lambda expression, the value of a variable can be captured in the enclosing scope. In order to make sure that the captured value is well defined, a crucial restriction is defined in Java i.e. In lambda expressions we can only use the variables whose value is fixed(doesn’t change). Also, the variables that lambda expressions use are called effectively final. Effectively final variables are those whose values don't change after initialization. But if we try to change its values then the compiler will throw an error.

e.g.

interface functionalInterface {
  int displayValues(int no1, int no2);
}

public class VariableScopeEx {
  public static void main(String[] args){
    int numValue = 9;
    System.out.println("The value of number: " +  numValue);
    functionalInterface getValues = (no1, no2) -> {
      numValue = no1 * no2;
      return numValue;
    };
  }
}

If the lambda expression does not have any parameter we can provide empty parentheses just like with the no parameter method.

Output

VariableScopeEx.java:10: error: local variables referenced from a lambda expression must be final or effectively final
  numValue = no1 * no2;

In the above example we have initialized the variable numValue with 9 and we are again trying to reinitialize it by assigning the result of no1 * no2 which will give an error.

Like nested blocks, the body of the lambda expression also has the same scope. The same principles apply to name conflicts and shadowing. It is invalid to declare local variables or parameters having the same name as the local variables in a lambda expression.

e.g.

interface functionalInterface {
  int displayValues(int no1, int no2);
}

public class VariableScopeEx {
  public static void main(String[] args){
    int numValue;
    functionalInterface getValues = (no1, no2) -> {
      int numValue;
      return numValue;
    };
  }
}

Output

Lambda expression’s local variable numValue cannot redeclare another local variable defined in the same enclosing scope

Comparator Interface

As we have already seen in Interface in Java blog, we can sort an array of objects, but they should be the object of classes that implements the Comparable interface. We can sort the array of strings as the String class implements Comparable<String> and compareTo() method of String class(String.compareTo()) compares strings in alphabetical order. Now suppose there is a situation where we want to sort the string by incrementing the length rather than sorting in alphabetical order. We cannot have the String class implement the compareTo() method in two ways. In order to deal with this situation, we can use Collections.sort() method which has the parameters as an array and comparator(an object of a class that implements the Comparable interface)

Syntax

public interface Comparator<ClassName>{
  int compare(ClassName first, ClassName second);
}

e.g.

import java.io.*;
import java.lang.*;
import java.util.*;

class AtrowelEmployee {
  int empId;
  String empName, address;
  public AtrowelEmployee(int empId, String empName, String address){
    this.empId = empId;
    this.empName = empName;
    this.address = address;
  }
  public String toString(){
    return this.empId + " " + this.empName + " "+ this.address;
  }
}

class SortByEmpId implements Comparator<AtrowelEmployee> {
  public int compare(AtrowelEmployee empObj1, AtrowelEmployee empObj2){
    return empObj1.empId - empObj2.empId;
  }
}

class SortbyEmpName implements Comparator<AtrowelEmployee> {
  public int compare(AtrowelEmployee empObj1, AtrowelEmployee empObj2){
    return empObj1.empName.compareTo(empObj2.empName);
  }
}

class TestSortEx {
  public static void main(String[] args){
    ArrayList<AtrowelEmployee>empArr = new  ArrayList<AtrowelEmployee>();
    empArr.add(new AtrowelEmployee(101, "John", "New York"));
    empArr.add(new AtrowelEmployee(102, "Smith", "Canada"));
    empArr.add(new AtrowelEmployee(103, "Charles", "London"));
    System.out.println("Record displaying in unsorted order ");
      for (int i = 0; i < empArr.size(); i++)
        System.out.println(empArr.get(i));
      Collections.sort(empArr, new SortByEmpId());
      System.out.println("Sorted by Emp Id");
      for (int i = 0; i < empArr.size(); i++)
        System.out.println(empArr.get(i));
      Collections.sort(empArr, new SortbyEmpName());
      System.out.println("Sorted by Name");
      for (int i = 0; i < empArr.size(); i++)
        System.out.println(empArr.get(i));
  }
}

Output

Record displaying in unsorted order
101 John New York
102 Smith Canada
103 Charles London
Sorted by Emp Id
101 John New York
102 Smith Canada
103 Charles London
Sorted by Name
103 Charles London
101 John New York
102 Smith Canada

We can sort the elements in any order as we wish just by changing the return value inside the compare() method. Now suppose we want to sort the collection by more than one field then i.e. firstly if we want to sort by Employee Name and secondly by Employee experience, we can do it as follows:

e.g.

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

class AtrowelEmployee {
  String empName;
  int experience;

  public AtrowelEmployee(String empName, Integer experience){
    this.empName = empName;
    this.experience = experience;
  }

  public String getEmpName() {
    return empName;
  }

  public void setEmpName(String empName) {
    this.empName = empName;
  }

  public Integer getExperience() {
    return experience;
  }

  public void setExperience(Integer experience) {
    this.experience = experience;
  }

  @Override
  public String toString(){
    return "Employee{"+ "Name=" + empName + ", Experience=" + experience + '}';
  }

  static class EmployeeSortingComparator implements Comparator<AtrowelEmployee> {
    @Override
    public int compare(AtrowelEmployee emp1,AtrowelEmployee emp2){
      int NameCompare = emp1.getEmpName().compareTo(emp1.getEmpName());
      int AgeCompare = emp1.getExperience().compareTo(emp1.getExperience());
      return (NameCompare == 0) ? AgeCompare: NameCompare;
    }
  }

  public static void main(String[] args){
    List<AtrowelEmployee> empList = new ArrayList<>();
    AtrowelEmployee obj1 = new AtrowelEmployee("John", 5);
    AtrowelEmployee obj2 = new AtrowelEmployee("Chales", 10);
    AtrowelEmployee obj3 = new AtrowelEmployee("Smith", 2);
    AtrowelEmployee obj4 = new AtrowelEmployee("Johnson", 15);
    empList.add(obj1);
    empList.add(obj2);
    empList.add(obj3);
    empList.add(obj4);
    Iterator<AtrowelEmployee> custIterator = empList.iterator();
    System.out.println("Before Sorting the List:");
    while (custIterator.hasNext()) {
      System.out.println(custIterator.next());
    }
    Collections.sort(empList, new EmployeeSortingComparator());
    System.out.println("After Sorting the List:");
    for (AtrowelEmployee emp : empList) {
      System.out.println(emp);
    }
  }
}
Get in Touch

Atrowel will be pleased to receive your feedback and suggestions. Those of you who would like to contribute, please send us an email.