How does Java support functional programming?

background

For a long time, Java has been an object-oriented language, everything is an object, if you want to call a function, the function must belong to a class or object, and then use the class or object to call. But in other programming languages, such as JS and C++, we can write a function directly, and then call it when needed. It can be said to be object-oriented programming or functional programming. From a functional point of view, there is nothing bad about object-oriented programming, but from a development point of view, object-oriented programming will write a lot of lines of code that may be repeated. For example, when creating an anonymous class of Runnable:

Runnable runnable = new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("do something..."); 
    } 
}; 

The only useful part of this piece of code is the content of the run method. The remaining part is a structural part of the Java programming language, which is useless, but it needs to be written. Fortunately, since Java 8, functional programming interfaces and Lambda expressions have been introduced to help us write less and more elegant code:

// 一行即可 
Runnable runnable = () -> System.out.println("do something..."); 

There are mainly three mainstream programming paradigms, process-oriented, object-oriented and functional programming.

Functional programming is not a very new thing, it has appeared more than 50 years ago. In recent years, functional programming has attracted more and more attention, and many new functional programming languages ​​have emerged, such as Clojure, Scala, Erlang, etc. Some non-functional programming languages ​​have also added many features, syntax, and class libraries to support functional programming, such as Java, Python, Ruby, JavaScript, etc. In addition, Google Guava also has enhancements to functional programming.

Because of the particularity of programming, functional programming can only give full play to its advantages in scientific computing, data processing, statistical analysis and other fields, so it cannot completely replace the more general object-oriented programming paradigm. But as a supplement, it also has great significance for existence, development and learning.

What is functional programming

The English translation of functional programming is Functional Programming.

So what exactly is functional programming? Actually, there is no strict official definition of functional programming. Strictly speaking, the "function" in functional programming does not refer to the concept of "function" in our programming language, but the exponential "function" or "expression" (for example: y=f(x)). However, when programming, for mathematical "functions" or "expressions", we generally habitually design them as functions. Therefore, if you don't delve into it, the "function" in functional programming can also be understood as the "function" in a programming language.

Each programming paradigm has its own unique place, which is why they are abstracted out as a paradigm. The biggest characteristic of object-oriented programming is that it takes classes and objects as the unit of code organization and its four characteristics. The biggest feature of process-oriented programming is that functions are used as the unit of code organization, and data and methods are separated. What is the most unique part of functional programming? In fact, the most unique part of functional programming lies in its programming ideas. Functional programming believes that a program can be represented by a combination of a series of mathematical functions or expressions. Functional programming is a lower-level abstraction of programming to mathematics, describing the calculation process as an expression. However, you will definitely have questions in this way. Can any program be expressed as a set of mathematical expressions?

In theory, it is possible. However, not all programs are suitable for this. Functional programming has its own suitable application scenarios, such as scientific computing, data processing, statistical analysis, etc. In these areas, programs are often easier to express in mathematical expressions. Compared with non-functional programming, which achieves the same function, functional programming can be done with very little code. However, for the development of a large-scale business system related to strong business, it is difficult to abstract it into a mathematical expression, and to use functional programming to achieve it is obviously asking for trouble. On the contrary, in this application scenario, object-oriented programming is more appropriate, and the written code is more readable and maintainable.

When it comes to programming, functional programming, like process-oriented programming, uses functions as a unit of code organization. However, it differs from procedural programming in that its functions are stateless. What is stateless? To put it simply, the variables involved in the function are all local variables. They will not share class member variables like object-oriented programming, nor will they share global variables like process-oriented programming. The execution result of the function is only related to the input parameters and has nothing to do with any other external variables. The same input, no matter how you execute it, the result is the same. This is actually the basic requirement of mathematical functions or mathematical expressions. for example:

// Stateful function: The execution result depends on the value of b. Even if the input parameters are the same, 
// the function's return value may be different if the  function is executed multiple times, because the value of b may be different. 
int b;  
int increase(int a) {  
  return a + b;  
 
// Stateless function: the execution result does not depend on the value of any external variable  
// As long as the input parameters are the same, no matter how many times it is executed, the return value of the function will be the same  
int increase (int a, int b) {  
  return a + b;  

Different programming paradigms are not completely different, there are always some common programming rules. For example, whether it is process-oriented, object-oriented or functional programming, they all have the concept of variables and functions. The top level must have the main function execution entry to assemble programming units (classes, functions, etc.). However, the object-oriented programming unit is a class or object, the process-oriented programming unit is a function, and the programming unit of functional programming is a stateless function.

Java's support for functional programming

Object-oriented programming does not necessarily have to use object-oriented programming languages. Similarly, functional programming does not necessarily have to use functional programming languages. Nowadays, many object-oriented programming languages ​​also provide corresponding syntax and class libraries to support functional programming.

Java, an object-oriented programming language, supports functional programming through an example:

public class Demo { 
  public static void main(String[] args) { 
    Optional<Integer> result = Stream.of("a", "be", "hello") 
            .map(s -> s.length()) 
            .filter(l -> l <= 3) 
            .max((o1, o2) -> o1-o2); 
    System.out.println(result.get()); // 输出2 
  } 
} 

The function of this code is to filter out the strings with length less than or equal to 3 from a set of string arrays, and find the maximum length among them.

Java introduces three new grammatical concepts for functional programming: Stream class, Lambda expression and functional interface (Functional Inteface). The Stream class is used to support the code writing method of cascading multiple function operations through "."; the function of introducing Lambda expressions is to simplify code writing; the function of the function interface is to allow us to wrap the function into a function interface to realize the function Use it as a parameter (Java does not support function pointers like C, you can use functions directly as parameters).

Stream class

Suppose we want to calculate such an expression: (3-1)*2+5. If written in the way of ordinary function calls, it looks like this:

add(multiply(subtract(3,1),2),5); 

However, writing code like this seems more difficult to understand. Let's change to a more readable way of writing, as shown below:

subtract(3,1).multiply(2).add(5); 

In Java, "." means to call a method of an object. In order to support the above cascading call method, we let each function return a generic Stream class object. There are two types of operations on the Stream class: intermediate operations and termination operations. The intermediate operation returns a Stream class object, and the termination operation returns a definite value result.

Let's look at the previous example again, and explain the code. Among them, map and filter are intermediate operations, returning Stream class objects, and you can continue to cascade other operations; max is a termination operation, and what is returned is not a Stream class object, and can no longer continue to cascade down.

public class Demo {  
  public static void main(String[] args) {  
    Optional<Integer> result = Stream.of("f", "ba", "hello") // of returns Stream<String>  
            object.map(s -> s.length()) // map returns Stream<Integer> object.  
            filter(l -> l <= 3) // filter returns Stream<Integer> object.  
            max((o1, o2) -> o1-o2 ); // max termination operation: return Optional<Integer>  
    System.out.println(result.get()); // output 2  
  }  

Lambda expression

As mentioned earlier, the main function of Java's introduction of Lambda expressions is to simplify code writing. In fact, we can also write the code in the example without Lambda expressions. Let's take the map function as an example.

The following three pieces of code, the first piece of code shows the definition of the map function. In fact, the parameter that the map function receives is a Function interface, which is also a function interface. The second code shows how to use the map function. The third piece of code is a simplified way of writing for the second piece of code with Lambda expressions. In fact, Lambda expression is just a syntactic sugar in Java. The bottom layer is implemented based on a functional interface, which is the way the second code shows.

// The definition of the map function in the Stream class:  
public interface Stream<T> extends BaseStream<T, Stream<T>> {  
  <R> Stream<R> map(Function<? super T,? Extends R> mapper);  
  / /... Omit other functions...  
 
// Examples of how to use map in the Stream class:  
Stream.of("fo", "bar", "hello").map(new Function<String, Integer>() {  
  @Override  
  public Integer apply(String s) {  
    return s.length();  
  }  
});  
 
// Simplified writing with Lambda expression:  
Stream.of("fo", "bar", "hello"). map(s -> s.length()); 

Lambda expressions include three parts: input, function body, and output. The expression is as follows:

(a, b) -> {statement 1; statement 2; ...; return output;} //a, b are input parameters 

In fact, Lambda expressions are very flexible. The above is the standard notation, and there are many simplified notations. For example, if there is only one input parameter, you can omit () and write it directly as a->{...}; if there is no input parameter, you can directly omit both the input and the arrow, and only keep the function body; if the function body has only one statement, then You can omit {}; if the function does not return a value, the return statement can be omitted.

Optional<Integer> result = Stream.of("f", "ba", "hello") 
        .map(s -> s.length()) 
        .filter(l -> l <= 3) 
        .max((o1, o2) -> o1-o2); 
         
// 还原为函数接口的实现方式 
Optional<Integer> result2 = Stream.of("fo", "bar", "hello") 
        .map(new Function<String, Integer>() { 
          @Override 
          public Integer apply(String s) { 
            return s.length(); 
          } 
        }) 
        .filter(new Predicate<Integer>() { 
          @Override 
          public boolean test(Integer l) { 
            return l <= 3; 
          } 
        }) 
        .max(new Comparator<Integer>() { 
          @Override 
          public int compare(Integer o1, Integer o2) { 
            return o1 - o2; 
          } 
        }); 

The similarities and differences between Lambda expressions and anonymous classes are concentrated in the following three points:

  • Lambda was born to optimize anonymous inner classes. Lambda is much more concise than anonymous classes.
  • Lambda is only applicable to functional interfaces, and anonymous classes are not restricted.
  • That is, the this in the anonymous class is the "anonymous class object" itself; the this in the Lambda expression refers to the "object that calls the Lambda expression".

Function interface

In fact, the Function, Predicate, and Comparator in the code above are all functional interfaces. We know that C language supports function pointers, which can use functions directly as variables.

However, Java does not have a syntax for function pointers. So it wraps the function in the interface through the function interface and uses it as a variable. In fact, a functional interface is an interface. However, it also has its own special place, that is, it requires only one unimplemented method. Because only in this way, the Lambda expression can clearly know which method is matched. If there are two unimplemented methods, and the input parameters and return values ​​of the interface are the same, then when Java translates the Lambda expression, it does not know which method the expression corresponds to.

Functional interface is also a kind of Java interface, but it also needs to meet:

  • A functional interface has only one abstract method (single abstract method);
  • The public abstract method in the Object class will not be regarded as a single abstract method;
  • Functional interfaces can have default methods and static methods;
  • Functional interfaces can be decorated with @FunctionalInterface annotations.

An interface that meets these conditions can be regarded as a functional interface. For example, the Comparator interface in Java 8:

@FunctionalInterface 
public interface Comparator<T> { 
    /** 
     * single abstract method 
     * @since 1.8 
     */ 
    int compare(T o1, T o2); 
 
    /** 
     * Object类中的public abstract method  
     * @since 1.8 
     */ 
    boolean equals(Object obj); 
 
    /** 
     * 默认方法 
     * @since 1.8 
     */ 
    default Comparator<T> reversed() { 
        return Collections.reverseOrder(this); 
    } 
 
     
    /** 
     * 静态方法 
     * @since 1.8 
     */ 
    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { 
        return Collections.reverseOrder(); 
    } 
 
    // omitted ...  

What's the use of functional interfaces? In a word, the biggest advantage of functional interfaces is that we can use minimal lambda expressions to instantiate interfaces. Why do you say that? We have more or less used interfaces with only one abstract method, such as Runnable, ActionListener, Comparator, etc. For example, we need to use Comparator to implement sorting algorithms. Our processing methods are usually nothing more than two:

  • Write a Java class that implements the Comparator interface properly to encapsulate the sorting logic. If the business requires multiple sorting methods, you have to write multiple classes to provide multiple implementations, and these implementations often only need to be used once.
  • Another smarter approach is nothing more than creating anonymous inner classes where needed, such as:
public class Test {  
    public static void main(String args[]) {  
        List<Person> persons = new ArrayList<Person>(); 
        Collections.sort(persons, new Comparator<Person>(){ 
            @Override 
            public int compare(Person o1, Person o2) { 
                return Integer.compareTo(o1.getAge(), o2.getAge()); 
            } 
        }); 
    }  
} 

The amount of code implemented by anonymous inner classes is not much, and the structure is fairly clear. The implementation of Comparator interface in Jdk 1.8 adds FunctionalInterface annotation, which means that Comparator is a functional interface, and users can safely instantiate it through lambda expressions. Then let's take a look at the code that needs to be written to use lambda expressions to quickly new a custom comparator:

Comparator<Person> comparator = (p1, p2) -> Integer.compareTo(p1.getAge(), p2.getAge()); 

-> The front () is the parameter list of the compare method in the Compare method, and the back -> is the method body of the compare method.

The following is an excerpt of the source code of the two function interfaces provided by Java: Function and Predicate:

@FunctionalInterface 
public interface Function<T, R> { 
    R apply(T t);  // 只有这一个未实现的方法 
 
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { 
        Objects.requireNonNull(before); 
        return (V v) -> apply(before.apply(v)); 
    } 
 
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { 
        Objects.requireNonNull(after); 
        return (T t) -> after.apply(apply(t)); 
    } 
 
    static <T> Function<T, T> identity() { 
        return t -> t; 
    } 
 
@FunctionalInterface 
public interface Predicate<T>{ 
    boolean test(T t); // Only this unimplemented method 
 
    default Predicate<T> and(Predicate<? super T> other) { 
        Objects.requireNonNull(other); 
        return (t) -> test(t) && other.test(t); 
    } 
 
    default Predicate<T> negate() { 
        return (t) -> !test(t); 
    } 
 
    default Predicate<T> or(Predicate<? super T> other) { 
        Objects.requireNonNull(other); 
        return (t) -> test(t) || other.test(t); 
    } 
 
    static <T> Predicate<T> isEqual(Object targetRef) { 
        return (null == targetRef) 
                ? Objects::isNull 
                : object -> targetRef.equals(object); 
    } 
} 

@FunctionalInterface annotation usage scenarios

We know that as long as an interface meets the condition of only one abstract method, it can be used as a functional interface. It doesn't matter whether it has @FunctionalInterface or not. However, there must be a reason why jdk defines this annotation. For developers, the use of this annotation must be thought twice before proceeding.

If this annotation is used and an abstract method is added to the interface, the compiler will report an error and the compilation will fail. In other words, @FunctionalInterface is a promise that only this abstract method will exist in the interface for generations. Therefore, developers can use Lambda to instantiate any interface that uses this annotation. Of course, the consequences of misusing @FunctionalInterface are extremely disastrous: if one day you remove this annotation and add an abstract method, all client code that uses Lambda to instantiate the interface will be compiled incorrectly.

In particular, when an interface has only one abstract method, but it is not decorated with the @FunctionalInterface annotation, it means that others have not promised that the interface will not add abstract methods in the future, so it is recommended not to use Lambda to instantiate, or honestly use the previous one The method is relatively safe.

summary

Functional programming is more in line with the idea of ​​mathematical function mapping. Specific to the programming language level, we can use Lambda expressions to quickly write function mappings, and functions are connected through chain calls to complete the required business logic. Lambda expressions in Java were introduced later. Due to the advantages of functional programming in parallel processing, they are being widely used in the field of big data computing.

Guess you like

Origin blog.csdn.net/sinat_37903468/article/details/108735418