• Uncategorised

Filtering Elements in Lists with Functional Interfaces and Lambdas in Java

Here’s a breakdown of functional interfaces and lambdas, along with how they can be used to solve the given scenario compared to a traditional approach.

Functional Interfaces:

  • Definition: A functional interface is an interface in Java that specifies a single abstract method. It outlines the functionality (what the method does) without defining how it’s implemented (the logic within the method).
  • Purpose: Functional interfaces enable functional programming concepts in Java, allowing you to treat functions as values and use them in operations like higher-order functions.
  • Example:
@FunctionalInterface
public interface CityFilter {
  boolean isFromNepal(String cityName);
}

This CityFilter interface has one abstract method, isFromNepal, which takes a city name as a string and returns a boolean indicating if it’s from Nepal.

Lambda Expressions:

  • Definition: Lambdas are a concise way to define anonymous functions in Java. They consist of parameters, an optional arrow (->), and the function body containing the logic.
  • Benefits:
    • Improve code readability and reduce boilerplate code, especially for small, well-defined functions.
    • Facilitate functional programming paradigms like working with streams and collections.

Using Lambdas with Functional Interfaces:

  • You can use lambda expressions to implement the abstract method of a functional interface. The lambda’s parameter list and return type must match the abstract method’s signature.
  • Example:
CityFilter nepalFilter = cityName -> cityName.startsWith("Nepal ");  // Checks if city name starts with "Nepal "

Here, a lambda expression is used to implement the CityFilter interface. It takes a city name (cityName) and checks if it begins with “Nepal “.

Traditional Approach (without lambdas):

  1. Define a Separate Method: Create a method that takes a list of strings (city names) and iterates through them.
  2. Filtering Logic: Inside the loop, use an if statement or similar logic to check if the current city name starts with “Nepal “.
  3. Add Matching Names: If it’s from Nepal, add the city name to a separate list or perform some other desired action.

Code Example (Traditional):

public static List<String> filterNepalCities(List<String> cityNames) {
  List<String> nepalCities = new ArrayList<>();
  for (String cityName : cityNames) {
    if (cityName.startsWith("Nepal ")) {
      nepalCities.add(cityName);
    }
  }
  return nepalCities;
}

Functional Interface and Lambda Approach:

  1. CityFilter Interface: Define the CityFilter interface as explained earlier.
  2. Lambda Expression: Use a lambda expression to implement the isFromNepal method. This lambda checks if the city name starts with “Nepal “.
  3. Stream Processing: Utilize Java Stream API methods like filter to process the list of city names. Pass the lambda expression (representing the filter criteria) to the filter method.

Code Example (Lambda):

public static List<String> filterNepalCities(List<String> cityNames) {
  return cityNames.stream()
      .filter(cityName -> cityName.startsWith("Nepal "))  // Filter using lambda
      .collect(Collectors.toList());
}

What parameter does filter expect.Talk about predicate

The filter method in Java Streams expects a single parameter, which is a Predicate.

  • Predicate: A Predicate is a functional interface with a single abstract method named test(T t). This method takes an object of type T and returns a boolean value (true if the object satisfies the condition, false otherwise).
List<String> cities = Arrays.asList("New York", "Tokyo", "Kathmandu", "London");

// Create a Stream from the list
Stream<String> cityStream = cities.stream();

// Filter cities starting with "N" (intermediate operation)
Stream<String> filteredStream = cityStream.filter(city -> city.startsWith("N"));

// Convert city names to uppercase (another intermediate operation)
Stream<String> uppercaseStream = filteredStream.map(String::toUpperCase);

// Collect the uppercase cities into a new list (terminal operation)
List<String> uppercaseCities = uppercaseStream.collect(Collectors.toList());

System.out.println(uppercaseCities);  // Output: [NEW YORK]

What argument does map expect?

The map method in Java Streams is a powerful tool for transforming elements within a stream. It takes two arguments:

  1. Function: The first argument is a Function interface. This interface defines a single abstract method named apply(T t). The T represents the type of the element in the stream, and the apply method takes an element of type T and returns an element of another type R.
@FunctionalInterface
public interface Function<T, R> {
  R apply(T t);
}

public class Squarer implements Function<Integer, Integer> {

  @Override
  public Integer apply(Integer value) {
    return value * value;
  }
}

public class Main {

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

    // Create a Squarer object (function object)
    Squarer squaringFunction = new Squarer();

    // Use Squarer object with map (directly implements Function)
    List<Integer> streamedSquaredNumbers = numbers.stream()
        .map(squaringFunction)  // Pass the Squarer object as Function
        .collect(Collectors.toList());
    System.out.println("Squared numbers (stream): " + streamedSquaredNumbers);
  }
}

You may also like...