Java Basics - other new features of Java 8

Posted by BenS on Sat, 19 Feb 2022 13:10:46 +0100

1, Lambda expression

1. Examples of Lambda expressions

    @Test
    public void test1(){

        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("I Love Beijing Tiananmen ");
            }
        };

        r1.run();

        System.out.println("***********************");

        Runnable r2 = () -> System.out.println("I Love Beijing Tiananmen ");
        r2.run();

    }


    @Test
    public void test2(){

        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };

        int compare1 = com1.compare(12,21);
        System.out.println(compare1);

        System.out.println("***********************");

        //Writing method of Lambda expression
        Comparator<Integer> com2 = (o1,o2) -> Integer.compare(o1,o2);

        int compare2 = com2.compare(32,21);
        System.out.println(compare2);

        System.out.println("***********************");

        //Method reference
        Comparator<Integer> com3 = Integer::compare;

        int compare3 = com3.compare(32,21);
        System.out.println(compare3);

    }

2. Use of Lambda expressions

①eg: (o1,o2) -> Integer.compare(o1,o2);

② Format:

->: lambda operator or arrow operator

Left: lambda formal parameter list (in fact, it is the formal parameter list of abstract methods in the interface)

Right: lambda body (actually the method body of the overridden abstract method)

③ Use of Lambda expressions: (six kinds of introduction)

    //Syntax format 1: no parameter, no return value
    @Test
    public void test1(){
        Runnable r1 = new Runnable(){
            @Override
            public void run(){
                System.out.println("I Love Beijing Tiananmen ");
            }
        };
        r1.run();

        System.out.println("***********************");

        Runnable r2 = () -> {
            System.out.println("I Love Beijing Tiananmen ");
        };
        r2.run();

    }

    //Syntax format 2: Lambda needs a parameter, but there is no return value
    @Test
    public void test2(){
        Consumer<String> con = new Consumer<String>(){
            @Override
            public void accept(String s){
                System.out.println(s);
            }
        };
        con.accept("What is the difference between a lie and an oath?");

        System.out.println("*******************");

        Comsumer<String> con1 = (String s) -> {
            System.out.println(s);
        };
        con1.accept("One is that the listener is serious and the other is that the speaker is serious");

    }

    //Syntax format 3: data types can be omitted because they can be inferred by the compiler, which is called "type inference"
    @Test
    public void test3(){
        Comsumer<String> con1 = (String s) -> {
            System.out.println(s);
        };
        con1.accept("One is that the listener is serious and the other is that the speaker is serious");

        System.out.println("*******************");

        Comsumer<String> con2 = (s) -> {
            System.out.println(s);
        };
        con2.accept("One is that the listener is serious and the other is that the speaker is serious");

        ArrayList<String> list = new ArrayList<>();//Type inference
        int[] arr = {1,2,3};//Type inference

    }

    //Syntax format 4: if Lambda only needs one parameter, the parentheses of the parameter can be omitted
    @Test
    public void test5(){
        Consumer<String> con1 = (s) -> {
            System.out.println(s);
        };
        con1.accept("One was taken seriously by the listener and the other was taken seriously by the speaker");

        System.out.println("*******************");

        Comsumer<String> con2 = s -> {
            System.out.println(s);
        }
        con2.accept("It's true to be a person");
    }

    //Syntax format 5: Lambda requires two or more parameters, multiple execution statements, and can have return values
    @Test
    public void test6(){
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                System.out.println(o1);
                System.out.println(o2);
                return o1.compareTo(o2);
            }
        };

        System.out.println(com1.compare(12,21));

        System.out.println("*****************************");

        Comparator<Integer> com2 = (o1,o2) -> {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        };
        System.out.println(com2.compare(12,6));
    }

    //Syntax format 6: when there is only one statement in Lambda body, return and braces, if any, can be omitted
    @Test
    public void test7(){
        Comparator<Integer> com1 = (o1,o2) -> {
            return o1.compareTo(o2);
        };

        System.out.println(com1.compare(12,6));

        System.out.println("*****************************");

        Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
        System.out.println(com2.compare(12,21));
    }

    @Test
    public void test8(){
        Consumer<String> con1 = s -> {
            System.out.println(s);
        };
        con1.accept("One was taken seriously by the listener and the other was taken seriously by the speaker");

        System.out.println("*****************************");

        Consumer<String> con2 = s -> System.out.println(s);
        con2.accept("One was taken seriously by the listener and the other was taken seriously by the speaker");
    }

Summary:

