Exploration and practice of JDK elegant programming features

Chain programming

summary

JDK chain programming has the advantages of strong programmability, readability and concise code. The principle of chain programming is to return a this object, that is, to return itself to achieve the chain effect. For example, the StringBuilder of JDK is to realize the effect of chain programming.

StringBuilder builder = new StringBuilder();
builder.append("aa").append("bb").append("cc").append("dd").append("ee");

Code example

Custom implementation

Student java

package cn.itxs.chain;

public class Student {
    private String name;
    private int age;
    private String address;

    public String getName() {
        return name;
    }

    public Student setName(String name) {
        this.name = name;
        return this;
    }

    public int getAge() {
        return age;
    }

    public Student setAge(int age) {
        this.age = age;
        return this;
    }

    public String getAddress() {
        return address;
    }

    public Student setAddress(String address) {
        this.address = address;
        return this;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

Test class

package cn.itxs.chain;

public class ChainMain {
    public static void main(String[] args) {
        Student student = new Student();
        student.setName("Li Shanshan").setAge(20).setAddress("Chaoyang District of Beijing City");
        System.out.println(student.toString());
    }
}

Lombok use

Add dependency to pom file

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.22</version>
    </dependency>

User class user java

package cn.itxs.other;

import com.sun.istack.internal.NotNull;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;

@Accessors(chain = true)
@Data
@RequiredArgsConstructor(staticName = "of")
public class User {
    @NotNull
    private String name;
    private int age;
}

Test class

package cn.itxs.other;

public class UMain {
    public static void main(String[] args) {
        User user = User.of("Chen Shuishui").setAge(25);
        System.out.println(user.toString());
    }
}

Lambada expression

summary

  • Lambda expression is an anonymous function. We can understand lambda expression as a piece of code that can be passed; Lambada expression is a new feature of JDK8 to avoid too many anonymous inner class definitions; Get rid of a pile of meaningless code, leave only the core logic and focus on logic implementation; You can write more concise, flexible and elegant Java code.
  • Lambda expression is very simple, so it must be used in a special context, so that the compiler can infer which method the lambda expression is and calculate the value of the lambda expression. The value of the lambda expression is the entry address of the method. Therefore, lambda can only implement the interface with only one abstract method.
    • Functional interface refers to an interface with only one abstract method; Java 8 introduces annotation @ functional interface to modify and check functional interfaces.
    • In case of multiple interface inheritance, the problem of default method coverage may occur. In this case, you can specify which interface's default method implementation is used.
  • Basic grammar
    • A new operator "- >", called arrow operator or Lambda operator, is introduced into Java 8; The arrow operator splits the Lambda expression into left and right parts.
    • Left: parameter list of Lambda expression
      • If there is only one parameter, the left parenthesis can not be written, but it is recommended to bring parentheses based on readability.
      • The data type of the parameter list of Lambda expression can be omitted, because the JVM compiler infers the data type through the context, that is, "type inference" (syntax sugar); If you want to write data types, you have to write them all.
    • Right: the function to be executed in the Lambda expression, that is, the Lambda body; There is only one statement on the right. Braces {} and return can be omitted. There are three forms
      • No parameter, no return value
      • There are parameters but no return value
      • There are parameters and return values
  • Method reference
    • When the Lambda body has only one statement, you can reference methods and constructors through English "::.

Code example

Thread call

When Java does not support lambda expressions, we need many lines of code to create a thread. The creation of a thread can be completed with one sentence of code using lambda expressions. Runnable is a functional interface.

new Thread(() -> System.out.println("hello lambada thread demo")).start();

Custom functional interface

The JDK8 interface supports the default implementation method. There is no default method before. Modifying the interface code will have a great impact; The function interface feature can provide a default implementation

Custom functional interface: OperateInterface

package cn.itxs.lambada;

@FunctionalInterface
public interface OperateInterface {
    int caculate(int num1,int num2,int num3);
    default int subtract(int num1, int num2) {
        return num1 -num2;
    }
}

Test class

package cn.itxs.lambada;

public class LambadaMain {

    private static void printDemo(int num1,int num2,int num3, OperateInterface operateInterface) {
        System.out.println(operateInterface.caculate(num1, num2, num3));
    }

    private static void printDescription(String description, UserBuilder userBuilder) {
        System.out.println(userBuilder.build(description).getDescription());
    }

    public static void main(String[] args) {
        //lambda expressions implemented directly
        printDemo(10,20,30,(int num1,int num2,int num3) -> (num1 + num2) * num3);
        //Class static method lambda expression
        printDemo(10,20,30,(int num1,int num2,int num3) -> Operater.muti(10,20,30));
        //Class static method reference
        printDemo(10,20,30,Operater::muti);
        Operater operater = new Operater();
        //Object method lambda expression
        printDemo(10,20,30,(int num1,int num2,int num3) -> operater.muti2(10,20,30));
        //Object method reference
        printDemo(10,20,30,operater::muti2);

        printDescription("Constructor reference test lambda expression",description -> new User(description));
        printDescription("Constructor reference test method reference",User::new);
    }
}

class Operater {
    public static int muti(int num1, int num2, int num3){
        return num1 * num2 * num3;
    }

    public int muti2(int num1,int num2,int num3){
        return num1 * num2 - num3;
    }
}

@FunctionalInterface
interface UserBuilder{
    User build(String description);
}

class User{
    private String description;

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public User(String description) {
        this.description = description;
    }
}

Common functional interface

summary

  • JDK provides a large number of built-in functional interfaces for us to use, making the use of lambda expressions more convenient and efficient; Lambda expressions are the basis of functional programming.
  • Jdk8 Java util. The function package provides common functional interfaces.

Predict interface

Predicate is an assertion function interface. It receives the parameter object T and returns a boolean result. In JDK, the function interface code is as follows:

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-y2grfqz0-1642690079887) (image-20220119164339846. PNG)]

