This is the fourth day of my participation in the August Text Challenge.More challenges in August

Content preview

  • Custom functional interfaces
  • Functional programming
  • Common functional interfaces

Functional interface

1.1 concept

A functional interface in Java is an interface that has only one abstract method. Functional interfaces, that is, interfaces suitable for functional programming scenarios. And functional programming in Java is Lambda, so functional interfaces are interfaces that can be used with Lambda. Lambdas in Java can only be derived smoothly by ensuring that there is one and only one abstract method in the interface.

Note: “syntactic sugar” refers to code syntaxes that are more convenient to use, but the principle remains the same. For example, the for-each syntax is used when iterating through collections, but the underlying implementation principle is still iterators. This is syntactic sugar. At an application level, Lambdas in Java can be thought of as “syntactic sugar” for anonymous inner classes, but the two are different in principle.

1.2 format

Just make sure there is one and only one abstract method in the interface:

The modifierinterfaceThe name of the interface{
     public abstractReturn value type Method name (optional parameter information);// Other non-abstract method content
}
Copy the code

Since the public abstract of the abstract method in the interface can be omitted, defining a functional interface is simple:

public interface MyFunctionalInterface {
     void myMethod(a);      
}
Copy the code

1.3 @ FunctionalInterface annotation

Similar to the @Override annotation, a new annotation was introduced in Java 8 specifically for functional interfaces: @FunctionalInterface. This annotation can be used to define an interface:

@FunctionalInterface 
public interface MyFunctionalInterface {
     void myMethod(a);      
}
Copy the code

Once the annotation is used to define the interface, the compiler forces a check to see if the interface does have one and only one abstract method, or an error will be reported. Note that even if this annotation is not used, this is still a functional interface and works the same as long as it satisfies the definition of a functional interface.

1.4 Custom functional interfaces

For the MyFunctionalInterface function interface just defined, the typical usage scenario is as a method parameter:

public class Demo09FunctionalInterface {
     // Use a custom functional interface as a method parameter
      private static void doSomething(MyFunctionalInterface inter) {
              inter.myMethod(); // Call custom functional interface methods
      }
  
      public static void main(String[] args) {
      // Call methods that use functional interfacesDoSomething () ‐ > System. Out. Println ("Lambda executed!")); }}Copy the code

Functional programming

On the basis of taking into account the object-oriented features, the Java language opens the door to functional programming for developers through Lambda expressions and method references. Let’s do a preliminary discussion.

2.1 Delayed execution of Lambda

In some scenarios, the code may not be used after execution, resulting in wasted performance. Lambda expressions, on the other hand, execute lazily, which is just the right solution to improve performance.

Logging cases of wasted performance

Note: Logs can help us quickly locate problems and record the running process of programs for monitoring and optimization of the project. A typical scenario is conditional use of parameters, such as concatenating log messages and printing them if conditions are met:

public class Demo01Logger {
     private static void log(int level, String msg) {
         if (level == 1) { System.out.println(msg); }}public static void main(String[] args) {
         String msgA = "Hello";
         String msgB = "World";
         String msgC = "Java";
  
         log(1, msgA + msgB + msgC); }}Copy the code

The problem with this code is that regardless of the level, the three strings that are the second argument to the log method must be concatenated and passed into the square method before the level is determined. If the level does not meet the requirements, then the concatenation of strings is useless and there is a waste of performance.

Note: SLF4J is a widely used logging framework. It does not recommend concatenating strings first when logging. Instead, it passes several parts of the string into methods as variable parameters, and concatenates strings only when the logging level is sufficient. For example: logger.debug (” The value of the variable {} is {}.” , “OS”, “macOS”), where curly braces {} are placeholders. If the log level requirements are met, the OS and macOS strings are concatenated to the brace position. Otherwise, string concatenation is not performed. This is also a possible solution, but Lambda could do better.

Experience a better way of writing Lambda

Using Lambda necessarily requires a functional interface:

@FunctionalInterface 
public interface MessageBuilder {
       String buildMessage(a);   
}
Copy the code

Then transform the log method:

public class Demo02LoggerLambda {
     private static void log(int level, MessageBuilder builder) {
         if (level == 1) { System.out.println(builder.buildMessage()); }}public static void main(String[] args) {
         String msgA = "Hello";
         String msgB = "World";
         String msgC = "Java";
  
         log(1‐> msgA + msgB + msgC); }}Copy the code

Thus, concatenation of three strings is performed only when the level is sufficient; Otherwise, the three strings will not be concatenated.

Prove Lambda delay

The following code can be verified with the result:

public class Demo03LoggerDelay {
     private static void log(int level, MessageBuilder builder) {
         if (level == 1) { System.out.println(builder.buildMessage()); }}public static void main(String[] args) {
         String msgA = "Hello";
         String msgB = "World";
         String msgC = "Java";
  
         log(2‐> {system.out.println ()"Lambda execute!");
             returnmsgA + msgB + msgC; }); }}Copy the code

As you can see from the results, Lambda will not execute if the level requirements are not met. Thus achieving the effect of saving performance.

Extension: You can actually achieve the same effect using inner classes, but defer code operations to another object by calling methods. Whether or not to call the method is executed after the condition is determined.

2.2 Use Lambda as argument and return value

Regardless of implementation principles, Lambda expressions in Java can be thought of as substitutes for anonymous inner classes. If the argument to a method is a functional interface type, Lambda expressions can be used instead. Using Lambda expressions as method parameters is essentially using functional interfaces as method parameters. For example, the java.lang.Runnable interface is a functional interface, and if you have a startThread method that takes that interface as an argument, you can use Lambda to pass arguments. This is no different from the Thread class whose constructor parameter is Runnable.

public class Demo04Runnable {
     private static void startThread(Runnable task) {
        new Thread(task).start();
        }
  