Left: the parameter type of lambda parameter list can be omitted (type inference); If the lambda parameter list has only one parameter, its pair () can also be omitted

Right: lambda body should be wrapped with a pair of {}; If the lambda body has only one execution statement (possibly a return statement), omit this pair of {} and return keywords

④ The essence of Lambda expression: as an example of functional interface.

⑤ If only one abstract method is declared in an interface, the interface is called a functional interface. We can use the @ functional interface annotation on an interface to check whether it is a functional interface.

@FunctionalInterface
public interface MyInterface {/ / user defined functional interface

    void method1();

}

⑥ Therefore, those previously expressed by anonymous implementation classes can now be written in Lambda expressions.

2, Functional interface

1. Four core functional interfaces built in java

Consumer < T > void accept (T)

Supplier < T > T get()

Function < T > R apply (T)

Predicate < T > Boolean test (T)

    @Test
    public void test1(){
        happyTime(500,new Consumer<Double>(){
            @Override
            public void accept(Double aDouble){
                System.out.println("Bought a bottle of mineral water at the price of:" + aDouble);
            }
        });

         System.out.println("********************");

         happyTime(400,money -> System.out.println("Bought a bottle of mineral water at the price of:" + money));

    }

    public void happyTime(double money,Comsumer<Double> con){
        con.accept(money);
    }

    @Test
    public void test2(){
        List<String> list = Arrays.asList("Beijing","Nanjing","Tianjin","Tokyo","the Western Capital","Vladimir Putin");

        List<String> filterStrs = filterString(list,new Predicate<String>(){
            @Override
            public boolean test(String s){
                return s.contains("Beijing");
            }
        });
        System.out.println(filterStrs);

        System.out.println("***********************");
        
        List<String> filterStrs1 = filterString(list,s -> s.contains("Beijing"));
        System.out.println(filterStrs1);

    }

    //Filter the strings in the collection according to the given rules. This rule is determined by the predict method
    public List<String> filterString(List<String> list,Predicate<String> pre){
        ArrayList<String> filterList = new ArrayList<>();

        for(String s:list){
            if(pre.test(s)){
                filterList.add(s);
            }
        }
        return filterList;
    }

Summary:

>When to use lambda expressions?

When you need to instantiate a functional interface, you can use lambda expressions.

3, Method reference and constructor reference

1. Method reference

① Usage context: when the operation to be passed to the Lambda body already has an implemented method, you can use the method reference.

② Method reference is essentially a Lambda expression, which is used as an instance of a functional interface. Therefore, method references are also instances of functional interfaces.

③ Use format: class (or object):: method name

④ There are three situations as follows:

Case 1: object:: non static method

Case 2: Class: static method

Case 3: Class: non static method

⑤ Requirements for use of method references:

>The formal parameter list and return value type of the abstract method in the interface are required to be the same as those of the method referenced by the method! (for cases 1 and 2)

    //Case 1: object: instance method
    //Void accept in Consumer
    //Void println in PrintStream
    @Test
	public void test1() {
        Consumer<String> con1 = str -> System.out.println(str);
        con1.accept("Beijing");

        System.out.println("*******************");

        PrintStream ps = System.out;
        Consumer<String> con2 = ps :: println;
        con2.accept("beijing");
    }

    //T get() in Supplier
    //String getName() in Employee
    @Test
	public void test2() {
        Employee emp = new Employee(1001,"Tom",23,5600);

        Supplier<String> sup1 = () -> emp.getName();
        System.out.println(sup1.get());

        System.out.println("*******************");

        Supplier<String> sup2 = emp :: getName;
        System.out.println(sup2.get());
    }
    //Case 2: Class: static method
    //Int compare in Comparator (t T1, t T2)
    //Int compare in Integer (t T1, t T2)
    @Test
	public void test3() {
        Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
        System.out.println(com1.compare(12,21));

        System.out.println("*******************");

        Comparator<Integer> com2 = Integer :: compare; 
        System.out.println(com2.compare(12,3));
    }

    //R apply in Function (T)
    //Long round(Double d) in Math
    @Test
	public void test4() {
        Function<Double,Long> func = new Function<Double,Long>(){
            @Override
            public Long apply(Double d){
                return Math.round(d);
            }
        };

        System.out.println("*******************");

        Function<Double,Long> func1 = d -> Math.round(d);
        System.out.println(func1.apply(12.3));

        System.out.println("*******************");

        Function<Double,Long> func2 = Math :: round;
        System.out.println(func2.apply(12.6));

    }