Test class

package cn.itxs.func;

import java.util.function.Predicate;

public class FunctionalMain {
    public static void main(String[] args) {
        Predicate<Integer> predicate = i -> {
            System.out.println("Assert functional interface test, input value:" + i);
            return i > 100;
        };
        System.out.println(predicate.test(100));
        System.out.println(predicate.test(101));

        Predicate<String> predicateStr = str -> {
            System.out.println("Assert functional interface test, input value:" + str);
            return "true".equals(str);};
        System.out.println(predicateStr.test("true"));
        System.out.println(predicateStr.test("haha"));
        
        //Interfaces generally have encapsulation of basic types, and there is no need to specify generic types when using specific types of interfaces, such as longpredict, doublepredicte, IntConsumer, etc
        LongPredicate longPredicate = i -> {
            System.out.println("Long integer assertion, functional interface test, input value:" + i);
            return i > 100000;
        };
        System.out.println(longPredicate.test(100000));
        System.out.println(longPredicate.test(100001));
    }
}

Supplier and Consumer interfaces

  • Supplier supplier function interface: it does not receive parameters and provides the creation factory of T object
  • Consumer function interface: receives a T-type parameter and returns no result
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

Test code

package cn.itxs.func;

import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class FunctionalMain {
    public static void main(String[] args) {
        Supplier<Integer>  supplier = () -> {
            System.out.println("Supplier functional interface test, randomly generate integer values within 100");
            return new Random().nextInt(100);
        };
        System.out.println(supplier.get());

        Consumer<String> consumer = (message) -> {
            System.out.println("Consumer functional interface test, received message" + message);
        };
        consumer.accept("hello java");
    }
}

Function and BiFunction interfaces

  • The Function interface receives the parameter object T and returns the result object R.
  • The BiFunction interface is just one more input than the Function interface.
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

Test code

package cn.itxs.func;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import java.util.function.Function;

public class FunctionalMain {
    public static void main(String[] args) {
        Function<Integer, List<Integer>> function = (i) ->{
            System.out.println("Function Functional interface test, enter an integer and return a set of 3 copied values");
            List<Integer> listInt = new CopyOnWriteArrayList<>();
            listInt.add(i);
            listInt.add(i);
            listInt.add(i);
            return listInt;
        };
        List<Integer> list1 = function.apply(100);
        System.out.println(list1);

        BiFunction<Integer,Integer,List<String>> biFunction = (i1, i2) -> {
            System.out.println("BiFunction Functional interface test, enter an integer and return the string set of the sum of two numbers");
            List<String> listStr = new ArrayList<>();
            listStr.add(String.valueOf(i1+i2));
            return listStr;
        };

        List<String> list2 = biFunction.apply(100001,200001);
        System.out.println(list2);
    }
}