     public static void main(String[] args) {startThread (() ‐ > System. Out. The println ("Thread task execution!")); }}Copy the code

Similarly, if a method’s return value type is a functional interface, then a Lambda expression can be returned directly. This method is invoked when a method is needed to get an object of type java.util.Comparator interface as a collator.

import java.util.Arrays; 
import java.util.Comparator;   

public class Demo06Comparator {
     private static Comparator<String> newComparator(a) {
        return(a, b) ‐> B. length() ‐ A. length(); }public static void main(String[] args) {
         String[] array = { "abc"."ab"."abcd"}; System.out.println(Arrays.toString(array)); Arrays.sort(array, newComparator()); System.out.println(Arrays.toString(array)); }}Copy the code

Simply return a Lambda expression.

Common functional interfaces

The JDK provides a number of commonly used functional interfaces to enrich typical use scenarios for Lambda, mainly provided in the java.util.function package. The following are some simple interfaces and usage examples.

3.1: Supplier interface

Java. Util. Function. : Supplier < T > interface contains only a no arguments: T get (). Gets the object data of the type specified by a generic parameter. Since this is a functional interface, this means that the corresponding Lambda expression needs to “supply” an object data that conforms to the generic type.

import java.util.function.Supplier;   

public class Demo08Supplier {
     private static String getString(Supplier<String> function) {
        return function.get();
        }
  
     public static void main(String[] args) {
         String msgA = "Hello";
         String msgB = "World"; System.out.println(getString() ‐> msgA + msgB)); }}Copy the code

3.2 Exercise: Find the maximum value of an array element

The title

Evaluates the large value in an int array using the Supplier interface as the method parameter type using a Lambda expression. Tip: Use the java.lang.Integer class for generics of the interface.

answer

public class Demo02Test {
     // Specify a method that passes Supplier to the method and uses Integer for generics
     public static int getMax(Supplier<Integer> sup){
         return sup.get();
     }
  
     public static void main(String[] args) {
         int arr[] = {2.3.4.52.333.23};
  
         // Call getMax, passing Lambda
         intMaxNum = getMax (() ‐ > {// Calculate the large value of the array
            int max = arr[0];
            for(int i : arr){
                if(i>max){ max = i; }}returnmax; }); System.out.println(maxNum); }}Copy the code

3.3 Consumer interface

Java. Util. The function. The Consumer < T > interface is just the opposite with: Supplier interface, it is not a production data, but the consumption of a data, the data type is determined by the generic.

Abstract method: Accept

The Consumer interface contains the abstract method void Accept (T T), which means consuming data of a specified generic type. Basic use such as:

import java.util.function.Consumer;   

public class Demo09Consumer {
     private static void consumeString(Consumer<String> function) {
        function.accept("Hello");
      }
  
     public static void main(String[] args) {consumeString (s ‐ > System. Out. Println (s)); }}Copy the code

Of course, a better way to write this is to use method references.

The default method is andThen