> when the first parameter of the functional interface method is the caller who needs to reference the method, and the second parameter is the parameter (or no parameter) that needs to reference the method: classname:: methodname

    //Case 3: Class: instance method
    //Int compare in Comparator (t T1, t T2)
    //Int T1 in String compareTo(t2)
    @Test
	public void test5() {
        Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("abc","abd"));

        System.out.println("*******************");

        Comparator<String> com2 = String :: compareTo;
        System.out.println(com2.compare("abd","abm"));
    }

    //Boolean test in BiPredicate (t T1, t T2);
    //Boolean T1 in String equals(t2)
    @Test
	public void test6() {
        BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
        System.out.println(pre1.test("abc","abc"));

        System.out.println("*******************");

        BiPredicate<String,String> pre2 = String :: equals;
        System.out.println(pre2.test("abc","abd"));

    }

    //R apply in Function (T)
    //String getName() in Employee
    @Test
	public void test7() {
        Employee employee = new Employee(1001,"Jerry",23,6000);

        Function<Employee,String> func1 = e -> e.getName();
        System.out.println(func1.apply(employee));

        System.out.println("*******************");

        Function<Employee,String> func2 = Employee :: getName;
        System.out.println(func2.apply(employee));
    }

⑥ Constructor reference

Similar to method reference, the formal parameter list of abstract method of functional interface is consistent with that of constructor. The return value type of the abstract method is the type of the class to which the constructor belongs.

    //Constructor reference
    //T get() in Supplier
    //Empty parameter constructor of Employee: Employee()
    @Test
    public void test1(){
        Supplier<Employee> sup = new Supplier<Employee>(){
            @Override
            public Employee get(){
                return new Employee();
            }
        };

        System.out.println("*******************");

        Supplier<Employee> sup1 = () -> new Employee();
        System.out.println(sup1.get());

        System.out.println("*******************");

        Supplier<Employee> sup2 = Employee :: new;
        System.out.println(sup2.get());
    }

    //R apply in Function (T)
    @Test
    public void test2(){
        Function<Integer,Employee> func1 = id -> new Employee(id);
        Employee employee = func1.apply(1001);
        System.out.println(employee);

        System.out.println("*******************");
        Function<Integer,Employee> func2 = Employee :: new;
        Employee employee1 = func2.apply(1002);
        System.out.println(employee1);
    }

    //R apply (T, t, u, U) in BiFunction
    @Test
    public void test3(){
        BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
        System.out.println(func1.apply(1001,"Tom"));

        System.out.println("*******************");

        BiFunction<Integer,String,Employee> func2 = Employee :: new;
        System.out.println(func2.apply(1002,"Tom"));
    }

⑦ Array reference

The array is regarded as a special class, and the writing method is consistent with the constructor reference.

    //Array reference
    //R apply in Function (T)
    @Test
    public void test4(){
        Function<Integer,String[]> func1 = length -> new String[length];
        String[] arr1 = func1.apply(5);
        System.out.println(Arrays.toString(arr1));

        System.out.println("*******************");

        Function<Integer,String[]> func2 = String[] :: new;
        String[] arr2 = func2.apply(10);
        System.out.println(Arrays.toString(arr2));
    }

4, Powerful Stream API

1. The difference between Stream and collection

Stream focuses on the operation of data and dealing with CPU

Collections focus on the storage of data and deal with memory

Java 8 provides a set of APIs, which can be used to filter, sort, map, reduce and other operations on the data in memory. This is similar to sql operations on tables in the database.

2. Characteristics of Stream

① Stream itself does not store elements

② Stream does not change the source object. Instead, they return a new stream that holds the result

③ Stream operations are deferred. This means they wait until they need results

3. Stream execution process

① Instantiation of Stream

② A series of intermediate operations (filtering, mapping,...)

③ Terminate operation

4. Explain

① An intermediate operation chain that processes the data of the data source.

② Once the termination operation is performed, the intermediate operation chain is executed and the results are generated. After that, it will not be used again.

5. Instantiation of Stream:

① Method 1 of creating Stream: through collection

    @Test
    public void test1(){
        List<Employee> employees = EmployeeData.getEmployees();

        //Default stream < E > stream(): returns a sequential stream
        Stream<Employee> stream = employees.stream();

        //Default stream < E > parallelstream(): returns a parallel stream
        Stream<Employee> parallelStream = employees.parallelStream();
    }

② Method 2 of creating Stream: through array

    @Test
    public void test2(){
        int[] arr = new int[]{1,2,3,4,5,6};

        //Call static < T > stream < T > stream (t [] array) of Arrays class: return a stream
        IntStream stream = Arrays.stream(arr);

        Employee e1 = new Employee(1001,"Tom");
        Employee e2 = new Employee(1002,"Jerry");
        Employee[] arr1 = new Employee[]{e1,e2};
        Stream<Employee> stream1 = Arrays.stream(arr1);
    }