UnaryOperator and BinaryOperator interfaces

  • The UnaryOperator interface receives the parameter object T and returns the result object t.
  • The BinaryOperator interface adds one more receiving parameter object than the UnaryOperator interface. Receive two T objects and return a T object result.
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }
    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

Test class

package cn.itxs.func;

import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;

public class FunctionalMain {
    public static void main(String[] args) {
        UnaryOperator<Integer> unaryOperator = (i) -> {
            System.out.println("unaryOperator Functional interface test, enter integer and return integer+1");
            return i+1;
        };
        System.out.println(unaryOperator.apply(100));

        BinaryOperator<String> binaryOperator = (str1,str2) -> {
            System.out.println("binaryOperator Functional interface test, enter two strings and return two strings");
            return str1 + str2;
        };
        System.out.println(binaryOperator.apply("good deed", "In pairs"));
    }
}

Optional

summary

Optional wrap the parameters of all methods with optional to avoid null pointers.

Basic use

package cn.itxs.optional;

import java.util.Optional;

public class OptionalMain {
    public static void main(String[] args) {
        // The third way is to create an empty Optional object directly through the empty method
        Optional<Object> option = Optional.empty();

        //It is not allowed to wrap null objects, otherwise the program will report an error
        //Optional op = Optional.of(null);

        //The package object is allowed to be nul, and empty option is returned when it is null
        Optional op = Optional.ofNullable(null);
        System.out.println(op);

        //When using get() to get the object in the container, if the object is null, there will be Java util. NoSuchElementException exception. Therefore, it is best to judge isPresent() first. If it returns true, it indicates that it exists, and then obtain it
        //op.get();

        //Judge whether two options are equal, mainly whether the wrapped objects are equal
        Optional op1 = Optional.of("java");
        Optional op2 = Optional.ofNullable("java");
        System.out.println(op1.equals(op2));
        op2 = Optional.of("java1");
        System.out.println(op1.equals(op2));

        //If the value exists and meets the assertion, it returns Optional that meets the condition, otherwise it returns empty, which is often used for filtering
        Optional<String> op3 = Optional.of("itxiaoshen");
        Optional r1 = op3.filter((str)-> str.length() > 15);
        System.out.println(r1);

        //If the value exists, perform the mapping function operation on it. If the map result is not empty, it returns Optional, otherwise it returns empty
        Optional r2 = op3.map((str) -> "hello->" + str);
        System.out.println(r2);

        //Similar to the map above, but you need to encapsulate yourself as Optional in the map function.
        Optional res = op3.flatMap((str) -> Optional.ofNullable("hello->"+str));
        System.out.println(res);

        //If the value exists, it returns; otherwise, it returns other values, which is equivalent to the default value.
        Optional<String> op4 = Optional.ofNullable(null);
        System.out.println(op4.orElse("haha"));
        op4 = Optional.ofNullable("hello");
        System.out.println(op4.orElse("haha"));

        //The function is similar to orElse above, except that it can use lambda expressions to handle the returned default values more flexibly
        Optional<String> op5 = Optional.ofNullable(null);
        System.out.println(op5.orElseGet(() -> String.valueOf(true)));

        //If the value exists, the lambda expression is executed. Otherwise, no processing is done and it does not return a value.
        Optional<String> op6 = Optional.ofNullable("itxiaoshen");
        op6.ifPresent((str) -> System.out.println(str));
        System.out.println("Separate output------------");
        op6 = Optional.ofNullable(null);
        op6.ifPresent((str) -> System.out.println(str));
    }
}

Application examples

There are Squad, Platoon and Company

package cn.itxs.optional;

public class Squad {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package cn.itxs.optional;

public class Platoon {
    private Squad squad;

    public Squad getSquad() {
        return squad;
    }

    public void setSquad(Squad squad) {
        this.squad = squad;
    }
}
package cn.itxs.optional;

public class Company {
    private Platoon platoon;

    public Platoon getPlatoon() {
        return platoon;
    }