If the arguments and return values of a method are both of type Consumer, then you can achieve the effect of consuming data by first doing one operation and then doing another operation to implement the composition. That method is the default method andThen in the Consumer interface. Here is the source code for the JDK:

default Consumer<T> andThen(Consumer<? super T> after) {
     Objects.requireNonNull(after);
     return(T T) ‐> {accept(T); after.accept(t); }; }Copy the code

The requireNonNull static method of java.util.Objects will actively throw a NullPointerException if the argument is null. This saves the trouble of repeatedly writing if statements and throwing null-pointer exceptions.

Composition requires two or more Lambda expressions, and the semantics of andThen are “step by step” operations. For example, when two steps are combined:

import java.util.function.Consumer;   

public class Demo10ConsumerAndThen {     private static void consumeString(Consumer<String> one, Consumer<String> two) {
        one.andThen(two).accept("Hello");
        }
  
     public static void main(String[] args) {
         consumeString(
             s ‐> System.out.println(s.toUpperCase()),
             s ‐> System.out.println(s.toLowerCase()));
     } 
}
Copy the code

The result will print HELLO in all upper case first, and then in all lower case. Of course, more steps can be combined by using the chain notation.

3.4 Exercise: Format printed information

The title

The following string array contains multiple information, please follow the format “name: XX. Gender: XX.” Print the information out in the format of. Require that the action of printing the first and last name be the Lambda instance of the first Consumer interface and the action of printing gender be the Lambda instance of the second Consumer interface, and “splice” the two Consumer interfaces together in order.

public static void main(String[] args) {
    String[] array = { "Dilieba, female."."Gulinaza, female"."Malzaha, male." };    
}
Copy the code

answer

import java.util.function.Consumer;   

