preface

Hi, I’m Milo. I’m back πŸ™ˆ

Why don’t you come in and tell us what I’ve been up to? Recently, the company accepted a new project of agricultural products trading website. Because of a code reconstruction problem, I almost did business with the boss. I thought that the boss deliberately made things difficult for me. Finally still discover is me too dish 😏, the thing is this appearance drop!

At the weekly meeting, the eldest brother told us that we had recently acquired a trading platform for agricultural products, which was mainly used for online trading of agricultural products in the whole province. When the first, is to sell our Gansu province Yellow River honey, I was arranged to sell melons! Oh, no, I am responsible for developing the function of selling melons 🀣; I quickly devised the following classes to define the Melon class:

/**
 * η“œ
 * @author Milo Lee
 * @dateThe 2021-04-07 "* /
public class Melon {
    / * * * /
    private final String type;
    / * * * / weight
    private final int weight;
    / * * * / origin
    private final String origin;

    public Melon(String type, int weight, String origin) {
        this.type = type;
        this.weight = weight;
        this.origin = origin;
    }
    // getters, toString() omitted
}

Copy the code

After a CRUD SAO operation, write the melon to add, delete, change and check the work, hand in work πŸ€—.

First screening of melons by type

The next day, the boss gave me a question, said that the increase can be filtered by melon type melon. Isn’t that simple? So, I create a Filters class that implements a filterMelonByType method

/ * * *@author Milo Lee
 * @dateThe 2021-04-07 * /, nay
public class Filters {

    /** * Filter melons by type *@paramMelons melon *@paramType type *@return* /
    public static List<Melon> filterMelonByType(List<Melon> melons, String type) {

        List<Melon> result = new ArrayList<>();
        for (Melon melon: melons) {
            if(melon ! =null&& type.equalsIgnoreCase(melon.getType())) { result.add(melon); }}returnresult; }}Copy the code

There we go. Let’s test it out

    public static void main(String[] args) {
        ArrayList<Melon> melons = new ArrayList<>();
        melons.add(new Melon("Sheep's horn honey".1."Thailand"));
        melons.add(new Melon("Watermelon".2."Sanya"));
        melons.add(new Melon("Yellow River Honey".3."Lanzhou"));
        List<Melon> melonType = Filters.filterMelonByType(melons, "Yellow River Honey");
        melonType.forEach(melon->{
        System.out.println("Melon type :"+melon.getType());
        });
    }

Copy the code

No problem, show it to the boss, boss looked at my code and said: if I asked you to add a filter by weight melon, what are you going to write? Think about it. Is this guy gonna pick on me? πŸ˜ͺ

A second screening of melons by weight

Back to the seat I thought, last time I have realized the type of screening melon, that I give him a copy of the change!

As follows:

    /** * Filter melons by weight *@param melons
     * @param weight
     * @return* /
    public static List<Melon> filterMelonByWeight(List<Melon> melons, int weight) {

        List<Melon> result = new ArrayList<>();
        for (Melon melon: melons) {
            if(melon ! =null&& melon.getWeight() == weight) { result.add(melon); }}return result;
    }

Copy the code
public static void main(String[] args) {
     	ArrayList<Melon> melons = new ArrayList<>();
        melons.add(new Melon("Sheep's horn honey".1."Thailand"));
        melons.add(new Melon("Watermelon".2."Sanya"));
        melons.add(new Melon("Yellow River Honey".3."Lanzhou"));
        List<Melon> melonType = Filters.filterMelonByType(melons, "Yellow River Honey");
        melonType.forEach(melon->{
            System.out.println("Melon type :"+melon.getType());
        });

        List<Melon> melonWeight = Filters.filterMelonByWeight( melons,3);
        melonWeight.forEach(melon->{
            System.out.println("Melon weight :"+melon.getWeight());
        });
    }

Copy the code

Programmer’s favorite way,CV fix, ha ha. But I find that filterByWeight () is very similar to filterByType(), except that the filtering conditions are different. I thought to myself, the boss is not going to ask me to write about sorting melons by type and weight. Take my code to eldest brother see, sure enough, afraid of what to what 😟.

Third screening of melons by type and weight

In order to fulfill the boss’s task, I combined the above code and quickly wrote the following code

    /** * Select melons by type and weight *@param melons
     * @param type
     * @param weight
     * @return* /
    public static List<Melon> filterMelonByTypeAndWeight(List<Melon> melons, String type, int weight) {

        List<Melon> result = new ArrayList<>();
        for (Melon melon: melons) {
            if(melon ! =null&& type.equalsIgnoreCase(melon.getType()) && melon.getWeight() == weight) { result.add(melon); }}return result;
    }

Copy the code

The boss looked at my code and said, you still don’t understand me. If today not only I, but also customers continue to ask for.

The Filters will have a lot of methods like this, which means a lot of boilerplate code (redundant code that has to be written);

From our programmers’ point of view, this is unacceptable. If you continue to add new filters, your code becomes difficult to maintain and error-prone. Learn about lambda expressions and functional interfaces, and make some modifications to your code. I’ve already established that he has it in for me 😀

Pass the behavior as an argument for the fourth time

After the above three times toss. I found that any attribute of the Melon class could theoretically be used as a Filter condition, so our Filter class would have a lot of boilerplate code and some of the methods would be very complex.

In fact, we can find that every time we write a method, there is a corresponding query behavior, query behavior must correspond to a filter condition. Is there any way we can write a method that takes the query behavior as an argument and returns our results?

So it’s given a name: behavior parameterization, illustrated in the figure below (left shows what we have now; The right side shows what we want), do you see the boilerplate code significantly reduced πŸ€”

If we think of filtering criteria as a behavior, it is straightforward to think of each behavior as an implementation of the interface. Analysis shows that all of these behaviors have one thing in common: filter conditions and Boolean returns. Abstract an interface as follows

public interface MelonPredicate {
  boolean test(Melon melon);
}

Copy the code

For example, filtering Yellow River honey could be written as HHMMelonPredicate.

public class HHMMelonPredicate implements MelonPredicate {

     @Override
     public boolean test(Melon melon) {
       return "Yellow River Honey".equalsIgnoreCase(melon.getType()); }}Copy the code

In the same way, we can also filter a certain weight of melon:

public class WeightMelonPredicate implements MelonPredicate {

     @Override
     public boolean test(Melon melon) {
       return melon.getWeight() > 5000; }}Copy the code

In fact, those familiar with design patterns should know this is: strategic design patterns.

The idea is to let the system dynamically choose which methods to call at run time. So we can think of the MelonPredicate interface as unifying all the algorithms dedicated to filtering melons, and each implementation as a strategy, or we can think of it as a behavior.

Currently, we use the policy design pattern to abstract the query behavior. We also need a method that takes the predicate argument. So I define the filterMelons() method as follows:

public static List<Melon> filterMelons(List<Melon> melons, MelonPredicate predicate) {
    
    List<Melon> result = new ArrayList<>();
    for (Melon melon: melons) {
      if(melon ! =null&& predicate.test(melon)) { result.add(melon); }}return result;
}


Copy the code

Great success, test, as expected than before easy to use much, let eldest brother Chou Chou

List<Melon> hhm = Filters.filterMelons(melons, new HHMMelonPredicate());

List<Melon> weight = Filters.filterMelons(melons, new WeightMelonPredicate());


Copy the code

The fifth time 100 filter conditions were added at a time

When I was smug, the eldest brother poured a basin of cold water on him. He said, “You think our platform is to buy Yellow River honey. If there are dozens of melon varieties in front and behind, and I give you a list of 100 filtering conditions, what will you do?

My heart ten thousand grass mud horse pentium 😫! The eldest brother is not to have a hard time with me intentionally! Although my code is flexible enough since my last overhaul, if I suddenly add 100 filters, I still need to write 100 policy classes to implement each filter. Then we need to pass the policy to the filterMelons() method.

Is there a way not to create these classes? I was smart enough to quickly discover that I could use Java anonymous inner classes.

As follows:

List<Melon> europeans = Filters.filterMelons(melons, new MelonPredicate() {

     @Override

     public boolean test(Melon melon) {

       return "europe".equalsIgnoreCase(melon.getOrigin()); }});Copy the code

It was a big step forward, but it didn’t seem to help. I still need to write a lot of code to implement this requirement. Anonymous inner classes are designed to make it easier for Java programmers to pass code as data. Sometimes, anonymous inner classes look a little complicated, and then I remember lambda expressions that my boss told me to learn, and I can use it to simplify things

List<Melon> europeansLambda = Filters.filterMelons(
  melons, m -> "europe".equalsIgnoreCase(m.getOrigin())
);
Copy the code

Sure enough, this is so handsome!! Once again I succeeded in my task. I excitedly took the code to the boss to see.

Introduce generics for the sixth time

The boss looked at my code and said, HMM, nice! My head finally opened up. Now consider that our platform is for agricultural products, that is, not only melons, but also other fruits. If you change to other fruits, how do you modify your code?

Currently our MelonPredicate only supports the Melon class. What’s wrong with this guy? Maybe one day he’ll have to buy vegetables and sea cucumbers, but you can’t make lots of interfaces to MelonPredicate. This time suddenly remembered the teacher said the generic type, it played a role!

So I define a new interface called Predicate

@FunctionalInterface
public interface Predicate<T> {

  boolean test(T t);

}


Copy the code

Next, we override the filterMelons() method and rename it filter() :

public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
  
	List<T> result = new ArrayList<>();

    for (T t: list) {

      if(t ! =null&& predicate.test(t)) { result.add(t); }}return result;

}


Copy the code

Now, we can filter the melons like this:

List<Melon> watermelons = Filters.filter(

  melons, (Melon m) -> "Watermelon".equalsIgnoreCase(m.getType()));


Copy the code

Similarly, we can do the same thing with numbers:

List<Integer> numbers = Arrays.asList(1.13.15.2.67);

List<Integer> smallThan10 = Filters.filter(numbers, (Integer i) -> i < 10);


Copy the code

To recalibrate, the code has changed significantly since the use of Java 8 functional interfaces and lambda expressions.

I don’t know if you noticed that the Predicate interface above has an annotation on @functionalInterface that marks functional interfaces.

So far, we have gone through a requirements evolution process to understand the concepts of lambda and functional interfaces and to deepen our understanding of them. For those familiar with Java8, there are more than 40 such interfaces in our java.util.function package

Functional interfaces and lambda expressions make a powerful team. From the above example, we know that a functional interface is a highly abstract form of our behavior, and we can see an example of a concrete implementation of this behavior.

Predicate<Melon> predicate = (Melon m)-> "Watermelon".equalsIgnoreCase(m.getType());
Copy the code

Lambda, in short

A lambda expression consists of three parts, as shown below:

Here is a description of the parts of a lambda expression:

  • It’s on the left of the arrowlambdaArguments used in the body of an expression.
  • It’s on the right of the arrowlambdaThe main body.
  • The arrow justlambdaDelimiter for parameters and body.

The anonymous class version of this lambda is as follows:

List<Melon> europeans = Filters.filterMelons(melons, new Predicate<Melon>() {

 @Override

 public boolean test(Melon melon) {

   return "Watermelon".equalsIgnoreCase(melon.getType()); }});Copy the code

Now, if we look at lambda expressions and their anonymous class versions, lambda expressions can be described in four ways

We can define a lambda expression as a succinct, transitive, anonymous function. First, we need to make it clear that a lambda expression is essentially a function. Although it does not belong to a specific class, it has an argument list, a function body, a return type, and even the ability to throw exceptions. Second, it is anonymous; lambda expressions do not have specific function names; Lambda expressions can be passed as arguments, simplifying code.

Lambda supports parameterization of behavior, as we demonstrated in the previous example. Finally, remember that lambda expressions can only be used in the context of functional interfaces.

conclusion

In this article, we focus on the utility and usability of functional interfaces, and we’ll look at how code can evolve from boilerplate code to a flexible implementation based on functional interfaces. I hope it helps you understand functional interfaces. Thank you.