    public void setPlatoon(Platoon platoon) {
        this.platoon = platoon;
    }
}

Test class

package cn.itxs.optional;

import java.util.Optional;

public class OptionalDemo {
    public static void main(String[] args) {
        //It turns out that obtaining the name value of square requires multiple if statements
        Company company = new Company();
        String name = null;
        if (null != company){
            if (null != company.getPlatoon()){
                if (null != company.getPlatoon().getSquad()){
                    name = company.getPlatoon().getSquad().getName();
                }
            }
        }

        name = Optional.ofNullable(company)
                .map(c -> c.getPlatoon())
                .map(p -> p.getSquad())
                .map(s -> s.getName())
                .orElse("I am a regiment");
        System.out.println(name);
    }
}

Streaming programming

summary

  • Streaming programming is a new feature in 1.8. It performs pipeline like operations on set class data based on four commonly used functional interfaces and Lambda expressions. Using stream streaming programming can not only simplify code development and enhance readability, but also filter and map data without modifying the original set.
  • Flow programming is divided into three steps: obtaining flow → operating flow → returning operation results.
    • Get the data source and read the data in the data source into the stream.
    • Various operations are performed on the data in the stream and the stream object itself is returned. Such operations are called - intermediate operations.
    • You can perform various processing on the data in the stream and close the stream. Such an operation is called - terminate operation.
    • In the intermediate operation and final operation, almost all method parameters are functional interfaces, which can be implemented by lambda expressions. Using set flow programming to simplify the amount of code requires proficiency in lambda expressions.
  • In Java util. Stream package (java.util.stream.Stream), but it is not a functional interface. The interface contains many abstract methods. These methods are very important for stream operation. The most common methods are filter, map, foreach, count, limit, skip and concat.
  • Stream is an enhancement of collection operations. Stream is not an element of a collection, is not a data structure, and is not responsible for data storage. Flow is more like an iterator, which can traverse each element in a collection in one direction and is not recyclable.
  • Usage scenario
    • Sometimes, when you operate on elements in a collection, you need to use the results of other operations. In this process, the set of streaming programming can greatly simplify the number of codes. Read the data from the data source into a stream, and you can operate on the data in the stream (delete, filter, map...). The result of each operation is also a flow object. You can perform other operations on this flow.

Code example

Creation of flow

package cn.itxs.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;

public class StreamMain {
    public static void main(String[] args) {
        //Get streams from collections. All collections in Java are implementation classes under Collection. Methods to get streams are provided in the Collection interface
        List<Integer> list1 = new ArrayList<>();
        Stream<Integer> stream1 = list1.stream();  // Get stream
        Stream<Integer> stream2 = list1.parallelStream(); // Get stream (multithreading, high efficiency under large amount of data)

        //Array to get the stream. Array Java provides an Arrays tool class. We can convert the array into a collection to get the stream
        Integer[] arr = {1, 2, 3, 4, 5};
        List<Integer> list2 = Arrays.asList(arr);
        list2.forEach(str->System.out.print(str+";"));  //lambda is written to print out list array elements
        Stream<Integer> stream3 = list2.stream();
        Stream<Integer> stream4 = list2.parallelStream();
        Stream<Integer> stream5 = Arrays.stream(arr);  //Get the stream directly through the Arrays class
        Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5); //The static method of the Stream interface, of, can obtain the Stream corresponding to the array. The parameter of the of method is actually a variable parameter, so it also supports arrays
        Stream<Integer> stream7 = Stream.of(arr);
        
        Stream<Integer> stream8 = Stream.generate(() -> new Random().nextInt(10)); //The generate method returns an infinite continuous unordered flow, in which each element is generated by the supplier provided. The generate method is used to generate a constant flow and a random element flow
        Stream<Integer> stream9 = Stream.generate(() -> new Random().nextInt(10)).limit(3); //generate returns an infinite continuous stream. In order to limit the number of elements in the stream, we can use stream Limit method
        Stream<Integer> stream10 = Stream.iterate(0, i -> i + 2).limit(5); //Specify a constant seed to generate a stream from seed to constant f (the value returned by UnaryOperator). According to the starting value seed(0), a specified number of increment (i+2) is generated each time. limit(5) is used to truncate the length of the stream, that is, only the first 5 elements are obtained.
    }
}

Stream operation

package cn.itxs.stream;

import java.util.ArrayList;