③ Three ways to create a Stream: through the of() of the Stream

    @Test
    public void test3(){
        Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
    }

④ Method 4 of creating Stream: create infinite Stream

    @Test
    public void test4(){
        //iteration
        //public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f)

        //Traverse the first 10 even numbers
        Stream.iterate(0,t -> t+2).limit(10).forEach(System.out :: println);

        //generate
        //public static<T> Stream<T> generate(Supplier<T> s)
        Stream.generate(Math :: random).limit(10).forEach(System.out :: println);
    }

6. Intermediate operation of Stream

① Screening and slicing

    @Test
    public void test1(){
        List<Employee> list = EmployeeData.getEmployees();

        //Filter (predict P): receive Lambda and exclude some elements from the stream
        Stream<Employee> stream = list.stream();

        //Exercise: query employee information with salary greater than 7000 in employee table
        stream.filter(e -> e.getSalary() > 7000).forEach(System.out :: println);

        System.out.println();

        //limit(n): truncate the stream so that its elements do not exceed the given number
        list.stream().limit(3).forEach(System.out :: println);

        System.out.println();

        //skip(n): skip elements and return a stream that throws away the first n elements. If there are less than n elements in the stream, an empty stream is returned. Complementary to limit(n)
        list.stream().skip(3).forEaach(System.out :: println);

        System.out.println();

        //distinct(): filter and remove duplicate elements through hashCode() and equals() of the elements generated by the stream
        list.stream().distinct().forEach(System.out :: println);

    }

② Mapping

public class StreamAPITest{    
    @Test
    public void test2(){
        //map(Function f): receive a function as a parameter to convert elements into other forms or extract information. The function will be applied to each element and mapped to a new element.
        List<String> list = Arrays.asList("aa","bb","cc","dd");
        list.stream().map(str -> str.toUpperCase()).forEach(System.out :: println);

        //Exercise 1: get the name of an employee whose name length is greater than 3.
        List<Employee> employees = EmployeeData.getEmployees();
        Stream<String> namesStream = employees.stream().map(Employee :: getName);
        namesStream.filter(name -> name.length() > 3).forEach(System.out :: println);

        System.out.println();

        //Exercise 2:
        Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest :: fromStringToStream);
        streamStream.forEach(s -> {
            s.forEach(System.out :: println);
        });

        System.out.println();

        //flatMap(Function f): take a function as a parameter, change each value in the stream into another stream, and then connect all streams into one stream
        Stream<Character> characterStream = list.stream().flatMap(StreamAPITest :: fromStringToStream);
        characterStream.forEach(System.out :: println);

    }

    //Converts a set composed of multiple characters in a string into an instance of the corresponding Stream
    public static Stream<Character> fromStringToStream(String str){ //aa
        ArrayList<Character> list = new ArrayList<>();
        for(Character c:str.toCharArray()){
            list.add(c);
        }
        return list.stream();
    }

    @Test
    public void test3(){
        ArrayList list1 = new ArrayList();
        list1.add(1);
        list1.add(2);
        list1.add(3);

        ArrayList list2 = new ArrayList();
        list2.add(4);
        list2.add(5);
        list2.add(6);

//        list1.add(list2);
        list1.addAll(list2);
        System.out.println(list1);

    }

}

③ Sort

    @Test
    public void test4(){
        //sorted(): natural sorting
        List<Integer> list = Arrays.asList(12,43,65,34,87,0,-98,7);
        list.stream().sorted().forEach(System.out :: println);

        //Throw exception. Reason: Employee does not implement Comparable interface
        //List<Employee> employees = EmployeeData.getEmployees();
        //employees.stream().sorted().forEach(System.out :: println);

        //sorted(Comparator com): customized sorting
        List<Employee> employees = EmployeeData.getEmployees();
        employees.stream().sorted((e1,e2) -> {
            
            int ageValue = Integer.compare(e1.getAge(),e2.getAge());
            if(ageValue != 0){
                return ageValue;
            }else{
                return -Double.compare(e1.getSalary(),e2.getSalary());
            }

        }).forEach(System.out :: println);
    }

7. Termination of Stream

