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):
- Define a Separate Method: Create a method that takes a list of strings (city names) and iterates through them.
- Filtering Logic: Inside the loop, use an
if
statement or similar logic to check if the current city name starts with “Nepal “. - 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:
- CityFilter Interface: Define the
CityFilter
interface as explained earlier. - Lambda Expression: Use a lambda expression to implement the
isFromNepal
method. This lambda checks if the city name starts with “Nepal “. - 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 thefilter
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 namedtest(T t)
. This method takes an object of typeT
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:
- Function: The first argument is a
Function
interface. This interface defines a single abstract method namedapply(T t)
. TheT
represents the type of the element in the stream, and theapply
method takes an element of typeT
and returns an element of another typeR
.
@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);
}
}