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 arrow
lambda
Arguments used in the body of an expression. - It’s on the right of the arrow
lambda
The main body. - The arrow just
lambda
Delimiter 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.