① Match and find

    @Test
    public void test1(){
        List<Employee> employees = EmployeeData.getEmployees();

        //allMatch(Predicate p): check whether all elements are matched
        //Exercise: are all employees older than 18
        boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
        System.out.println(allMatch);

        //Anymatch (predict P): checks whether at least one element is matched
        //Exercise: is there an employee whose salary is greater than 10000
        boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
        System.out.println(anyMatch);

        //Nonematch (predict P): check whether there are no matching elements
        //Exercise: is there an employee surnamed "Lei"
        boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("thunder"));
        System.out.println(noneMatch);

        //findFirst(): returns the first element
        Optional<Employee> employee = employees.stream().findFirst();
        System.out.println(employee);

        //findAny(): returns any element in the current stream
        Optional<Employee> employee1 = employees.parallelStream().findAny();
        System.out.println(employee1);
    }
    @Test
    public void test2(){
        List<Employee> employees = EmployeeData.getEmployees();

        //count: returns the total number of elements in the stream
        long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
        System.out.println(count);

        //max(Comparator c): returns the maximum value in the stream
        //Exercise: return to the highest salary:
        Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
        Optional<Double> maxSalary = salaryStream.max(Double :: compare);
        System.out.println(maxSalary);

        //min(Comparator c): returns the minimum value in the stream
        //Exercise: return to the minimum wage employee
        Optional<Employee> employee = employees.stream().min((e1,e2) -> Double.compare(e1.getSalary(),e2.getSalary()));
        System.out.println(employee);

        System.out.println();

        //forEach(Consumer c): internal iteration
        employees.stream().forEach(System.out :: println);

        //Traversal using collections
        employees.forEach(System.out :: println);

    }

② Reduction

    @Test
    public void test3(){
        //Reduce (T identity, binary operator): you can combine the elements in the stream repeatedly to get a value. Return T
        //Exercise 1: calculate the sum of natural numbers 1-10
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Integer sum = list.stream().reduce(0,Integer :: sum);
        System.out.println(sum);

        //Reduce (binary operator): you can combine the elements in the flow repeatedly to get a value. Return to optional < T >
        //Exercise 2: calculate the total salary of all employees in the company
        List<Employee> employees = EmployeeData.getEmployees();
        Stream<Double> salaryStream = employees.stream().map(Employee :: getSalary);
        //Optional<Double> sumMoney = salaryStream.reduce(Double :: sum);
        Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
        System.out.println(sumMoney.get());
    }

③ Collect

    @Test
    public void test4(){
        //collect(Collector c): convert a Stream to another form. Receive the implementation of a collector interface, which is used to summarize the elements in the Stream
        //Exercise 1: find employees whose salary is greater than 6000, and the result is returned as a List or Set

        List<Employee> employees = EmployeeData.getEmployees();
        //List < T > tolist(): collect the elements in the stream into a list
        List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
        employeeList.forEach(System.out :: println);

        System.out.println();

        Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());

        employeeSet.forEach(System.out :: println);

    }

Collector s need to use Collectors to provide instances:

5, Optional class

1. The optional < T > class (java.util.Optional) is a container class that can store the value of type T, representing the existence of this value. Or just save null, indicating that the value does not exist.

2. Optional class: created to avoid null pointer exceptions in programs.

3. Common methods:

Optional. Of (t): create an optional instance. t must be non empty

Optional.empty(): create an empty optional instance

Optional. Ofnullable (T): t can be null

    @Test
    public void test1(){
        //empty(): value = null inside the created Optional object
        Optional<Object> op1 = Optional.empty();
        if(!op1.isPresent()){ //Does the Optional encapsulated data contain data
            System.out.println("Data is empty");
        }
        System.out.println(op1);
        System.out.println(op1.isPresent());
        //If the data value encapsulated by option is empty, get() will report an error. Otherwise, value is returned when value is not empty.
        System.out.println(op1.get());

    }
    @Test
    public void test2(){
        String str = "hello";
        //str = null;
        //Of (T): encapsulates data t to generate Optional objects. It is required that t is not empty, otherwise an error will be reported.
        Optional<String> op1 = Optional.of(str);
        //get(): usually used with the of() method. Used to obtain the internally encapsulated data value
        String str1 = op1.get();
        System.out.println(str1);
    }
    @Test
    public void test3(){
        String str = "beijing";
        str = null;
        //ofNullable(T t): the value that encapsulates the data t assigned to the internal part of Optional. Don't ask t to be non empty
        Optional<String> op1 = Optional.ofNullable(str);
        //orElse(T t1): if the value inside Optional is not empty, this value will be returned; If value is empty, t1 is returned.
        String str2 = op1.orElse("shanghai");
        System.out.println(str2);
    }

Topics: Java Back-end