Writing in the front

As mentioned earlier, an important criterion for determining whether a language supports functional programming is whether it treats functions as “first-class citizens.”

Functions are “first class citizens,” meaning they have the same status as other data types — they can be assigned to a variable, used as an argument to another function, or returned as a value from another function.

Java 8 gives functions the ability to be “first-class citizens” through functional interfaces.

This article takes a closer look at functional interfaces in Java 8.

Sample code for this article is available at gitee, gitee.com/cnmemset/ja…

Complete column access, can pay attention to the public number [member said].

Functional interface

What is a function interface? All interfaces with only one abstract method are functional interfaces.

As per the specification, it is strongly recommended that you annotate @functionalinterface when defining a FunctionalInterface so that it can be determined at compile time whether the interface complies with the FunctionalInterface specification. Of course, you can leave @functionalInterface unannotated, which does not affect the definition and use of functional interfaces.

The following is a typical functional interface:

// The annotation @functionalInterface is strongly recommended
@FunctionalInterface
public interface Consumer {
    // The only abstract method
    void accept(T t);

    // There can be multiple non-abstract methods (default methods)
    default Consumer andThen(Consumer after) {
        Objects.requireNonNull(after);
        return(T t) -> { accept(t); after.accept(t); }; }}Copy the code

A functional interface is essentially an interface, so we can implement a functional interface through a concrete class (including anonymous classes). But unlike normal interfaces, the implementation of a functional interface can also be a lambda expression or even a method reference.

Let’s walk through some of the typical functional interfaces built into the JDK.

Functional interfaces built into Java 8

The new built-in functional interfaces in Java 8 are defined in the java.util.function package and include:

1. Functions

Function

In the code world, the most common type of functional interface is to take a parameter value and return a response value. The JDK provides a standard generic Function interface:

@FunctionalInterface
public interface Function<T.R> {
    /** * Given a parameter T of type T, return a response value of type R. * *@paramT function argument *@returnResult */
    R apply(T t);
}
Copy the code

 

A classic use of Function is the computeIfAbsent Function for a Map.

public V computeIfAbsent(K key,
                         Function<? super K, ? extends V> mappingFunction);
Copy the code

The computeIfAbsent function checks whether the key exists in the map. If the key does not exist, the mappingFunction calculates a value, writes the key/value pair to the map, and returns the calculated value. If the key already exists, the value corresponding to the key in the map is returned.

In a hypothetical application scenario, we want to build a HashMap where key is a word and value is the letter length of the word. Example code is as follows:

public static void testFunctionWithLambda(a) {
    // Build a HashMap where key is a word and value is the letter length of the word
    Map wordMap = new HashMap<>();
    Integer wordLen = wordMap.computeIfAbsent("hello", s -> s.length());
    System.out.println(wordLen);
    System.out.println(wordMap);
}
Copy the code

The above example would print:

5
{hello=5}
Copy the code

Notice the code snippet “s -> s.length()”, which is a typical lambda expression with the same meaning as a function:

public static int getStringLength(String s) {
    return s.length();
}
Copy the code

A more detailed introduction to lambda expressions can be found in a subsequent article series.

As mentioned earlier, functional interfaces can also be implemented through a method reference. Example code is as follows:

public static void testFunctionWithMethodReference(a) {
    Map wordMap = new HashMap<>();
    Integer wordLen = wordMap.computeIfAbsent("hello", String::length);
    System.out.println(wordLen);
    System.out.println(wordMap);
}
Copy the code

Noting that methods reference “String:: Length,” Java 8 allows us to convert an instance method into an implementation of a functional interface. It has the same meaning as the lambda expression “s -> s.length()”.

A more detailed introduction to method references can be found in a subsequent article series.

BiFunction

Function is limited to one argument, but two arguments are quite common, so BiFunction takes two parameter values and then returns a response value.

@FunctionalInterface
public interface BiFunction<T.U.R> {
    /** * Given a parameter T of type T and a parameter U of type U, return a response of type R. * *@paramT the first argument *@paramU The second argument *@returnResult */
    R apply(T t, U u); . }Copy the code

A classic use of Function is the replaceAll Function of Map.

public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
Copy the code

The replaceAll function of a Map iterates through all entries in the Map, calculates a new value using the BiFunction parameter function, and then replaces the old value with the new value.

In a hypothetical application scenario, we use a HashMap to record some words and their length, and then the product manager has a new requirement to uniformly record the length of some specified words to zero. Example code is as follows:

public static void testBiFunctionWithLambda(a) {
    Map wordMap = new HashMap<>();
    wordMap.put("hello".5);
    wordMap.put("world".5);
    wordMap.put("on".2);
    wordMap.put("at".2);
    
    // K and v in the lambda expression are the key and original value of the Entry in the Map respectively.
    // The return value of the lambda expression is a new value value.
    wordMap.replaceAll((k, v) -> {
        if ("on".equals(k) || "at".equals(k)) {
            // Corresponds to the word on and at, and the length of the word is 0
            return 0;
        } else {
            // For other words, the length of the word remains unchanged
            returnv; }}); System.out.println(wordMap); }Copy the code

The output of the above code is:

{world=5, at=0, hello=5, on=0}
Copy the code

2. Supplier

In addition to Function and BiFunction, a common functional interface is to return a response value without any arguments. This is Supplier:

@FunctionalInterface
public interface Supplier<T> {
    /** * get an instance of an object of type T. * *@returnObject instance */
    T get(a);
}
Copy the code

A typical application scenario for Supplier is to quickly implement factory-class production methods, including delayed or asynchronous production methods. Example code is as follows:

public class SupplierExample {
    public static void main(String[] args) {
        testSupplierWithLambda();
    }
    
    public static void testSupplierWithLambda(a) {
        final Random random = new Random();

        // Generate a random integer
        lazyPrint(() -> {
            return random.nextInt(100);
        });

        // Delay 3 seconds, generate a random integer
        lazyPrint(() -> {
            try {
                System.out.println("waiting for 3s...");
                Thread.sleep(3*1000);
            } catch (InterruptedException e) {
                // do nothing
            }

            return random.nextInt(100);
        });
    }

    public static void lazyPrint(Supplier lazyValue) { System.out.println(lazyValue.get()); }}Copy the code

The output looks like this:

26
waiting for 3s...
27
Copy the code

3. Consumers

If a Supplier is a producer, its opposite is a Consumer.

Consumer

In contrast to Supplier, a Consumer receives an argument and does not return any value.

@FunctionalInterface
public interface Consumer<T> {
    /** * Performs operations on a given single parameter. * *@paramT Enter the parameter */
    void accept(T t); . }Copy the code

Sample code:

public static void testConsumer(a) {
    List list = Arrays.asList("Guangdong"."Zhejiang"."Jiangsu");

    // Consume each element in the list
    list.forEach(s -> System.out.println(s));
}
Copy the code

The output of the above code is:

Guangdong
Zhejiang
Jiangsu
Copy the code

BiConsumer

And BiConsumer, which has the same semantics as Consumer, except that BiConsumer accepts two parameters.

@FunctionalInterface
public interface BiConsumer<T.U> {
    /** * Perform operations on the given two parameters. * *@paramT the first argument *@paramU The second argument */
    void accept(T t, U u); . }Copy the code

Sample code:

public static void testBiConsumer(a) {
    Map cityMap = new HashMap<>();
    cityMap.put("Guangdong"."Guangzhou");
    cityMap.put("Zhejiang"."Hangzhou");
    cityMap.put("Jiangsu"."Nanjing");

    // Consume each (key, value) key-value pair in the map
    cityMap.forEach((key, value) -> {
        System.out.println(String.format("The capital of %s is %s", key, value));
    });
}
Copy the code

The output of the above code is:

The capital of Guangdong is Guangzhou and Zhejiang is Hangzhou. The capital of Jiangsu is NanjingCopy the code

4. Predicate

Predicate means that it takes a parameter value and then returns a Boolean value based on a given Predicate condition. It is essentially a special Function, a Function that specifies a return value of type Boolean.

@FunctionalInterface
public interface Predicate<T> {
    /** * A Boolean result is computed based on the given argument. * *@paramT Enter the parameter *@returnReturns true if the argument meets the assertion condition, false */ otherwise
    boolean test(T t); . }Copy the code

The scenarios for Predicate are often used as some kind of filtering condition. Example code:

public static void testPredicate(a) {
    List provinces = new ArrayList<>(Arrays.asList("Guangdong"."Jiangsu"."Guangxi"."Jiangxi"."Shandong"));

    boolean removed = provinces.removeIf(s -> {
        return s.startsWith("G");
    });
    
    System.out.println(removed);
    System.out.println(provinces);
}
Copy the code

The code above filters out the provinces starting with the letter G and outputs:

true
[Jiangsu, Jiangxi, Shandong]
Copy the code

5. Operators

The Operator functional interface is a special type of Function that requires the return value type to be the same as the parameter type.

Like Function/BiFunction, Operators also supports one or two arguments.

UnaryOperator

UnaryOperator supports one argument. UnaryOperator<T> is equivalent to Function<T, T> :

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T.T> {... }Copy the code

UnaryOperator converts province pinyin to uppercase and lowercase letters:

public static void testUnaryOperator(a) {
    List provinces = Arrays.asList("Guangdong"."Jiangsu"."Guangxi"."Jiangxi"."Shandong");
    
    // Convert the province letters to uppercase letters
    // Use lambda expressions to implement UnaryOperator
    provinces.replaceAll(s -> s.toUpperCase());
    System.out.println(provinces);
        
    // Convert the letters of the province to lowercase letters.
    // Implement UnaryOperator using method references
    provinces.replaceAll(String::toLowerCase);

    System.out.println(provinces);
}
Copy the code

The output of the above code is:

[GUANGDONG, JIANGSU, GUANGXI, JIANGXI, SHANDONG]
[guangdong, jiangsu, guangxi, jiangxi, shandong]
Copy the code

BinaryOperator

BinaryOperator supports two parameters. BinaryOperator<T> is the same as BiFunction<T, T, T>.

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T.T.T> {... }Copy the code

Example code for BinaryOperator — evaluates the sum of all integers ina List:

public static void testBinaryOperator(a) {
    List values = Arrays.asList(1.3.5.7.11);

    // Sum using reduce: 0+1+3+5+7+11 = 27
    int sum = values.stream()
            .reduce(0, (a, b) -> a + b);

    System.out.println(sum);
}
Copy the code

The output of the above code is:

27
Copy the code

6. Legacy functional interfaces from Java 7 and previous versions

The definition of a functional interface was mentioned earlier: all interfaces that have only one abstract method are functional interfaces.

According to this definition, some “older” interfaces defined in Java 7 or earlier are also functional interfaces, including:

Runnable, Callable, Comparator, etc.

Of course, these legacy functional interfaces are annotated with @functionalInterface in Java 8.

Composite functional interfaces

As we mentioned in our first article, functional programming is a programming paradigm in which the entire program consists of function calls and function combinations.

Function composing, “composing,” means to combine a series of simple functions into a composite function.

Functional interfaces in Java 8 also provide the ability to compose functions. Notice that almost every built-in functional interface has a nonabstract method, andThen. The function of the andThen method is to combine multiple functional interfaces and execute them one by one in serial order to form a new functional interface.

Take the consumer.andthen method, which returns a new Consumer instance. The new Consumer instance will execute the current ACCPET method and then the After AccPET method. The source code snippet is as follows:

@FunctionalInterface
public interface Consumer<T> {...default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);

        // Execute the accept method of the current Consumer first, and then the accept method of after
        // In particular, accept(t) cannot be written before the return statement, otherwise accept(t) will be executed earlier
        return(T t) -> { accept(t); after.accept(t); }; }... }Copy the code

Example code is as follows:

public static void testConsumerAndThen(a) {
    Consumer printUpperCase = s -> System.out.println(s.toUpperCase());
    Consumer printLowerCase = s -> System.out.println(s.toLowerCase());

    // Combine to get a new Consumer: print uppercase first, then lowercase
    Consumer prints = printUpperCase.andThen(printLowerCase);

    List list = Arrays.asList("Guangdong"."Zhejiang"."Jiangsu");

    list.forEach(prints);
}
Copy the code

The output of the above code is:

GUANGDONG
guangdong
ZHEJIANG
zhejiang
JIANGSU
jiangsu
Copy the code

The function. andThen method is more complex. It returns a new Function instance, in which the current apply method is executed with a parameter T of type T, resulting in a return value R of type R, andThen R is used as an input parameter. Continue with the apply method after, which returns a value of type V:

@FunctionalInterface
public interface Function<T.R> {
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);

        // execute the apply method with T of type T and return R of type R;
        // apply after to return a value of type V;
        // In particular, apply(t) cannot be written before a return statement, otherwise apply(t) will be executed earlier.
        return(T t) -> after.apply(apply(t)); }}Copy the code

Code examples:

public static void testFunctionAndThen(a) {
    // wordLen counts the length of words
    Function wordLen = s -> s.length(); S -> {return s.length(); }

    // effectiveWord Indicates a valid word only when the length of the word is greater than or equal to 4
    Function effectiveWordLen = len -> len >= 4;
    
    // Function and Function combine to get a new Function,
    // If the Integer type is removed, the Integer type is returned.
    Function effectiveWord = wordLen.andThen(effectiveWordLen);

    Map wordMap = new HashMap<>();
    wordMap.computeIfAbsent("hello", effectiveWord);
    wordMap.computeIfAbsent("world", effectiveWord);
    wordMap.computeIfAbsent("on", effectiveWord);
    wordMap.computeIfAbsent("at", effectiveWord);

    System.out.println(wordMap);
}
Copy the code

The output of the above code is:

{at=false, world=true, hello=true, on=false}
Copy the code

conclusion

Java 8 gives functions the ability to be “first-class citizens” through functional interfaces.

A functional interface allows functions, like any other data type, to be assigned to a variable, as an argument to another function, or as a return value to another function.

The implementation of a functional interface can be a class (including anonymous classes), but is more likely to be a lambda expression or a method reference.