Java 8 introduces new features such usages of lambdas and functional interfaces. This enables methods to be first class citizen in Java and allows it to be assigned to variables and passed around

Lambdas

Lambdas are functions or methods without a name being assigned to it. It also referred to as Anonymous functions. Syntax of a lambda expression. It contains a input params, arrow and a body. () -> {}

Usages We can use lambdas to help with our functional interfaces implementation such as Comparator or Runnable

Comparator<Integer> comparatorLambda = (a,b) -> a.compareTo(b);
System.out.println(comparatorLambda.compare(5,6)); // returns -1 since it's not the same

Syntatic sugar

  • () -> single statement or expression; don't require curly braces
  • () -> {multiple statements}; curly braces are required for multiple statements
  • a -> a + 1; don't require parentheses when it's only 1 input parameter
  • (a,b) -> a-b; require parentheses when it's has multiple input parameters

Functional Interfaces

Functional Interface or also known single abstract method(SAM) is an interface with only 1 abstract method It can also be denoted by this annotation @FunctionalInterface. There are 4 main functional interfaces introduced in Java 8

  • Consumer
  • Predicate
  • Function
  • Supplier

Consumer & BiConsumer

  • Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.
@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
}

@FunctionalInterface
public interface BiConsumer<T,U> {
  void accept(T t, U u);
}
Helper class for functional interface example

@Getter @AllArgsConstructor public class Student{ private String name; private Integer gradeLevel; private double gpa; private String gender; private Integer noteBooks; private List activities; }

public class StudentRepository{ public static List getAllStudents() { return List.of( new Student(“Adam”, 2, 3.6, “male”, 10, List.of(“swimming”, “basketball”, “volleyball”)), new Student(“Jenny”, 2, 3.8, “female”, 11, List.of(“swimming”, “gymnastics”, “soccer”)), new Student(“Emily”, 3, 3.4, “female”, 12, List.of(“swimming”, “gymnastics”, “aerobics”)), new Student(“Dave”, 3, 3.4, “male”, 12, List.of(“swimming”, “gymnastics”, “aerobics”)), new Student(“Sophia”, 4, 4.3, “female”, 10, List.of(“swimming”, “dancing”, “football”)), new Student(“James”, 4, 4.6, “male”, 22, List.of(“swimming”, “basketball”, “baseball”, “football”)) ); } }

public class ConsumerExample {
    public static void main(String[] args) {
        Consumer<String> upperCaseConsumer = s -> System.out.println(s.toUpperCase());

        List<Student> studentList = StudentRepository.getAllStudents();
        studentList.forEach(student -> upperCaseConsumer.accept(student.getName()));

        BiConsumer<Integer,Integer> eligibility = (age, percentage) -> {
            if(age > 14 && percentage > 75){
                System.out.println("You're eligible to participate in school");
            }else{
                System.out.println("You're are not eligible");
            }
        };
        eligibility.accept(16,89);
    }
}

Output

ADAM
JENNY
EMILY
DAVE
SOPHIA
JAMES
You're eligible to participate in school

Predicate & BiPredicate

Represents a predicate (boolean-valued function) of one argument. This is a functional interface whose functional method is test(Object).

We can use predicates to create modular predicates and use it for plug and play situation

@FunctionalInterface
public interface Predicate<T> {
  boolean test(T t);
}

@FunctionalInterface
public interface BoPredicate<T,U> {
  boolean test(T t, U u);
}
public class PredicateExample {
    static Predicate<Student> filterByMaleGender = student -> student.getGender().equals("male");
    static Predicate<Student> filterByGpa = student -> student.getGpa() > 4.0;
    static Predicate<Student> filterByFemaleGender = student -> student.getGender().equals("female");

    public static void main(String[] args) {
        BiConsumer<String, List<String>> studentBiConsumer = (name, activities) -> System.out.println(name + " : " + activities);

        Consumer<Student> maleStudentConsumerAcceptance = student -> {
            if(filterByMaleGender.and(filterByGpa).test(student)){
                studentBiConsumer.accept(student.getName(), student.getActivities());
            }
        };

        List<Student> studentList = StudentRepository.getAllStudents();
        studentList.forEach(maleStudentConsumerAcceptance);

        BiPredicate<Integer, Double> filterByGradeAndGpa = (grade, gpa) -> grade>=3 && gpa>=3.9;
        Consumer<Student> studentsInThirdFourthGradeWithHighGpa = student -> {
            if(filterByGradeAndGpa.test(student.getGradeLevel(), student.getGpa())){
                studentBiConsumer.accept(student.getName(), student.getActivities());
            }
        };
        studentList.forEach(studentsInThirdFourthGradeWithHighGpa);
    }
}

Output

James : [swimming, basketball, baseball, football]
****BiPredicates**
Sophia : [swimming, dancing, football]
James : [swimming, basketball, baseball, football]

Function & BiFunction Represents a function that accepts one argument and produces a result.This is a functional interface whose functional method is apply(Object).

@FunctionalInterface
public interface Function<T,R> {
  R apply(T t);
}

@FunctionalInterface
public interface BiFunction<T,U,R> {
  R apply(T t, U u);
}
public class FunctionExample {
    static Function<List<Student>, Map<String, Double>> mapStudentListToMap = students -> {
        Map<String, Double> studentGradeMap = new HashMap<>();
        students.forEach(student -> {
            if (PredicateExample.filterByFemaleGender.and(PredicateExample.filterByGpa).test(student))
                studentGradeMap.put(student.getName(), student.getGpa());
        });
        return studentGradeMap;
    };

    static BiFunction<List<Student>, Predicate<Student>, Map<String, Double>> biFunctionMapStudentListToMapWithPredicate = (students, studentPredicate) -> {
        Map<String, Double> studentGradeMap = new HashMap<>();
        students.forEach(student -> {
            if (studentPredicate.test(student)) studentGradeMap.put(student.getName(), student.getGpa());
        });
        return studentGradeMap;
    };

    public static void main(String[] args) {
        List<Student> studentList = StudentRepository.getAllStudents();
        System.out.println(mapStudentListToMap.apply(studentList));
        System.out.println(biFunctionMapStudentListToMapWithPredicate.apply(studentList, PredicateExample.filterByMaleGender));
    }
}

Output

{Sophia=4.3}
{Adam=3.6, James=4.6, Dave=3.4}

Supplier Represents a supplier of results. There is no requirement that a new or distinct result be returned each time the supplier is invoked. This is a functional interface whose functional method is get().

public class SupplierExample {
    static Supplier<Student> studentSupplier = () ->
            new Student("Adam", 2, 3.6, "male", 10, List.of("swimming", "basketball", "volleyball"));

    static Supplier<List<Student>> studentListSupplier = StudentRepository::getAllStudents;

    public static void main(String[] args) {
        System.out.println(studentSupplier.get());
        System.out.println(studentListSupplier.get());
    }
}

Output

Student{name='Adam', gradeLevel=2, gpa=3.6, gender='male', noteBooks=10}
[Student{name='Adam', gradeLevel=2, gpa=3.6, gender='male', noteBooks=10}, Student{name='Jenny', gradeLevel=2, gpa=3.8, gender='female', noteBooks=11}, Student{name='Emily', gradeLevel=3, gpa=3.4, gender='female', noteBooks=12}, Student{name='Dave', gradeLevel=3, gpa=3.4, gender='male', noteBooks=12}, Student{name='Sophia', gradeLevel=4, gpa=4.3, gender='female', noteBooks=10}, Student{name='James', gradeLevel=4, gpa=4.6, gender='male', noteBooks=22}]