Lambdas

Lambdas were replacement of anonymous class. They give is the power of treating code as data. That means, now a function can take another function as input parameter. This makes things easy in a lot of ways. Specially, when we need to do data transformations. We can apply single filter and apply it on a collection and then return the filtered response.

Lambdas are functional interface (Any interface that has only one abstract method, it can have one or more default and static methods in it). If we don’t have lambdas we need to use anonymous class that will look like this.

printObject(myCollection, 
			new CheckMyCollection() {
				public boolean test(Person p) {
					return p.getGender() == Persopn.Sex.MALE
					&& p.getAge() >=16 && p.getAge() <=25;	
				}
			}
		);

Lambda version:

printObject(
	roster, (Person p) -> p.getGender() == Person.Sex.MALE && p.geAge() >=16 && p.getAge() <=25
);

Functional Interfaces

Predicate Interface

It only gives boolean results.

Jshell:

Predicate<String> checkSomething = x -> x.length() > 2;
System.out.println(checkSomething.test("duck"));



// Another example 

Predicate<Integer> isPositive = x -> x > 0;
Predicate<Integer> isEven = x -> x % 2 == 0;
Predicate<Integer> isPositiveAndEven = isPositive.and(isEven);


List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

numbers.stream().filter(isEven).collect(Collectors.toList());
System.out.println(evenNumbers); // [2, 4, 6]


// Can be done in a single line. 
Arrays.asList(1, 2, 3, 4, 5, 6).stream().filter(isEven).collect(Collectors.toList());



Consumer Interface

This is used for modify data, it does not return anything. Good for things like data transformations.


Consumer<String> printConsumer = str -> System.out.println("Print: " + str);

Consumer<String> lengthConsumer = str -> System.out.println("Length: " + str.length());
Consumer<String> combinedConsumer = printConsumer.andThen(lengthConsumer);


combinedConsumer.accept("Hello, World!");

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

Consumer<String> uppercaseConsumer = str -> System.out.println(str.toUpperCase());

names.forEach(uppercaseConsumer);

Function Interface

This is used for transformations and mappings.

Function<String, Integer> lengthFunction = str -> str.length();
lengthFunction.apply("Hello")


Function<String, Integer> lengthFunction = str -> str.length();

        Function<Integer, Integer> squareFunction = x -> x * x;

        Function<String, Integer> composedFunction = lengthFunction.andThen(squareFunction);
        
        composedFunction.apply("Hello")

Supplier Interface

This does not take any arguments and only returns a result. It’s often used in scenarios where values need to be lazily evaluated or deferred until needed.

public class DeferredComputationExample {

    private static Map<Integer, Long> fibCache = new HashMap<>();

  

    public static void main(String[] args) {

        Supplier<Long> fibonacciSupplier = () -> {

            return computeFibonacci(10); // Compute Fibonacci number for n=10

        };

  

        // No computation happens until get() is called

        System.out.println("Before get()");

        System.out.println("Fibonacci number: " + fibonacciSupplier.get()); // Compute Fibonacci number when needed

        System.out.println("After get()");

    }

  

    // Compute Fibonacci number using memoization

    private static long computeFibonacci(int n) {

        if (n <= 1) {

            return n;

        }

  

        // Check cache first

        if (fibCache.containsKey(n)) {

            return fibCache.get(n);

        }

  

        // Compute recursively and store in cache

        long result = computeFibonacci(n - 1) + computeFibonacci(n - 2);

        fibCache.put(n, result);

        return result;

    }

}

Predicate Interface

It is often used for conditions where you need to test objects and return a boolean value based on some criteria.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Predicate<Integer> isEven = n -> n % 2 == 0;
numbers.stream().filter(isEven).collect(Collectors.toList());                                   

Lambdas operations.

forEach


import java.util.Arrays;

import java.util.List;

  

public class StreamExamples {

    public static void main(String[] args) {

        List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C#", "Ruby");

  

        // Using forEach to print each element in the list

        languages.stream()

                 .forEach(lang -> System.out.println("Language: " + lang));

    }

}

filter

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExamples {
    public static void main(String[] args) {
        List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C#", "Ruby");

        // Using filter to select languages starting with 'J'
        List<String> filteredLanguages = languages.stream()
                                                  .filter(lang -> lang.startsWith("J"))
                                                  .collect(Collectors.toList());

        System.out.println("Filtered languages: " + filteredLanguages); // Output: [Java, JavaScript]
    }
}

Map

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExamples {
    public static void main(String[] args) {
        List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C#", "Ruby");

        // Using map to transform each language to its length
        List<Integer> languageLengths = languages.stream()
                                                 .map(lang -> lang.length())
                                                 .collect(Collectors.toList());

        System.out.println("Language lengths: " + languageLengths); // Output: [4, 6, 10, 2, 4]
    }
}

collect

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExamples {
    public static void main(String[] args) {
        List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C#", "Ruby");

        // Using collect to join all languages into a single comma-separated string
        String joinedLanguages = languages.stream()
                                         .collect(Collectors.joining(", "));

        System.out.println("Joined languages: " + joinedLanguages); // Output: Java, Python, JavaScript, C#, Ruby
    }
}

flatMap

It’s particularly useful when dealing with nested collections or when you want to transform and combine elements in a nested structure.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExamples {
    public static void main(String[] args) {
        List<List<Integer>> nestedLists = Arrays.asList(
                Arrays.asList(1, 2, 3),
                Arrays.asList(4, 5, 6),
                Arrays.asList(7, 8, 9)
        );

        // Using flatMap to flatten the nested lists into a single stream of integers
        List<Integer> flattenedList = nestedLists.stream()
                                                 .flatMap(List::stream)
                                                 .collect(Collectors.toList());

        System.out.println("Flattened list: " + flattenedList); // Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
}

reduce

import java.util.Arrays;
import java.util.List;

public class StreamExamples {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // Using reduce to calculate the sum of numbers in the stream, Integer.MIN_VALUE is from where to start reducing.
        int sum = numbers.stream()
                         .reduce(Integer.MIN_VALUE, (a, b) -> a + b);

        System.out.println("Sum of numbers: " + sum); // Output: 15
    }
}

sorted

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExamples {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("banana", "apple", "cherry", "date");

        // Using sorted to sort the words alphabetically
        List<String> sortedWords = words.stream()
                                       .sorted()
                                       .collect(Collectors.toList());

        System.out.println("Sorted words: " + sortedWords); // Output: [apple, banana, cherry, date]
    }
}

distinct

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExamples {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 5);

        // Using distinct to filter unique numbers
        List<Integer> uniqueNumbers = numbers.stream()
                                             .distinct()
                                             .collect(Collectors.toList());

        System.out.println("Unique numbers: " + uniqueNumbers); // Output: [1, 2, 3, 4, 5]
    }
}

Limt and skip


import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExamples {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Using limit and skip for paging through the list
        List<Integer> page = numbers.stream()
                                    .skip(3)     // Skip the first 3 elements
                                    .limit(3)    // Limit to 3 elements
                                    .collect(Collectors.toList());

        System.out.println("Paged list: " + page); // Output: [4, 5, 6]
    }
}