public class DemoConsumer {
     public static void main(String[] args) {
         String[] array = { "Dilieba, female."."Gulinaza, female"."Malzaha, male."}; PrintInfo (s ‐ > System. Out. Print ("Name:" + s.split(",") [0]), s ‐ > System. Out.println (". Gender:" + s.split(",") [1] + "。"),
                   array);
     }
       private static void printInfo(Consumer<String> one, Consumer<String> two, String[] array) {
         for (String info : array) {
             one.andThen(two).accept(info); // Name: Dilieba. Gender: Female.}}}Copy the code

3.5 Predicate interface

Sometimes we need to evaluate certain types of data to get a Boolean result. Then you can use Java. Util. The function. The Predicate < T > interface.

Abstract method: test

The Predicate interface contains an abstract method: Boolean test(T T). Scenarios for conditional judgment:

import java.util.function.Predicate;   

public class Demo15PredicateTest {
     private static void method(Predicate<String> predicate) {
         boolean veryLong = predicate.test("HelloWorld");
         System.out.println("Is it a long string?" + veryLong);
     }
  
     public static void main(String[] args) {method(s ‐> s.length() >5); }}Copy the code

The criterion for the condition is the incoming Lambda expression logic, which considers any string longer than 5 to be very long.

The default method is and

Since it is a conditional judgment, there will be and, or, not three common logical relations. You can use the default method and when you connect two Predicate conditions together using “and” logic to implement “and”. The JDK source code is:

default Predicate<T> and(Predicate<? super T> other) {
     Objects.requireNonNull(other);
     return (t) ‐> test(t) && other.test(t); 
}
Copy the code

If you want to determine that a string contains both a capital “H” and a capital “W”, then:

import java.util.function.Predicate;   

public class Demo16PredicateAnd {
     private static void method(Predicate<String> one, Predicate<String> two) {
         boolean isValid = one.and(two).test("Helloworld");
         System.out.println("Does the string meet the requirements:" + isValid);
     }
  
     public static void main(String[] args) {method (s ‐ > s.c ontains ("H"), s ‐ > s.c ontains ("W")); }}Copy the code

The default method is OR

Similar to the “and” of and, the default method or implements “or” in logical relationships. JDK source code:

default Predicate<T> or(Predicate<? super T> other) {
     Objects.requireNonNull(other);
     return(t) ‐ > test (t) | |. Other test (t); }Copy the code

If you want to implement the logic “string with capital H or with capital W”, the code simply changes the “and” to the “or” name, and nothing else changes:

import java.util.function.Predicate;   

public class Demo16PredicateAnd {
     private static void method(Predicate<String> one, Predicate<String> two) {
         boolean isValid = one.or(two).test("Helloworld");
         System.out.println("Does the string meet the requirements:" + isValid);
     }
  
     public static void main(String[] args) {method (s ‐ > s.c ontains ("H"), s ‐ > s.c ontains ("W")); }}Copy the code

Default: negate

The “and” and “or” are understood, and the rest of the “not” will be easy. The JDK source code for the default method negate is:

default Predicate<T> negate(a) {
     return(t) ‐ >! test(t); }Copy the code

It is easy to see from the implementation that the result Boolean is called “! “after executing the test method. Take instead already. Be sure to call the negate method before the test method is called, just like the AND and OR methods:

import java.util.function.Predicate;   

public class Demo17PredicateNegate {
     private static void method(Predicate<String> predicate) {
         boolean veryLong = predicate.negate().test("HelloWorld");
         System.out.println("Is it a long string?" + veryLong);
     }
  
     public static void main(String[] args) {method(s ‐> s.length() <5); }}Copy the code

3.6 Exercise: Collection information filtering

The title

There are multiple “names and genders” in the array as follows. Use the Predicate interface to filter the required strings into the ArrayList. Both conditions must be met:

  1. It must be female;
  2. The name is four characters long.
public class DemoPredicate {
     public static void main(String[] args) {
        String[] array = { "Dilieba, female."."Gulinaza, female"."Malzaha, male."."Zhao Liying, female"}; }}Copy the code

answer

import java.util.ArrayList; 
import java.util.List; 
import java.util.function.Predicate;   

public class DemoPredicate {
     public static void main(String[] args) {
         String[] array = { "Dilieba, female."."Gulinaza, female"."Malzaha, male."."Zhao Liying, female" };
         List<String> list = filter(array,
                                    s ‐> "Female".equals(s.split(",") [1]), s ‐ > s.s plit (",") [0].length() == 4);
         System.out.println(list);
     }
  
     private static List<String> filter(String[] array, Predicate
       
         one, Predicate
        
          two)
        
        {
         List<String> list = new ArrayList<>();
         for (String info : array) {
             if(one.and(two).test(info)) { list.add(info); }}returnlist; }}Copy the code

3.7 the Function interface

Java. Util. The function. The function < T, R > interface is used to according to a type of data to get another type of data, the former is called the precondition, the latter is called post-conditions. Abstract methods: The main abstract methods in the apply Function interface are: R apply(T T), which obtains the results of type R based on the parameters of type T. The scenarios used are as follows: convert String to Integer.

import java.util.function.Function;   

public class Demo11FunctionApply {
     private static void method(Function<String, Integer> function) {
         int num = function.apply("10");
         System.out.println(num + 20);
     }
  
     public static void main(String[] args) {method (s ‐ > Integer. ParseInt (s)); }}Copy the code

Of course, the good thing is to write it by method reference.

The default method is andThen

The Function interface has a default andThen method for composite operations. The JDK source code is as follows:

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
     Objects.requireNonNull(after);
     return(T T) ‐ > after the apply (apply (T)); }Copy the code

This method also works in the “do something first, do something later” scenario, similar to andThen in Consumer:

import java.util.function.Function;   

public class Demo12FunctionAndThen {     private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
         int num = one.andThen(two).apply("10");
         System.out.println(num + 20);
     }
  
     public static void main(String[] args) {method (STR ‐ > Integer. ParseInt (STR) +10, i ‐> i *= 10); }}Copy the code

The first operation parses the string to an int number, and the second operation multiplies it by 10. The two operations are combined sequentially through andThen.

Note that the preconditional generics for Function can be the same as the postconditional generics.

3.8 Exercise: Custom function model splicing

The title

String STR = “zhao Liying,20″; String STR =” zhao Liying,20″;

  1. Truncate the numeric age part of the string to get the string;
  2. Converts the string from the previous step to a number of type int.
  3. Add the int number from the previous step to 100 to get the int number.

answer

import java.util.function.Function;   

public class DemoFunction {
     public static void main(String[] args) {
         String str = "Zhao Liying,20";
         intAge = getAgeNum(STR, s ‐> s.split(",") [1], S ‐> INTEger.parseint (s), n ‐> N +=100);
         System.out.println(age);
     }
  
     private static int getAgeNum(String str, Function
       
         one, Function
        
          two, Function
         
           three)
         ,>
        ,>
       ,> {
         returnone.andThen(two).andThen(three).apply(str); }}Copy the code