Chapter 17 Java 8 new features
primary coverage
- Lambda expression
- StreamAPI
- Optional
Learning objectives
- Be able to understand the advantages of functional programming over object-oriented programming
- Be able to master the standard format of Lambda expression
- Ability to use Lambda standard format
- Be able to master the ellipsis format and rules of Lambda expression
- The user-defined interface (with only one abstract method) can be used through Lambda
- Ability to use @ FunctionalInterface annotations
- Ability to use Supplier functional interfaces
- Ability to use the Consumer functional interface
- Ability to use functional interfaces
- Ability to use predict functional interfaces
- Ability to use method references and constructor references
- Be able to understand the advantages of flow over collection
- Be able to understand the delayed execution characteristics of flow
- The ability to get streams from collections, maps, or arrays
- Be able to master common flow operations
- You can wrap an object using the Optional class and get the object wrapped in it
Chapter 17 Java 8 new features
There are many new features in Java 8. Previously, we learned the static methods and default methods of the interface when learning the interface. When learning common classes, we learned the new version of the date time API. Today we'll learn about two of the most revolutionary new features of Java 8: Lambda expressions and stream APIs. Then lead you to solve the most troublesome null pointer exception with Optioanl class.
17.1 Lambda expression
17.1.1 functional programming idea
In mathematics, a function is a set of calculation schemes with input and output, that is, "what to do with". Functions in programming also have a similar concept. When you call me, give me an argument to assign a formal parameter, and then return a result to you by running the method body. For the caller to do, focus on what kind of function this method has. Relatively speaking, object-oriented overemphasizes that "things must be done in the form of objects", while functional thinking tries to ignore the complex object-oriented syntax - emphasizing what to do rather than what form to do.
-
Object oriented thinking:
- Do a thing, find an object that can solve it, call the method of the object, and complete the thing
-
Functional programming idea:
- As long as the results can be obtained, it doesn't matter who does it or how to do it. It pays attention to the results rather than the process
After the introduction of Lambda expressions in Java 8, Java also began to support functional programming.
Lambda expressions were not first used in Java. Many languages support lambda expressions, such as C + +, c#, python, Scala, etc. If you have a language foundation of Python or Javascript, it is very helpful to understand lambda expression. It can be said that lambda expression is actually a syntax sugar for implementing SAM interface, making java a language that supports functional programming. Lambda's good writing can greatly reduce code redundancy, and its readability is better than lengthy anonymous inner classes.
Note: "syntax sugar" refers to the code syntax that is more convenient to use, but the principle remains the same. For example, the for each syntax used when traversing a collection is actually
The underlying implementation principle is still iterator, which is "syntax sugar". From the application level, Lambda in Java can be regarded as anonymous internal
Class, but they are different in principle.
Redundant anonymous inner classes
When a thread needs to be started to complete a task, it usually defines the task content through the java.lang.Runnable interface and uses the java.lang.Thread class to start the thread. The code is as follows:
public class Demo01Runnable { public static void main(String[] args) { // Anonymous Inner Class Runnable task = new Runnable() { @Override public void run() { // Override abstract method System.out.println("Multithreaded task execution!"); } }; new Thread(task).start(); // Start thread } }
Based on the idea of "everything is an object", this approach is understandable: first, create an anonymous internal class object of Runnable interface to specify the task content, and then hand it over to a thread to start.
Code analysis:
For the anonymous inner class usage of Runnable, several points can be analyzed:
- The Thread class requires the Runnable interface as a parameter, and the abstract run method is used to specify the core of the Thread task content;
- In order to specify the method body of run, the implementation class of Runnable interface must be required;
- In order to save the trouble of defining a RunnableImpl implementation class, anonymous inner classes have to be used;
- The abstract run method must be overridden, so the method name, method parameters and method return value must be written again without error;
- In fact, it seems that only the method body is the key.
Transformation of programming ideas
What to do, not who to do, how to do
Do we really want to create an anonymous inner class object? no We just have to create an object to do this. What we really want to do is pass the code in the run method body to the Thread class.
Pass a piece of code -- that's our real purpose. Creating objects is only a means that has to be taken due to the limitation of object-oriented syntax. So, is there a simpler way? If we return our focus from "how to do" to "what to do", we will find that as long as we can better achieve the goal, the process and form are not important.
Life examples:
When we need to travel from Beijing to Shanghai, we can choose high-speed rail, car, cycling or hiking. Our real goal is to get to Shanghai, and the form of how to get to Shanghai is not important, so we have been exploring whether there is a better way than high-speed rail - by plane.
Now this kind of aircraft (or even spacecraft) has been born: Java 8 (JDK 1.8) released by Oracle in March 2014 added the heavyweight new feature of Lambda expression, opening the door to a new world for us.
Experience Lambda's better writing
With the new syntax of Java 8, the anonymous internal class writing method of the above Runnable interface can be equivalent through a simpler Lambda expression:
public class Demo02LambdaRunnable { public static void main(String[] args) { new Thread(() -> System.out.println("Multithreaded task execution!")).start(); // Start thread } }
The execution effect of this code is exactly the same as that just now. It can be passed at the compilation level of 1.8 or higher. From the semantics of the code, we can see that we start a thread, and the content of the thread task is specified in a more concise form.
There is no longer the constraint of "having to create interface objects", and there is no longer the burden of "abstract method override", which is so simple!
17.1.2 functional interface
lambda expression is actually the syntax sugar to implement SAM interface. The so-called SAM interface is Single Abstract Method, that is, there is only one abstract method to be implemented in the interface. Of course, the interface can contain other non abstract methods.
In fact, as long as the interfaces that meet the "SAM" characteristics can be called functional interfaces, Lambda expressions can be used. However, if you want to be more specific, it is best to add @ FunctionalInterface when declaring the interface. Once the annotation is used to define the interface, the compiler will force to check whether the interface does have and only has one abstract method, otherwise an error will be reported.
Among the SAM interfaces learned before, the functional interfaces marked with @ FunctionalInterface are Runnable, Comparator and FileFilter.
Java8 adds many functional interfaces to java.util.function: it is mainly divided into four categories: consumption type, supply type, judgment type and function type. It can basically meet our development needs. Of course, you can also define your own functional interface.
1. Custom functional interface
Just ensure that there is and only one abstract method in the interface:
Modifier interface Interface name { public abstract Return value type method name(Optional parameter information); // Other non abstract method content }
The public abstract of the abstract method in the interface can be omitted
For example, declare a Calculator interface with an abstract method calc, which can calculate two int numbers and return results:
public interface Calculator { int calc(int a, int b); }
In the test class, declare the following method:
public static void invokeCalc(int a, int b, Calculator calculator) { int result = calculator.calc(a, b); System.out.println("The result is:" + result); }
Test as follows:
public static void main(String[] args) { invokeCalc(1, 2, (int a,int b)-> {return a+b;}); invokeCalc(1, 2, (int a,int b)-> {return a-b;}); invokeCalc(1, 2, (int a,int b)-> {return a*b;}); invokeCalc(1, 2, (int a,int b)-> {return a/b;}); invokeCalc(1, 2, (int a,int b)-> {return a%b;}); invokeCalc(1, 2, (int a,int b)-> {return a>b?a:b;}); }
2. Consumer interface
Abstract method features of consumer interface: it has formal parameters, but the return value type is void
Interface name | Abstract method | describe |
---|---|---|
Consumer | void accept(T t) | Receive an object to complete the function |
BiConsumer<T,U> | void accept(T t, U u) | Receive two objects to complete the function |
DoubleConsumer | void accept(double value) | Receive a double value |
IntConsumer | void accept(int value) | Receive an int value |
LongConsumer | void accept(long value) | Receive a long value |
ObjDoubleConsumer | void accept(T t, double value) | Receive an object and a double value |
ObjIntConsumer | void accept(T t, int value) | Receive an object and an int value |
ObjLongConsumer | void accept(T t, long value) | Receive an object and a long value |
3. Supply type interface
Abstract method characteristics of this kind of interface: no parameters, but no return value
Interface name | Abstract method | describe |
---|---|---|
Supplier | T get() | Returns an object |
BooleanSupplier | boolean getAsBoolean() | Returns a boolean value |
DoubleSupplier | double getAsDouble() | Returns a double value |
IntSupplier | int getAsInt() | Returns an int value |
LongSupplier | long getAsLong() | Returns a long value |
4. Judgment interface
The abstract method feature of the interface here is that it has parameters, but the return value type is boolean result.
Interface name | Abstract method | describe |
---|---|---|
Predicate | boolean test(T t) | Receive an object |
BiPredicate<T,U> | boolean test(T t, U u) | Receive two objects |
DoublePredicate | boolean test(double value) | Receive a double value |
IntPredicate | boolean test(int value) | Receive an int value |
LongPredicate | boolean test(long value) | Receive a long value |
5. Functional interface
Abstract method features of this kind of interface: both parameters and return values
Interface name | Abstract method | describe |
---|---|---|
Function<T,R> | R apply(T t) | Receive a T-type object and return an R-type object result |
UnaryOperator | T apply(T t) | Receive a T-type object and return a T-type object result |
DoubleFunction | R apply(double value) | Receive a double value and return an R-type object |
IntFunction | R apply(int value) | Receive an int value and return an R-type object |
LongFunction | R apply(long value) | Receive a long value and return an R-type object |
ToDoubleFunction | double applyAsDouble(T value) | Receive a T-type object and return a double |
ToIntFunction | int applyAsInt(T value) | Receive a T-type object and return an int |
ToLongFunction | long applyAsLong(T value) | Receive a T-type object and return a long |
DoubleToIntFunction | int applyAsInt(double value) | Receive a double value and return an int result |
DoubleToLongFunction | long applyAsLong(double value) | Receive a double value and return a long result |
IntToDoubleFunction | double applyAsDouble(int value) | Receive an int value and return a double result |
IntToLongFunction | long applyAsLong(int value) | Receive an int value and return a long result |
LongToDoubleFunction | double applyAsDouble(long value) | Receive a long value and return a double result |
LongToIntFunction | int applyAsInt(long value) | Receive a long value and return an int result |
DoubleUnaryOperator | double applyAsDouble(double operand) | Receive a double value and return a double |
IntUnaryOperator | int applyAsInt(int operand) | Receive an int value and return an int result |
LongUnaryOperator | long applyAsLong(long operand) | Receive a long value and return a long result |
BiFunction<T,U,R> | R apply(T t, U u) | Receive a T-type and a U-type object and return an R-type object result |
BinaryOperator | T apply(T t, T u) | Receive two T-type objects and return a T-type object result |
ToDoubleBiFunction<T,U> | double applyAsDouble(T t, U u) | Receive a T-type and a U-type object and return a double |
ToIntBiFunction<T,U> | int applyAsInt(T t, U u) | Receives a T-type and a U-type object and returns an int |
ToLongBiFunction<T,U> | long applyAsLong(T t, U u) | Receive a T-type and a U-type object and return a long |
DoubleBinaryOperator | double applyAsDouble(double left, double right) | Receive two double values and return a double result |
IntBinaryOperator | int applyAsInt(int left, int right) | Receive two int values and return an int result |
LongBinaryOperator | long applyAsLong(long left, long right) | Receive two long values and return a long result |
17.1.3 Lambda expression syntax
Lambda expression is used to assign values to variables or formal parameters of [functional interface].
In fact, Lambda expressions are essentially "abstract methods" used to implement [functional interfaces]
Lambda expression syntax format
(parameter list ) -> {Lambda body}
explain:
- (formal parameter list) it is the abstract method (formal parameter list) of the functional interface you want to assign value. Copy it
- {Lambda} is the method body that implements this abstract method
- ->It is called Lambda operator (there can be no space between minus sign and greater than sign, and it must be half angle input in English)
Optimization: Lambda expressions can be streamlined
- When there is only one sentence in {Lambda body}, {} and {;} can be omitted
- When there is only one sentence in {Lambda body} and the sentence is still a return statement, return can also be omitted, but if {;} is not omitted, return cannot be omitted
- The type of (formal parameter list) can be omitted
- When there is only one parameter in (parameter list), the data type and () can be omitted, but the parameter name cannot be omitted
- () cannot be omitted when (formal parameter list) is an empty parameter
Example code:
public class TestLambdaGrammer { @Test public void test1(){ //Assign a value to the formal parameter or variable of the Runnable interface with a Lambda expression /* * To write a lambda expression, you need to determine two things * (1)What does the abstract method of this interface look like * public void run() * (2)What does the implementation of this abstract method do * For example: I want to print "hello lambda"“ */ Runnable r = () -> {System.out.println("hello lambda");}; } @Test public void test2(){ //lambda body omits {;} Runnable r = () -> System.out.println("hello lambda"); } @Test public void test3(){ String[] arr = {"hello","Hello","java","chai"}; //Sort the arr array, but you want to be case insensitive /* * public static <T> void sort(T[] a,Comparator<? super T> c) * Here, you use a Lambda expression to assign a value to a formal parameter of type Comparator * * Two things: * (1)Abstract method of this interface: int compare(T o1, T o2) * (2)What does this abstract method do? * For example, for elements of String type, case insensitive comparison is required */ // Arrays.sort(arr, (String o1, String o2) -> {return o1.compareToIgnoreCase(o2);}); //{return;} is omitted // Arrays.sort(arr, (String o1, String o2) -> o1.compareToIgnoreCase(o2)); //Two strings are omitted Arrays.sort(arr, (o1, o2) -> o1.compareToIgnoreCase(o2)); for (String string : arr) { System.out.println(string); } } @Test public void test4(){ ArrayList<String> list = new ArrayList<>(); list.add("hello"); list.add("java"); list.add("world"); /* * JDK1.8 For collections of Collection series, to be exact, a default method is added to the Iterable interface * default void forEach(Consumer<? super T> action) * This method is used to traverse the set, etc. instead of the original foreach loop. * * The formal parameter of this method is the Consumer interface type, which is the representative of the Consumer interface in the functional interface * I now call this method to assign a value to the type parameter of the Consumer interface with a Lambda expression * * Two things: * (1)Its abstract method: void accept (T) * (2)What does the implementation of abstract methods accomplish * For example, here you want to print this t */ // list.forEach((String t) -> {System.out.println(t);}); //Omit {;} // list.forEach((String t) -> System.out.println(t)); //Omit String // list.forEach((t) -> System.out.println(t)); //The formal parameter () can be omitted list.forEach(t -> System.out.println(t)); } }
17.1.4 Lambda expression exercise
Exercise 1: no parameter, no return value form
If there is a user-defined functional interface, Call is as follows:
public interface Call { void shout(); }
Declare the following method in the test class:
public static void callSomething(Call call){ call.shout(); }
The callSomething method is called in the main method of the test class, and the Lambda expression is used to assign the parameter call, so you can shout whatever you want to say.
public class TestLambda { public static void main(String[] args) { callSomething(()->System.out.println("Go home for dinner")); callSomething(()->System.out.println("I love you!")); callSomething(()->System.out.println("rats , screw you")); callSomething(()->System.out.println("come back")); } public static void callSomething(Call call){ call.shout(); } } interface Call { void shout(); }
Exercise 2: consumer interface
Code example: Consumer interface
In JDK1.8, a default method is added to the iteratable interface, the parent interface of the Collection interface:
Public default void foreach (consumer <? Super T > action) traverses each element of the Collection set and performs the "xxx consumption type" operation.
In JDK1.8, a default method is added to the Map collection interface:
Public default void foreach (biconsumer <? Super K,? Super V > action) traverses each pair of mapping relationships in the Map set and executes the "xxx consumption type" operation.
Case:
(1) Create a Collection of Collection series, add the programming language you know, and call the forEach method to traverse the view
(2) Create a set of Map series, add some (key,value) key value pairs, for example, add the programming language ranking and language name, and call the forEach method to traverse and view
Example code:
@Test public void test1(){ List<String> list = Arrays.asList("java","c","python","c++","VB","C#"); list.forEach(s -> System.out.println(s)); } @Test public void test2(){ HashMap<Integer,String> map = new HashMap<>(); map.put(1, "java"); map.put(2, "c"); map.put(3, "python"); map.put(4, "c++"); map.put(5, "VB"); map.put(6, "C#"); map.forEach((k,v) -> System.out.println(k+"->"+v)); }
Exercise 3: supply interface
Code example: Supplier interface
The stream API is added in JDK1.8. java.util.stream.Stream is a data stream. This type has a static method:
Public static < T > Stream < T > generate (supplier < T > s) can create an object of Stream. It also contains a forEach method that can traverse the elements in the flow: public void forEach (consumer <? Super T > action).
Case:
Now, please call the generate method of Stream to generate a Stream object, and call the Math.random() method to generate data and assign values to the formal parameters of the Supplier functional interface. Finally, the forEach method is called to traverse the data in the Stream to see the result.
@Test public void test2(){ Stream.generate(() -> Math.random()).forEach(num -> System.out.println(num)); }
Exercise 4: functional interfaces
Code example: function < T, R > interface
In JDK1.8, many methods are added to the Map interface, such as:
Public default void replaceall (bifunction <? Super K,? Super V,? Extends V > function) replaces the value in the map according to the operation specified by function.
Public default void foreach (biconsumer <? Super K,? Super V > action) traverses each pair of mapping relationships in the Map set and executes the "xxx consumption type" operation.
Case:
(1) Declare an Employee type, including number, name and salary.
(2) Add n employee objects to a HashMap < integer, employee > collection, where the employee number is key and the employee object is value.
(3) Call forEach of Map to traverse the collection
(4) Call the replaceAll method of Map and set the salary to 10000 if the salary is less than 10000 yuan.
(5) Call forEach of Map again to traverse the collection and view the results
Employee class:
class Employee{ private int id; private String name; private double salary; public Employee(int id, String name, double salary) { super(); this.id = id; this.name = name; this.salary = salary; } public Employee() { super(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } @Override public String toString() { return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]"; } }
Test class:
import java.util.HashMap; public class TestLambda { public static void main(String[] args) { HashMap<Integer,Employee> map = new HashMap<>(); Employee e1 = new Employee(1, "Zhang San", 8000); Employee e2 = new Employee(2, "Li Si", 9000); Employee e3 = new Employee(3, "Wang Wu", 10000); Employee e4 = new Employee(4, "Zhao Liu", 11000); Employee e5 = new Employee(5, "Qian Qi", 12000); map.put(e1.getId(), e1); map.put(e2.getId(), e2); map.put(e3.getId(), e3); map.put(e4.getId(), e4); map.put(e5.getId(), e5); map.forEach((k,v) -> System.out.println(k+"="+v)); System.out.println(); map.replaceAll((k,v)->{ if(v.getSalary()<10000){ v.setSalary(10000); } return v; }); map.forEach((k,v) -> System.out.println(k+"="+v)); } }
Exercise 5: judgmental interface
Code example: predict interface
In JDK1.8, the Collecton interface adds the following methods, one of which is as follows:
Public default Boolean removeif (predict <? Super E > filter) is used to delete the judgment that meets the conditions specified by the filter in the collection.
Public default void foreach (consumer <? Super T > action) traverses each element of the Collection set and performs the "xxx consumption type" operation.
Case:
(1) Add some strings to a Collection
(2) Call forEach to traverse the collection
(3) Call the removeIf method to delete the string whose length is less than 5
(4) Call forEach again to traverse the collection
import java.util.ArrayList; public class TestLambda { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("hello"); list.add("java"); list.add("atguigu"); list.add("ok"); list.add("yes"); list.forEach(str->System.out.println(str)); System.out.println(); list.removeIf(str->str.length()<5); list.forEach(str->System.out.println(str)); } }
Exercise 6: judgmental interface
Case:
(1) Declare an Employee type, including number, name, gender, age and salary.
(2) Declare an employeeservice employee management class, which contains an all attribute of the ArrayList collection. In the constructor of employeeservice, create some employee objects and initialize the all collection.
(3) In the employeeservice employee management class, declare a method: ArrayList get (predict p), that is, add the employees who meet the conditions specified by p to a new ArrayList collection and return.
(4) Create an object of employeeservice employee management class in the test class, and call the get method to obtain:
- All employee objects
- All employees over 35
- All female employees with salary higher than 15000
- All employees with even numbers
- An employee whose name is "Zhang San"
- Male employees over the age of 25 with salary less than 10000
Example code:
Employee class:
public class Employee{ private int id; private String name; private char gender; private int age; private double salary; public Employee(int id, String name, char gender, int age, double salary) { super(); this.id = id; this.name = name; this.gender = gender; this.age = age; this.salary = salary; } public Employee() { super(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } @Override public String toString() { return "Employee [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + ", salary=" + salary + "]"; } }
Employee management:
class EmployeeService{ private ArrayList<Employee> all; public EmployeeService(){ all = new ArrayList<Employee>(); all.add(new Employee(1, "Zhang San", 'male', 33, 8000)); all.add(new Employee(2, "Cuihua", 'female', 23, 18000)); all.add(new Employee(3, "you 're incompetent", 'male', 46, 8000)); all.add(new Employee(4, "Li Si", 'female', 23, 9000)); all.add(new Employee(5, "Lao Wang", 'male', 23, 15000)); all.add(new Employee(6, "Big mouth", 'male', 23, 11000)); } public ArrayList<Employee> get(Predicate<Employee> p){ ArrayList<Employee> result = new ArrayList<Employee>(); for (Employee emp : all) { if(p.test(emp)){ result.add(emp); } } return result; } }
Test class:
public class TestLambda { public static void main(String[] args) { EmployeeService es = new EmployeeService(); es.get(e -> true).forEach(e->System.out.println(e)); System.out.println(); es.get(e -> e.getAge()>35).forEach(e->System.out.println(e)); System.out.println(); es.get(e -> e.getSalary()>15000 && e.getGender()=='female').forEach(e->System.out.println(e)); System.out.println(); es.get(e -> e.getId()%2==0).forEach(e->System.out.println(e)); System.out.println(); es.get(e -> "Zhang San".equals(e.getName())).forEach(e->System.out.println(e)); System.out.println(); es.get(e -> e.getAge()>25 && e.getSalary()<10000 && e.getGender()=='male').forEach(e->System.out.println(e)); } }
17.1.3 method reference and constructor reference
Lambda expressions are syntax that can simplify the assignment of variables and formal parameters of functional interfaces. Method references and constructor references are used to simplify lambda expressions. When the lambda expression satisfies some special conditions, it can also be simplified:
(1) The Lambda body has only one sentence and is completed by calling an existing method of an object / class
For example, the System.out object calls the println() method to complete the Lambda body
Math class, call the random() static method to complete the Lambda body
(2) And the formal parameter of Lambda expression is exactly the argument given to the method
For example: T - > system. Out. Println (T)
() - > math. Random() are all parameterless
Method reference
Syntax format of method reference:
(1) Instance object name:: instance method
(2) Class name:: static method
(3) Class name:: instance method
explain:
- :: it is called a method reference operator (there must be no space between the two, and it must be entered in English)
- The formal parameter list of a Lambda expression is used in the Lambda body, either as an object calling a method or as an argument to a method.
- There is no additional data in the whole Lambda body.
@Test public void test4(){ // Runnable r = () -> System.out.println("hello lambda"); Runnable r = System.out::println;//Print blank lines //The method reference cannot be simplified because the "hello lambda" cannot be omitted } @Test public void test3(){ String[] arr = {"Hello","java","chai"}; // Arrays.sort(arr, (s1,s2) -> s1.compareToIgnoreCase(s2)); //Simplify with method reference /* * Lambda The first formal parameter of the expression (for example, s1) is exactly the object calling the method, and the remaining formal parameters (for example, s2) are exactly the arguments given to the method */ Arrays.sort(arr, String::compareToIgnoreCase); } @Test public void test2(){ // Stream<Double> stream = Stream.generate(() -> Math.random()); //Simplify with method reference Stream<Double> stream = Stream.generate(Math::random); } @Test public void test1(){ List<Integer> list = Arrays.asList(1,3,4,8,9); //list.forEach(t -> System.out.println(t)); //Simplify again with method list.forEach(System.out::println); }
Constructor reference
(1) When the Lambda expression creates an object and satisfies the formal parameters of the Lambda expression, it is exactly the argument list of the constructor that creates the object.
(2) When the Lambda expression creates an array object and satisfies the Lambda expression parameters, the length of the array object is exactly given
Syntax format of constructor reference:
- Class name:: new
- Array type name:: new
Example code:
public class TestMethodReference { @Test public void teset04() { Stream<Integer> stream = Stream.of(1,2,3); Stream<int[]> map = stream.map(int[]::new); } //This method is to imitate the code in HashMap to correct the length of the array you specify to the nth power of 2 //createArray() creates an array with a length of 2 to the nth power public <R> R[] createArray(Function<Integer,R[]> fun,int length){ int n = length - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; length = n < 0 ? 1 : n + 1; return fun.apply(length); } @Test public void test3(){ /* * Function Is a functional interface that can be assigned with Lambda expressions * Function<T,R>Abstract method r apply (T) * * createArray Function < Integer, R [] > fun is used in this method. Description T type has been specified as Integer * explain */ // Function<Integer,String[]> f = (Integer len) -> new String[len]; //Because the Lambda body is completed by creating an array object, and the formal parameters of the Lambda expression are exactly the length used to create the array //Omitted by constructor reference Function<Integer,String[]> f = String[]::new; String[] array = createArray(f, 10); System.out.println(array.length);//16 } @Test public void teset02() { Stream<String> stream = Stream.of("1.0","2.3","4.4"); // Stream<BigDecimal> stream2 = stream.map(num -> new BigDecimal(num)); Stream<BigDecimal> stream2 = stream.map(BigDecimal::new); } @Test public void test1(){ // Supplier<String> s = () -> new String();// Provide an empty string object through the supply interface //Constructor reference Supplier<String> s = String::new;//Provide an empty string object through the supply interface } }
17.2 StreamAPI
There are two of the most important changes in Java 8. The first is a Lambda expression; The other is the Stream API.
The Stream API (Java. Util. Stream) introduces a true functional programming style into Java. This is the best supplement to the Java class library so far, because the Stream API can greatly improve the productivity of Java programmers and enable programmers to write efficient, clean and concise code.
Stream is a key abstract concept for processing collections in Java 8. It can specify the operations you want to perform on collections, and can perform very complex operations such as finding, filtering and mapping data. Using the Stream API to operate on the collection data is similar to a database query executed using SQL. You can also use the Stream API to perform operations in parallel. In short, the Stream API provides an efficient and easy-to-use way to process data.
Stream is a data channel used to manipulate the sequence of elements generated by data sources (sets, arrays, etc.). "Set is about data and is responsible for storing data. Stream is about calculation and is responsible for processing data!"
be careful:
① Stream itself does not store elements.
② Stream does not change the source object. Each process returns a new stream that holds the result.
③ Stream operations are deferred. This means that they wait until the results are needed.
There are three steps for Stream operation:
1 - create a Stream: obtain a Stream from a data source (such as a collection or array)
2 - intermediate operation: the intermediate operation is an operation chain that processes the data of the data source n times, but it will not be really executed before the operation is terminated.
3 - termination operation: once the termination operation is executed, the intermediate operation chain will be executed, the final result will be generated and the Stream will be ended.
17.2.1 creating a Stream
1. Method 1 of creating Stream: through collection
The Collection interface in Java 8 is extended to provide two methods to obtain streams:
-
Public default stream(): returns a sequential stream
-
public default Stream parallelStream(): returns a parallel stream
2. Method 2 of creating Stream: through array
The static method stream() of Arrays in Java 8 can obtain the array stream:
- Public static stream (t [] array): returns a stream
Overloaded form, which can handle arrays of corresponding basic types:
- Public static intstream (int [] array): returns an integer data stream
- public static LongStream stream(long[] array): returns a long integer data stream
- Public static double stream (double [] array): returns a floating-point data stream
3. Method 3 of creating a Stream: through of()
You can call the static method of() of the Stream class to create a Stream by displaying the value. It can receive any number of parameters.
- public static Stream of(T... values): returns a sequential stream
4. Create Stream mode 4: create infinite Stream
You can create an infinite stream using the static methods Stream.iterate() and Stream.generate().
- public static Stream iterate(final T seed, final UnaryOperator f): returns an infinite stream
- Public static stream generate (suppliers): returns an infinite stream
package com.atguigu.test06; import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.Test; public class Test07StreamCreate { @Test public void test06(){ /* * Stream<T> iterate(T seed, UnaryOperator<T> f) * UnaryOperator Interface, SAM interface, abstract method: * * UnaryOperator<T> extends Function<T,T> * T apply(T t) */ Stream<Integer> stream = Stream.iterate(1, num -> num+=2); // stream = stream.limit(10); stream.forEach(System.out::println); } @Test public void test05(){ Stream<Double> stream = Stream.generate(Math::random); stream.forEach(System.out::println); } @Test public void test04(){ Stream<Integer> stream = Stream.of(1,2,3,4,5); stream.forEach(System.out::println); } @Test public void test03(){ String[] arr = {"hello","world"}; Stream<String> stream = Arrays.stream(arr); } @Test public void test02(){ int[] arr = {1,2,3,4,5}; IntStream stream = Arrays.stream(arr); } @Test public void test01(){ List<Integer> list = Arrays.asList(1,2,3,4,5); //In JDK 1.8, methods are added to the Collection series Stream<Integer> stream = list.stream(); } }
17.2.2 intermediate operation
Multiple intermediate operations can be connected to form a pipeline. Unless the termination operation is triggered on the pipeline, the intermediate operation will not perform any processing! When the operation is terminated, it is processed all at once, which is called "lazy evaluation".
method | Description |
---|---|
filter(Predicate p) | Receive Lambda and exclude some elements from the stream |
distinct() | Filter to remove duplicate elements through equals() of the elements generated by the flow |
limit(long maxSize) | Truncate the stream so that its elements do not exceed the given number |
skip(long 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) |
peek(Consumer action) | Receive Lambda and perform Lambda volume operation for each data in the stream |
sorted() | Generate a new stream, which is sorted in natural order |
sorted(Comparator com) | Generates a new stream, sorted in comparator order |
map(Function f) | Receive a function as an argument, which is applied to each element and mapped to a new element. |
mapToDouble(ToDoubleFunction f) | Receive a function as an argument, which will be applied to each element to generate a new DoubleStream. |
mapToInt(ToIntFunction f) | Receive a function as a parameter, which will be applied to each element to generate a new IntStream. |
mapToLong(ToLongFunction f) | Receive a function as an argument, which will be applied to each element to generate a new LongStream. |
flatMap(Function f) | Take a function as a parameter, replace each value in the stream with another stream, and then connect all streams into one stream |
package com.atguigu.test06; import java.util.Arrays; import java.util.stream.Stream; import org.junit.Test; public class Test08StreamMiddle { @Test public void test12(){ String[] arr = {"hello","world","java"}; Arrays.stream(arr) .flatMap(t -> Stream.of(t.split("|")))//Function < T, R > interface Abstract Method R apply (T, t) now R is a Stream .forEach(System.out::println); } @Test public void test11(){ String[] arr = {"hello","world","java"}; Arrays.stream(arr) .map(t->t.toUpperCase()) .forEach(System.out::println); } @Test public void test10(){ Stream.of(1,2,3,4,5) .map(t -> t+=1)//Function < T, R > interface Abstract Method R apply (T, t) .forEach(System.out::println); } @Test public void test09(){ //I hope to find the top three maximum values. The top three maximum values are not repeated Stream.of(11,2,39,4,54,6,2,22,3,3,4,54,54) .distinct() .sorted((t1,t2) -> -Integer.compare(t1, t2))//Comparator interface int compare(T t1, T t2) .limit(3) .forEach(System.out::println); } @Test public void test08(){ long count = Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .distinct() .peek(System.out::println) //Abstract method void accept (T) of Consumer interface .count(); System.out.println("count="+count); } @Test public void test07(){ Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .skip(5) .distinct() .filter(t -> t%3==0) .forEach(System.out::println); } @Test public void test06(){ Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .skip(5) .forEach(System.out::println); } @Test public void test05(){ Stream.of(1,2,2,3,3,4,4,5,2,3,4,5,6,7) .distinct() //(1,2,3,4,5,6,7) .filter(t -> t%2!=0) //(1,3,5,7) .limit(3) .forEach(System.out::println); } @Test public void test04(){ Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .limit(3) .forEach(System.out::println); } @Test public void test03(){ Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5) .distinct() .forEach(System.out::println); } @Test public void test02(){ Stream.of(1,2,3,4,5,6) .filter(t -> t%2==0) .forEach(System.out::println); } @Test public void test01(){ //1. Create Stream Stream<Integer> stream = Stream.of(1,2,3,4,5,6); //2. Processing //Filter: filter (predict P) //Take out the even number inside /* * filter(Predicate p) * Predicate It is a functional interface. The abstract method is Boolean test (T) */ stream = stream.filter(t -> t%2==0); //3. End operation: for example: traversal stream.forEach(System.out::println); } }
17.2.3 termination operation
The terminal operation generates results from the pipeline of the stream. The result can be any value that is not a stream, such as List, Integer, or even void. The stream cannot be used again after it has been terminated.
method | describe |
---|---|
boolean allMatch(Predicate p) | Check that all elements match |
boolean anyMatch(Predicate p) | Check to see if at least one element matches |
boolean noneMatch(Predicate p) | Check that no elements match |
Optional findFirst() | Returns the first element |
Optional findAny() | Returns any element in the current stream |
long count() | Returns the total number of elements in the stream |
Optional max(Comparator c) | Returns the maximum value in the stream |
Optional min(Comparator c) | Returns the minimum value in the stream |
void forEach(Consumer c) | iteration |
T reduce(T iden, BinaryOperator b) | You can combine the elements in the flow repeatedly to get a value. Return T |
U reduce(BinaryOperator b) | You can combine the elements in the flow repeatedly to get a value. Return to Optional |
R collect(Collector c) | Convert the Stream to another form. Receive the implementation of a Collector interface, which is used to summarize the elements in the Stream |
The implementation of the method in the Collector interface determines how to perform collection operations (such as collecting List, Set and Map). In addition, the Collectors utility class provides many static methods to easily create common Collector instances.
package com.atguigu.test06; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Test; public class Test09StreamEnding { @Test public void test14(){ List<Integer> list = Stream.of(1,2,4,5,7,8) .filter(t -> t%2==0) .collect(Collectors.toList()); System.out.println(list); } @Test public void test13(){ Optional<Integer> max = Stream.of(1,2,4,5,7,8) .reduce((t1,t2) -> t1>t2?t1:t2);//BinaryOperator interface T apply(T t1, T t2) System.out.println(max); } @Test public void test12(){ Integer reduce = Stream.of(1,2,4,5,7,8) .reduce(0, (t1,t2) -> t1+t2);//BinaryOperator interface T apply(T t1, T t2) System.out.println(reduce); } @Test public void test11(){ Optional<Integer> max = Stream.of(1,2,4,5,7,8) .max((t1,t2) -> Integer.compare(t1, t2)); System.out.println(max); } @Test public void test10(){ Optional<Integer> opt = Stream.of(1,2,4,5,7,8) .filter(t -> t%3==0) .findFirst(); System.out.println(opt); } @Test public void test09(){ Optional<Integer> opt = Stream.of(1,2,3,4,5,7,9) .filter(t -> t%3==0) .findFirst(); System.out.println(opt); } @Test public void test08(){ Optional<Integer> opt = Stream.of(1,3,5,7,9).findFirst(); System.out.println(opt); } @Test public void test04(){ boolean result = Stream.of(1,3,5,7,9) .anyMatch(t -> t%2==0); System.out.println(result); } @Test public void test03(){ boolean result = Stream.of(1,3,5,7,9) .allMatch(t -> t%2!=0); System.out.println(result); } @Test public void test02(){ long count = Stream.of(1,2,3,4,5) .count(); System.out.println("count = " + count); } @Test public void test01(){ Stream.of(1,2,3,4,5) .forEach(System.out::println); } }
17.2.4 exercise
Case:
Now there are two ArrayList collections that store the names of multiple members in the queue, which require the traditional for loop (or enhanced for loop) to be used in turn
The following steps:
- The first team only needs the name of the member whose name is three words; Store in a new collection.
- After the first team screening, only the first three people; Store in a new collection.
- The second team only needs the names of members surnamed Zhang; Store in a new collection.
- After the second team is screened, do not use the first two people; Store in a new collection.
- Merge the two teams into one team; Store in a new collection.
- Create a Person object based on the name; Store in a new collection.
- Print the Person object information of the whole team.
The code of Person class is:
public class Person { private String name; public Person() {} public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{name='" + name + "'}"; } }
The codes of the two teams (sets) are as follows:
public static void main(String[] args) { //First team ArrayList<String> one = new ArrayList<>(); one.add("Delireba"); one.add("Song Yuanqiao"); one.add("Su Xinghe"); one.add("Stone breaks the sky"); one.add("Shi Zhongyu"); one.add("Laozi"); one.add("Zhuangzi"); one.add("master hongqi"); //Second team ArrayList<String> two = new ArrayList<>(); two.add("Gulinaza"); two.add("zhang wuji"); two.add("Zhao Liying"); two.add("Zhang Sanfeng"); two.add("Nicholas Zhao Si"); two.add("Zhang Tianai"); two.add("Zhang Ergou"); // ... write code to complete the title requirements }
Reference answer:
public static void main(String[] args) { //First team ArrayList<String> one = new ArrayList<>(); one.add("Delireba"); one.add("Song Yuanqiao"); one.add("Su Xinghe"); one.add("Stone breaks the sky"); one.add("Shi Zhongyu"); one.add("Laozi"); one.add("Zhuangzi"); one.add("master hongqi"); //Second team ArrayList<String> two = new ArrayList<>(); two.add("Gulinaza"); two.add("zhang wuji"); two.add("Zhao Liying"); two.add("Zhang Sanfeng"); two.add("Nicholas Zhao Si"); two.add("Zhang Tianai"); two.add("Zhang Ergou"); // The first team only needs the name of the member whose name is three words; // After the first team screening, only the first three people; Stream<String> streamOne = one.stream().filter(s ‐> s.length() == 3).limit(3); // The second team only needs the names of members surnamed Zhang; // After the second team is screened, do not use the first two people; Stream<String> streamTwo = two.stream().filter(s ‐> s.startsWith("Zhang")).skip(2); // Merge the two teams into one team; // Create a Person object based on the name; // Print the Person object information of the whole team. Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println); }
17.3 Optional class
By far, the notorious null pointer exception is the most common cause of failure in Java applications. Previously, in order to solve the null pointer exception, Google's famous guava project introduced the Optional class. Guava prevents code pollution by checking null values, which encourages programmers to write cleaner code. Inspired by Google Guava, the Optional class has become part of the Java 8 class library.
Optional is actually a container: it can hold the value of type T, or just null. Optional provides many useful methods so that we don't need to explicitly detect null values.
17.3.1 API
1. How do I create an Optional object? Or how to use Optional to load value objects or null values
(1) static Optional empty(): used to create an empty Optional
(2) Static option of (t value): used to create a non empty option
(3) static Optional ofNullable(T value): used to create an Optional that may or may not be empty
2. How to remove the wrapped object from the Optional container?
(1) T get(): Optional container must be non empty
It is safe to use T get() and of(T value)
(2)T orElse(T other) :
orElse(T other) is used with ofNullable(T value),
If the Optional container is not empty, the wrapped value is returned. If it is empty, the default value (spare tire) specified by orElse(T other)other is used instead
(3)T orElseGet(Supplier<? extends T> other) :
If the Optional container is not empty, the wrapped value is returned. If it is empty, the value provided by the Lambda expression of the Supplier interface is used instead
(4) T orElseThrow(Supplier<? extends X> exceptionSupplier)
If the Optional container is not empty, the wrapped value will be returned. If it is empty, the exception type you specified will be thrown instead of the original NoSuchElementException
3. Other methods
(1) boolean isPresent(): judge whether the value in the Optional container exists
(2)void ifPresent(Consumer<? super T> consumer) :
Judge whether the value in the Optional container exists. If it exists, perform the operation specified by the Consumer. If it does not exist, do not perform the operation
(3) Optional map(Function<? super T,? extends U> mapper)
Judge whether the value in the Optional container exists. If it exists, perform the operation specified by the Function interface. If it does not exist, do not perform the operation
package com.atguigu.test07; import java.util.ArrayList; import java.util.Optional; import org.junit.Test; public class TestOptional { @Test public void test9(){ String str = "Hello"; Optional<String> opt = Optional.ofNullable(str); //Judge whether it is a pure letter word. If so, turn it to uppercase, otherwise it remains unchanged String result = opt.filter(s->s.matches("[a-zA-Z]+")). map(s -> s.toLowerCase()). orElse(str); System.out.println(result); } @Test public void test8(){ String str = null; Optional<String> opt = Optional.ofNullable(str); String string = opt.orElseThrow(()->new RuntimeException("Value does not exist")); System.out.println(string); } @Test public void test7(){ String str = null; Optional<String> opt = Optional.ofNullable(str); String string = opt.orElseGet(String::new); System.out.println(string); } @Test public void test6(){ String str = "hello"; Optional<String> opt = Optional.ofNullable(str); String string = opt.orElse("atguigu"); System.out.println(string); } @Test public void test5(){ String str = null; Optional<String> opt = Optional.ofNullable(str); // System.out.println(opt.get());//java.util.NoSuchElementException: No value present } @Test public void test4(){ String str = "hello"; Optional<String> opt = Optional.of(str); String string = opt.get(); System.out.println(string); } @Test public void test3(){ String str = null; Optional<String> opt = Optional.ofNullable(str); System.out.println(opt); } @Test public void test2(){ String str = "hello"; Optional<String> opt = Optional.of(str); System.out.println(opt); } }
17.3.2 exercise
Exercise 1
Case:
(1) Declare a Girl type containing the name (String) attribute
(2) Declare a Boy type, including name (String) and girlfriend (Girl) attributes
(3) In the test class, create a Boy object and
If he has a girlfriend, show his girlfriend's name;
If he has no girlfriend, his girlfriend defaults to "Chang'e", that is, he can only enjoy "Chang'e"
class Girl{ private String name; public Girl(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Girl [name=" + name + "]"; } } class Boy{ private String name; private Girl girlFriend; public Boy(String name, Girl girlFriend) { super(); this.name = name; this.girlFriend = girlFriend; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Girl getGirlFriend() { return girlFriend; } public void setGirlFriend(Girl girlFriend) { this.girlFriend = girlFriend; } @Override public String toString() { return "Boy [name=" + name + ", girlFriend=" + girlFriend + "]"; } }
Test class
public static void main(String[] args) { // Boy boy = new Boy("Zhang San", null); Boy boy = new Boy("Zhang San",new Girl("Cui Cui")); Optional<Girl> grilFriend = Optional.ofNullable(boy.getGirlFriend()); Optional.of(grilFriend.orElse(new Girl("Chang'e"))).ifPresent(g->System.out.println(g)); }
Exercise 2
Case:
(1) Declare the student class, including name and age
(2) Add several student objects to an ArrayList collection
(3) Operate on the students in the set, find out those older than 30 years old, and take out the first student. If there is no such student, construct a new student object with no parameters and print the student information
Student class example code:
class Student{ private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } public Student() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } }
Test class
@Test public void test1(){ ArrayList<Student> list = new ArrayList<>(); list.add(new Student("Zhang San", 23)); //... //Take out the age of the first student older than 30 in the stream and print its age. If not, create a student object with no parameter structure Student stu = list.stream() .filter(s -> s.getAge()>30) .findFirst().orElse(new Student()); System.out.println("Student's age:" + stu.getAge()); }
public Girl getGirlFriend() { return girlFriend; } public void setGirlFriend(Girl girlFriend) { this.girlFriend = girlFriend; } @Override public String toString() { return "Boy [name=" + name + ", girlFriend=" + girlFriend + "]"; }
}
Test class ```java public static void main(String[] args) { // Boy boy = new Boy("Zhang San", null); Boy boy = new Boy("Zhang San",new Girl("Cui Cui")); Optional<Girl> grilFriend = Optional.ofNullable(boy.getGirlFriend()); Optional.of(grilFriend.orElse(new Girl("Chang'e"))).ifPresent(g->System.out.println(g)); }
Exercise 2
Case:
(1) Declare the student class, including name and age
(2) Add several student objects to an ArrayList collection
(3) Operate on the students in the set, find out those older than 30 years old, and take out the first student. If there is no such student, construct a new student object with no parameters and print the student information
Student class example code:
class Student{ private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } public Student() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } }
Test class
@Test public void test1(){ ArrayList<Student> list = new ArrayList<>(); list.add(new Student("Zhang San", 23)); //... //Take out the age of the first student older than 30 in the stream and print its age. If not, create a student object with no parameter structure Student stu = list.stream() .filter(s -> s.getAge()>30) .findFirst().orElse(new Student()); System.out.println("Student's age:" + stu.getAge()); }