One of the core features of modern Java is lambda expressions. Introduced
in Java 8, lambdas provide concise syntax allowing the deferred execution
of a block of code. Put a different way, lambdas allow us to pass
behaviour as a method parameter. When the method executes, the lambda
expression is run. This capability is often referred to as *behaviour
parameterization*.

Behaviour parameterization can be achieved in a number of ways, of which lambda expressions are usually the most convenient, and they are definitely the most concise. But what is behaviour parameterization, and why would we want to use it? To motivate this discussion, let’s work through a real-world example of filtering a list of items according to some criteria. More concretely, let’s investigate the problem filtering a list of students to find the ones with the best grades.

## Filtering by Value

First, we define our `Student`

class with some simple properties.

```
public class Student {
private String name;
private Double gradePointAverage;
private Integer age;
// Standard constructor, getter, setter, etc.
```

Then we can filter the list of students using a simple conditional and loop:

```
public static List<Student> filterGoodStudents(List<Student> students) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (student.getGradePointAverage() >= 3.7) {
result.add(student);
}
}
return result;
}
```

The core of this algorithm is the conditional
`student.getGradePointAverage() >= 3.7`

. This simple test provides the
filtering function and the rest of the algorithm is simple boilerplate
around the solution. For example, if we now wish to find great students
with a 4.0 GPA we can use the same algorithm, varying only the
conditional. The method `filterGreatStudents`

updates the previous method
by returning students that have a GPA of 4.0 or higher.

```
public static List<Student> filterGreatStudents(List<Student> students) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (student.getGradePointAverage() >= 4.0) {
result.add(student);
}
}
return result;
}
```

## Parameterizing our Filter

Naturally, once we have filters in place for finding good and great
students, we get a request for an additional function that filters all
students with a passing grade. Since we are repeating ourselves three
times now, it would be easier to improve our interface by allowing the
caller to supply the GPA they wish to filter by. We can add a GPA
parameter to our function and change the conditional to
`student.getGradePointAverage() >= gpa`

to filter using our parameter.

```
public static List<Student> filterByGpa(List<Student> students, Double gpa) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (student.getGradePointAverage() >= gpa) {
result.add(student);
}
}
return result;
}
```

*This is our first example of behaviour parameterization*. We are allowing
the behaviour of our method to be changed based on a parameter. Clearly,
this example is very simple, and it breaks down as we get to more complex
cases. For example, what if we now have a request to filter students by
age? Knowing what we know about parameterization, instead of creating
a new hard-coded filter function we jump straight to adding an age
parameter to our method and using that in the filter.

```
public static List<Student> filterByAge(List<Student> students, Integer age)
```

And maybe another method for filtering by both age and by GPA:

```
public static List<Student> filterByAgeAndGpa(List<Student> students, Integer age, Double gpa)
```

And then maybe add a flag to signal which filter to use at a time. When
`true`

the `byAge`

flag in this method will filter using the `age`

parameter, and when `false`

the method will filter using the `gpa`

parameter.

```
public static List<Student> filterByAgeOrGpa(List<Student> students, Integer age, Double gpa, Boolean byAge) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (byAge) {
if (student.getAge() >= age) {
result.add(student);
}
} else {
if (student.getGradePointAverage() >= gpa) {
result.add(student);
}
}
}
return result;
}
```

Hopefully you can see how this is becoming a messy implementation. To drive the point home, consider the client perspective when trying to use this method. It requires some pretty arcane parameters to make sense of.

```
List<Student> goodStudents = filterByAgeOrGpa(students, 0, 3.7, false);
List<Student> passingStudents = filterByAgeOrGpa(students, 0, 2.0, false);
List<Student> oldStudents = filterByAgeOrGpa(students, 16, 0.0, true);
List<Student> allStudents = filterByAgeOrGpa(students, 5, 0.0, true);
```

This just isn’t working very well. What is the problem? We are trying to
parameterize the filter algorithm with *values* like Int and Double. This
works fine for certain problems that are very well defined, but in our
case it would be much better if could parameterize the behaviour of this
method, which is controlled by the conditional that does the filtering.

## Filtering by Predicate

In functional programming, a predicate is a function that returns a boolean. A predicate for filtering students can be simply defined with the following interface:

```
public interface StudentPredicate {
boolean test(Student s);
}
```

And we can implement the filtering method to leverage our predicate. The
following method takes the predicate as a parameter, and uses the
predicates `test`

function as the conditional to filter by.

```
public static List<Student> filterStudents(List<Student> students, StudentPredicate predicate) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (predicate.test(student)) {
result.add(student);
}
}
return result;
}
```

Now, any time we want to change the behaviour of our filter, we can supply
a new implementation of the `StudentPredicate`

without changing any of the
filter implementation.

```
public class GreatStudentsPredicate implements StudentPredicate {
@Override
public boolean test(Student s) {
return s.getGradePointAverage() >= 3.7;
}
}
```

As a client, using this revised filter method is also straightforward:

```
List<Student> greatStudents = filterStudents(students, new GreatStudentsPredicate());
```

With this attempt at filtering, the *behaviour* of the filter method
depends on the code passed into it via the predicate object while the
logic for iterating through a collection and applying a filtering test
remains the same. The downside to this approach is that each new predicate
requires the client to create an additional class that implements the
predicate interface. This is fairly cumbersome and verbose for the
functionality you get in return.

## Filtering by Anonymous Class

A feature called *anonymous classes* can be used by clients of our
interface to implement our predicate without needing to create a new
class. For example, a caller to our filter function can supply our
predicate with the following syntax:

```
List<Student> greatStudents = StudentFilter.filterStudents(students, new StudentPredicate() {
@Override
public boolean test(Student s) {
return s.getGradePointAverage() >= 3.7;
}
});
```

Anonymous classes take us one step closer to easy-to-use behaviour parameterization, but they are still fairly verbose and they can be confusing to use as the functionality of the class becomes more complex.

## Filtering with Lambdas

We can further improve our client code by leveraging a Java feature called
*lambdas* that reduce the verbosity of our implementation. The following
code uses the sample filtering method, but provides the implementation of
the predicate using lambda syntax, which reads as “given a student s, then
execute and return the following code”.

```
List<Student> greatStudents = StudentFilter.filterStudents(students, s -> s.getGradePointAverage() >= 3.7);
```

With lambdas, we’ve found a concise and flexible implementation of behaviour parameterization. A lambda is a concise representation of an anonymous class that can be passed around to methods. The lambda function is not associated with a named class, but it does have a list of parameters, a body, a return type, and a set of possible exceptions.