This is the first day of my participation in the First Challenge 2022

Have something to do with

Lambda expressions, kids!

Everything can stream

The introduction

Java has long been considered an object-oriented programming language, and the idea that “everything is an object” has taken root. But with the release of Java8, everything seems to be changing. The introduction of Lambda expressions and streams gave Java a new lease of life, allowing people to think functionally. This paper mainly introduces the application of functional programming in Java.

Directive or declarative?

Let’s start with a bit of code: calculate the maximum price of an item. We typically implement this:

int max = 0;
for(int price : prices) {
    if(max < price) { max = price; }}Copy the code

This is typical of imperative. It uses the computer’s instructions or syntax to tell the computer what to do step by step. For the same function, let’s write it another way:

int max = prices.stream().reduce(0, Math::max);
Copy the code

This concise code is “declarative.” He is more like telling the computer what to do than what to do inside the computer.

If you’re familiar with software design principles, this is the Hollywood principle in action, Tell, Don’t Ask!

Implement design patterns with functional thinking

In GoF’s classic book Design Patterns, 23 common design patterns are described in detail. Careful readers will notice the fine print below the title: Fundamentals of Reusable object-oriented software. That is to say, it is implemented with object-oriented ideas. Over the years, the design pattern debate has been going on, but it doesn’t matter anymore, and today let’s take a fresh look at design patterns from a functional programming perspective.

Command mode

The command mode encapsulates commands and provides interfaces for users to invoke. First look at an example: sweeping robot can execute straight, turn left, turn right and other instructions operation. First define the instruction interface and implementation class:

// Command interface
public interface Command {
    void execute(a);
}
// The forward command is implemented
public class forward implements Command {
    public void execute(a) {
        System.out.println("go forward"); }}// The right turn command is implemented
public class Right implements Command {
    @Override
    public void execute(a) {
        System.out.println("go right"); }}Copy the code

Implement the robot below:

public class Robot {
    public static void move(Command... commands) {
        for(Command command : commands) { command.execute(); }}public static void main(String[] args) {
        Robot.move(new Forward(), newRight()); }}Copy the code

The functionality was there, but there was a problem: too many classes were created!

The business logic that was supposed to be the focus was buried in a plethora of class implementations.

Let’s see how functional programming works, okay?

Because execute() in the Command interface is a method with no incoming arguments and no return results, it is naturally reminiscent of Java’s built-in functional interface Runnable, which also has a similarly signed run() method. Although the Runnable interface was originally intended for multithreading, here we use it in functional programming.

First we replace the Robot move() method with the Runnable interface:

public static void flexibleMove(Runnable... commands) {
	Stream.of(commands).forEach(Runnable::run);
}
Copy the code

In this way, we only need to implement the command in the class method.

public static void forward(a) {
	System.out.println("go forward");
}
public static void right(a) {
	System.out.println("go right");
}
Copy the code

The call becomes:

Robot.flexibleMove(Robot::forward, Robot::right);
Copy the code

This implementation reduces the number of command implementation classes and focuses more on business logic.

The strategy pattern

The policy pattern enables separation of interface and implementation by injecting different implementation policies. Let’s start with a strategy pattern implemented with object-oriented thinking: set different formatting strategies for text to produce different output.

Here is the code implementation:

Defines a text editor class. Constructors implement the default formatting strategy, and methods can set other formatting strategies.

public class Editor {
    private Formatter formatter;
    private String text;
    public Editor(String text) {
        this.text = text;
        this.formatter = new DefaultFormatter();
    }
    public void setFormatter(Formatter formatter) {
        this.formatter = formatter;
    }
    public String output(a) {
        returnformatter.format(text); }}Copy the code

Implementation of various strategies:

// The default implementation policy, i.e. raw text output
public class DefaultFormatter implements Formatter {
    @Override
    public String format(String text) {
        returntext; }}// Switch to uppercase output policy
public class UppercaseFormatter implements Formatter {
    @Override
    public String format(String text) {
        returntext.toUpperCase(); }}Copy the code

Client call:

String text = "Hello, World";
Editor editor = new Editor(text);
String defaultText = editor.output();
editor.setFormatter(new UppercaseFormatter());
String upperText = editor.output();
Copy the code

This is the standard policy pattern implementation, and let’s see how we can implement it functionally, okay?