public class StreamMain {
    public static void main(String[] args) {
        ArrayList<Employee> list = new ArrayList<>();
        list.add(new Employee("Zhang San", 32, 'male', 8000));
        list.add(new Employee("Li Ting", 36, 'female', 7000));
        list.add(new Employee("Zhang San", 32, 'male', 8000));
        list.add(new Employee("Guanhua", 32, 'male', 18000));
        list.add(new Employee("Li Wu", 22, 'male', 2800));
        list.add(new Employee("Zhang Haidong", 34, 'male', 12000));
        list.add(new Employee("Zhang Anfu", 32, 'male', 8000));
        list.add(new Employee("Jia Yi", 22, 'female', 21000));
        list.add(new Employee("Fang Shuzhen", 26, 'female', 14800));
        list.add(new Employee("Huang Shidi", 37, 'male', 26300));
        System.out.println("filter------");
        //filter filtering method
        list.stream()
                .filter(e -> e.getAge() > 32)
                .forEach(System.out::println);
        System.out.println("intercept------");
        //Only get 5 pieces of data in the result (intercept from the first one)
        list.stream()
                .limit(3)
                .forEach(System.out::println);
        System.out.println("skip------");
        //Two elements are skipped here
        list.stream()
                .skip(7)
                .forEach(System.out::println);
        System.out.println("duplicate removal------");
        // For the de duplication operation, one of the two three objects is reserved
        list.stream()
                .distinct()
                .forEach(System.out::println);
        System.out.println("sort------");
        // Sort collections by salary
        list.stream()
                .sorted((e1, e2)->Integer.compare(e1.getSalary(), e2.getSalary()))
                .forEach(System.out::println);
        System.out.println("transformation------");
        // The name of each user is returned through the map, and the user set is changed into the user name set
        list.stream()
                .map(Employee::getName)
                .forEach(System.out::println);
    }
}

Return operation result

The forEach used in the above section belongs to the code that returns the result. If only the filter method is called without calling the return result, the filter method will not be executed.

package cn.itxs.stream;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamMain1 {
    public static void main(String[] args) {
        ArrayList<Employee> list = new ArrayList<>();
        list.add(new Employee("Zhang San", 32, 'male', 8000));
        list.add(new Employee("Li Ting", 36, 'female', 7000));
        list.add(new Employee("Zhang San", 32, 'male', 8000));
        list.add(new Employee("Guanhua", 32, 'male', 18000));
        list.add(new Employee("Li Wu", 22, 'male', 2800));
        list.add(new Employee("Zhang Haidong", 34, 'male', 12000));
        list.add(new Employee("Zhang Anfu", 32, 'male', 8000));
        list.add(new Employee("Jia Yi", 22, 'female', 21000));
        list.add(new Employee("Fang Shuzhen", 26, 'female', 14800));
        list.add(new Employee("Fang Shuzhen", 26, 'female', 14800));
        list.add(new Employee("Huang Shidi", 37, 'male', 26300));

        //Take the minimum value
        Employee employee = list.stream()
                .filter(item -> item.getSalary() > 8000)
                .min(Comparator.comparingInt(Employee::getSalary))
                // The entity class can only be obtained after get ting
                .get();
        System.out.println(employee);

        //Take the maximum value
        employee = list.stream()
                .filter(item -> item.getSalary() > 8000)
                .max(Comparator.comparingInt(Employee::getSalary))
                .get();
        System.out.println(employee);

        //Count results
        long count = list.stream()
                .filter(item -> item.getSalary() > 10000)
                .count();
        System.out.println(count);

        //Return operation result
        List<Employee> collect = list.stream()
                .filter(item -> item.getSalary() > 10000)
                // Call the collect method directly, and then call toList to transform the result to the List set.
                .collect(Collectors.toList());
        System.out.println(collect);

        //Combine two flows
        Stream<Integer> stream1 = Stream.of(11,22);
        Stream<Integer> stream2 = Stream.of(33,44);
        Stream<Integer> stream3 = Stream.concat(stream1, stream2);
        stream3.forEach(s->System.out.println(s));

        //Comprehensive example
        List<String> result = list.stream()
                // Find all female employees
                .filter(item->item.getGender()=='female')
                // Remove duplicate data
                .distinct()
                // Sort by age
                .sorted(Comparator.comparingInt(Employee::getAge))
                // Extract name
                .map(Employee::getName)
                // Convert to List collection
                .collect(Collectors.toList());
        // Print view effect
        System.out.println(result);
    }
}

**My blog website** IT God www.itxiaoshen.com

Keywords: Java Back-end

Added by SalokinX on Fri, 21 Jan 2022 21:29:38 +0200