From the format method of formatter, we can see that both the input parameter and the return result are of type String, which reminds us of Java’s built-in functional interface, UnaryOperator.

Here is the code implementation:

Text:

public class EditorPlus {
    private String text;
    private UnaryOperator<String> formatter;
    public EditorPlus(String text) {
        this.text = text;
        this.formatter = s -> s;
    }
    public void setFormatter(UnaryOperator<String> formatter) {
        this.formatter = formatter;
    }
    public String output(a) {
        returnformatter.apply(text); }}Copy the code

Client call:

EditorPlus editor = new EditorPlus("Hello, World");
String defaultText = editor.output();
editor.setFormatter(String::toUpperCase);
String upperText = editor.output();
Copy the code

The formatter type is changed to UnaryOperator, so that the default implementation can be written as Lambda expressions: s->s. Other formatting strategies can also be implemented using Lambda expressions, eliminating the need to write as many policy implementation classes.

Decorative pattern

Decorator mode can add new features without changing the behavior of the original component, and the features themselves can be superimposed.

Let’s start with an object oriented requirement: add a filter to the camera. You can have multiple filters.

Define a device interface that contains only methods to get colors:

public interface Equipment {
    Color getColor(a);
}
Copy the code

Redefine the camera class to implement the device interface:

public class Camera implements Equipment {
    private Color color;
    public Camera(Color color) {
        this.color = color;
    }
    @Override
    public Color getColor(a) {
        return this.color; }}Copy the code

Let’s define an abstract filter class that also implements the device interface:

public abstract class FilterDecorator implements Equipment {
    protected Equipment equipment;
    public FilterDecorator(Equipment equipment) {
        this.equipment = equipment;
    }
    public abstract Color getColor(a);
}
Copy the code

Next we define two filters: Lighten and darken, inheriting the filter abstract class.

public class BrighterFilter extends FilterDecorator {
    public BrighterFilter(Equipment equipment) {
        super(equipment);
    }
    @Override
    public Color getColor(a) {
        returnequipment.getColor().brighter(); }}public class DarkerFilter extends FilterDecorator {
    public DarkerFilter(Equipment equipment) {
        super(equipment);
    }
    @Override
    public Color getColor(a) {
        returnequipment.getColor().darker(); }}Copy the code

The client call is:

Equipment decoratedCamera = new DarkerFilter(
                                new BrighterFilter(
                                    new Camera(new Color(155.120.30))));
decoratedCamera.getColor()
Copy the code

Let’s take a look at how to implement it functionally:

The filter Function converts one Color to another, so it’s a natural reference to Java’s own functional interface, Function. It has a compose method that implements a combination of methods.

Here is the code implementation:

public class Camera {
    private Color color;
    private Stream<Function<Color, Color>> filterStream;
    
    public Camera(Color color, Function<Color, Color>... filters) {
        this.color = color;
        this.filterStream = Stream.of(filters);
    }
    public Color getColor(a) {
        Function<Color, Color> composedFilter = filterStream.reduce(
                (filter, next) -> filter.compose(next))
                    .orElse(color -> color);
        return composedFilter.apply(this.color); }}Copy the code

The Camera input is changed from the original Equipment interface to a Function list. And internal reduce() combined with compose() method to realize the accumulation of features. The code is much simpler.

Finally, look at the client call:

Camera camera = new Camera(new Color(155.120.30), Color::brighter,Color::darker);
camera.getColor();
Copy the code

The code is further simplified through method references.

As you can see from the functional implementation of the above three patterns, using a functional approach leads to a significant reduction in the number of classes, streamlined code, and more explicit semantics.

Design coherent interface

Fluent Interface is a common design technique for internal DSL. It usually uses the Builder design pattern to return a method to this object so that the method can be called like a chain, forming a coherent interface.

Let’s start with an example of a coherent interface:

Design an email sending function, including sending address, destination address, recipient, subject, subject, etc.

// Define a mail sending builder
public class MailBuilder {
    public MailBuilder from(final String address) {return this; }
    public MailBuilder to(final String address) {return this; }
    public MailBuilder subject(final String line) {return this; }
    public MailBuilder body(final String message) {return this; }
    public void send(a) { System.out.println("sending..."); }}Copy the code

The client call is:

new MailBuilder()
        .from("[email protected]")
        .to("[email protected]")
        .subject("article")
        .body("fp in java")
        .send();
Copy the code

This is how we commonly use a coherent interface. But it also has two small problems:

  1. The new approach makes the coherent interface less readable.
  2. Again, this is “directive” writing, first compose the message body, then send the message. Better semantics are the mail.send (mail) approach.

Can you do both? This can be done using functional interfaces.

Within Java’s built-in functional interface, there is a Consumer interface that can be used to receive parameters for consumption, which is exactly what we intended.

The code implementation is as follows:

public class Mailer {
    private Mailer(a) {}
    public Mailer from(final String address) { return this; }
    public Mailer to(final String address) { return this; }
    public Mailer subject(final String line) { return this; }
    public Mailer body(final String message) { return this; }
    public static void send(final Consumer<Mailer> block) {
        final Mailer mailer = new Mailer();
        block.accept(mailer);
        System.out.println("sending..."); }}Copy the code

As you can see from the above implementation, the constructor becomes private, the send() method becomes static, and receives a block of type Consumer.

The client call then becomes:

Mailer.send(mail ->
            mail.from("[email protected]")
                .to("[email protected]")
                .subject("article")
                .body("fp in java"));
Copy the code

Such code preserves a coherent interface and is more expressive.

Use of resources

We often operate on resources in daily development, such as database connection, file operation, lock operation and so on. These operations on resources have a common feature: open the resource, then operate on the resource, and finally close the resource. The code would normally look like this:

resource.open();
try {
    doSomethingWith(resource);
} finally {
   resource.close();
}
Copy the code

This is a piece of boilerplate code that seems simple, but in the course of its use, careless programmers forget to include finally statements to release resources, resulting in memory leaks.

Is there a way to automatically close a resource after it has been operated on?

You can do this using a functional interface. You can pass the Consumer interface to boilerplate code so that the client only has to deal with the resource, and the shutdown is done automatically.

The code implementation is as follows:

public static void handle(Consumer<Resource> consumer) {
    Resource resource = new Resource();
    try {
        consumer.accept(resource);
    } finally{ resource.close(); }}Copy the code

When used by the client, it can be written as:

handle(resource -> doSomethinWith(resource));
Copy the code

No more worrying about using resources and forgetting to release them!

Lazy loading

Lazy loading is an important feature of functional programming, which can improve software performance when used properly. Here is an example: Perform the and operation on an operation that takes a long time. Let’s see what happens without lazy loading:

public class Evaluation {
    public static boolean evaluate(final int value)  {
        System.out.println("evaluating ..." + value);
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return value > 100;
    }
    public static void eagerEvaluator(final boolean input1, final boolean input2) {
        System.out.println("eagerEvaluator called...");
        System.out.println("accept?: " + (input1 && input2));
    }
    public static void main(String[] args) throws InterruptedException {
        eagerEvaluator(evaluate(1), evaluate(2)); }}Copy the code

Evaluate () is a time consuming operation, such as 20s. EagerEvaluator () passes in two Boolean results and does an and internally. Since both evaluate() methods must be completed, the total time is at least 40s.

As we know, the && operation in Java is a short-circuit operation, that is, if the first condition is false, the subsequent condition is not evaluated and false is returned.

Can we use this feature? If the argument passed to the evaluate() method is a functional interface, it will not execute immediately, but wait until it is actually called, thus achieving the effect of short-circuiting.

Here is the code implementation:

public static void lazyEvaluator(final Supplier<Boolean> input1, final Supplier<Boolean> input2) {
    System.out.println("lazyEvaluator called...");
    System.out.println("accept?: " + (input1.get() && input2.get()));
}
Copy the code

The Supplier function interface is passed to the lazyEvaluator() method, which can be called as:

lazyEvaluator(() -> evaluate(1), () -> evaluate(2));
Copy the code

The lambda expression passed in thus is executed only when called, and since the first condition returns false, the short-circuit operation is performed, returning the result directly. In this way, the operation time is reduced by half and the performance is significantly improved.

summary

Java may not be a functional programming language after all, despite the introduction of functional programming elements, but that doesn’t stop us from using functional programming thinking to solve problems. After all, the problems of the world are not opposable, and perhaps object-oriented and functional programming can be used together to achieve unexpected results.