The release of Stream programming with JDK 1.8, along with Lambda expressions, marks another milestone in Java history. Stream programming was frequently used in the company’s data analysis and presentation requirements. Today we’ll take a look at the principles and applications of Stream programming in data analysis, and again we’ll review the use of Lambda expressions.
Lambda expressions
I’ve written about functional programming in the JDK before.
Blog.csdn.net/pbrlovejava…
1.1. Basic use
Before introducing Stream programming, you need to understand the use and rationale of Lambda expressions.
A common scenario for using Lambda expressions is to simplify code by optimizing verbose declarations of anonymous inner classes. For example, in JDK 1.7, when we needed to declare a Thread and include a Runnable task, we did this:
- JDK version 1.7
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run(a) {
// The specific task execution content}}; Thread thread =new Thread(task);
thread.start();
}
Copy the code
The essence of the task here is an anonymous inner class that implements the Runnable interface and is finally passed into the constructor at new Thread(task) to create a Thread to execute the task.
Of course, we can simplify it further:
- JDK version 1.7 (simplified)
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run(a) {
// The specific task execution content}}); thread.start(); }Copy the code
We can create a Thread directly by passing new Runnable to the constructor and overwriting the run method.
At this point, without the help of Idea, you should at least remember to write this code:
- The constructor of the Thread class can pass in types
- Pass in methods that the type needs to override
But this is a cumbersome way to write it. We know that this is just to fit the definition of the construction, but it does mean to pull down your pants and fart. In this case, the Lambda expression solves the problem:
We just need to remember the method arguments and return values that the anonymous inner class needs to override. The rest is up to the Lambda!
- JDK 1.8 (Lambda)
public static void main(String[] args) {
Thread thread = new Thread(()->{
// The specific task execution content
});
thread.start();
}
Copy the code
How simple and clear? Now the code looks a lot easier to read. Here are some more examples:
- Implementing large top heap (JDK 1.7)
public static void main(String[] args) {
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer x, Integer y) {
returny - x; }}); }Copy the code
- Implementing a large top heap (JDK 1.8 Lambda)
public static void main(String[] args) {
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((x, y) -> y - x);
}
Copy the code
If you can see the difference, using Lambda expressions requires only one line to declare a large top heap, without having to manually create the comparator and override the comparator methods.
Finally, note that when using a Lambda expression to override a method instead of an anonymous interface, the interface has one and only one abstract method, otherwise the Lambda expression cannot determine which method to override.
1.2 Implementation principle
After looking at the basics, it’s time to look at how Lambda expressions are implemented.
Debug can find:When the Lambda expression is executed, the bytecode technology and reflection technology generate the corresponding anonymous inner class implementation. (Since it is generated in memory, you can only view the runtime status through Idea)
So when using Lambda expressions, there is an additional efficiency loss in bytecode generation and reflection compared to the normal anonymous inner class approach, but it is generally not noticeable as long as it is not used particularly frequently and in many places.
For more insight, read:
Cloud.tencent.com/developer/a…
2. Stream programming
The use of Stream programming, which relies on Lambda expressions, is a feature of JDK 1.8. Stream programming can be very convenient and flexible data processing, and do not need to do additional processing through the database, so it is widely used in data processing related programs.
2.1 Stream API and Lambda Expression implementation traversal Demo
Now suppose we need to get the List< String > starting with the letter A, which can be written in either of the following two ways, which we can find condensed using streaming programming and Lambda expressions: Get stream-Filter stream-Iterate stream.
String[] arrays = {"a1"."a2"."b"."c"};
// Convert arrays to List
List<String> stringList = Arrays.asList(arrays);
// The original processing method
for (String s : stringList) {
if(s.charAt(0) = ='a'){ System.out.println(s); }}//Stream API combined with Lambda Expression
stringList.stream()
.filter(s->s.charAt(0) = ='a')
.forEach(s-> System.out.println(s));
Copy the code
2.2. Common methods for Stream
A Stream is a Stream that has been updated since Java 8. It is different from an InputStream, OutputStream, etc in an IO Stream. To be more specific, the Stream under java.util has nothing to do with an IO Stream. Stream here is a data Stream and an object Stream.
2.2.1, of (T… values)
To convert a List or Set into a data stream, use xxxlist.stream () or stream.of (T… Values), T… Values represent an array or an indefinite number of data that are converted into a data stream in order.
- Three ways to get a Stream
/ / 1
Stream<String> stringStream1 = Stream.of(new String[]{"a"."b"."c"});
/ / 2
Stream<String> stringStream2 = Stream.of("a"."b"."c");
/ / 3
Stream<String> stringStream3 = stringList.stream();
Copy the code
2.2.2, filter (Predicate <? super T> predicate)
Filter is used to filter streams into new streams as needed, passing in a Predicate interface under java.util.function and overriding the test method to verify:
Stream newStream = stringList.stream().filter(new Predicate() {
@Override
public boolean test(Object s) {
if (s.toString().charAt(0) = ='a') {
return true;
}
return false; }});Copy the code
Using Lambda expressions, we can simplify the above code to:
Stream newStream = stringList.stream().filter(s->s.charAt(0) = ='a');
Copy the code
Then, the forEach (Consumer <? super T> action)
To operate on each element of this stream, you need to pass in parameters to the Consumer interface and implement its Accept method:
stringStream.forEach(new Consumer(){
@Override
public void accept(Object s) { System.out.println(s); }});Copy the code
Combining the Lambda expression:
stringStream.forEach(s->System.out.println(s));
Copy the code
2.2.4, map (Function <? super T,? extends R> mapper)
The map method processes the Stream and returns an object that acts as the original Stream.
- Converts data to uppercase
String[] arrays = {"a1"."a2"."b"."c"};
// Convert arrays to List
List<String> stringList = Arrays.asList(arrays);
List<String> collect = stringList.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(collect);
Copy the code
To be clear, the argument to the map method is a functional method, either using a lambda expression or using a double colon expression directly (the double colon expression :: can now be understood as an object by calling the method :: and passing the current data as an argument); The collect method “collects” the map-processed streams to form a new stream, passing the parameter global.tolist () to convert the stream in the form of a List.
- Delete the trailing number
List<String> collect1 = Stream.of(arrays) .filter(v -> v ! =null)
.map(v -> {
if (v.length() == 2) {
// Delete the tail
v = v.substring(0, v.length()-1);
}
// Return the final result
return v;
})
.collect(Collectors.toList());
System.out.println(collect1);
Copy the code
- Convert a Person object to a Student object
class Person implements Serializable {
/ / number
private int id;
/ / name
private String name;
/ / age
private int age;
//getter setter...
}
class Student implements Serializable {
/ / student id
private int schoolId;
/ / name
private String name;
/ / age
private int age;
//getter setter...
}
public static void main(String[] args) {
List<Person> personList = Arrays.asList(new Person(1."lily".18),new Person(2."arong".19),new Person(3."joke".20));
// Convert PersonList to Studentlist
List<Student> studentList = personList.stream().map(p -> {
Student student = new Student();
student.setSchoolId(3111000 + p.getId());
student.setName(p.getName());
student.setAge(p.getAge());
// Return the converted student as the result
returnstudent; }).collect(Collectors.toList()); System.out.println(studentList.toString()); }}Copy the code
Use Stream and Lambda expressions for data processing
3.1 basic Applications
Now let’s look at some of the advantages of Stream programming in terms of data processing.
Suppose there is such a requirement that the front end needs to obtain the basic monitoring data provided by the back end for display. The VO of the basic monitoring data interacting with MySQL has been encapsulated as follows:
- BasicDataVO
/ * * *@Auther: ARong
* @Date: 2020/7/9 5:59 下午
* @Description: Basic data VO */
public class BasicDataVO {
private String name; // Data name
private String type; // Data category
private int count; // Number of data
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType(a) {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getCount(a) {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString(a) {
return "BasicDataVO{" +
"name='" + name + '\' ' +
", type='" + type + '\' ' +
", count=" + count +
'} '; }}Copy the code
Now you need to display the following data table in a single page:
- Basic data of the top three in quantity (sorted by quantity)
- Basic data for category “domestic” (sorted by quantity)
- Correct the quantity in the base data of category “overseas” and return it with the full data (sorted by quantity)
With normal programming logic, these three requirements would require three database queries and three different SQL runs to combine the required data sets:
- BasicDataController
@Controller
public class BasicDataController {
@RequestMapping("/getBasicData")
public Map<String, List<BasicDataVO>> getBasicData() {
HashMap<String, List<BasicDataVO>> res = new HashMap<String, List<BasicDataVO>>();
// Basic data of the top three (sorted by quantity)
List<BasicDataVO> dataList1 = queryLimit3OrderByCount();
// Base data (in order of quantity)
List<BasicDataVO> dataList2 = queryType1OrderByCount();
// Return the quantity of the base data of category "overseas" with the accuracy correction and the full data (sorted by quantity)
List<BasicDataVO> dataList3 = queryType2AndFixDataOrderByCount();
// Data encapsulation
res.put("list1", dataList1);
res.put("list2", dataList2);
res.put("list3", dataList3);
returnres; }}Copy the code
Of course, this is fine, but it is a waste of resources to do three MySQL join and query for three pieces of data that are not very different, so the alternative is to query the generic data first, and then use Steam streaming programming and Lambda expressions to get the customized data needed on the generic data:
- BasicDataController
@Controller
public class BasicDataController {
@RequestMapping("/getBasicData")
public Map<String, List<BasicDataVO>> getBasicData() {
HashMap<String, List<BasicDataVO>> res = new HashMap<String, List<BasicDataVO>>();
// Get generic data
List<BasicDataVO> dataList = queryBasicData();
// Basic data of the top three (sorted by quantity)
List<BasicDataVO> dataList1 = dataList.stream()
.sorted((x, y) -> x.getCount() - y.getCount())
.limit(3)
.collect(Collectors.toList());
// Base data (in order of quantity)
List<BasicDataVO> dataList2 = dataList.stream()
.filter(x -> "Domestic server".equals(x.getType()))
.sorted((x, y) -> x.getCount() - y.getCount())
.collect(Collectors.toList());
// Return the quantity of the base data of category "overseas" with the accuracy correction and the full data (sorted by quantity)
// There is a shallow copy problem
List<BasicDataVO> dataList3 = dataList.stream().map(x -> {
x.setCount(fixData(x.getName()));
return x;
})
.sorted((x, y) -> x.getCount() - y.getCount())
.collect(Collectors.toList());
// Data encapsulation
res.put("list1", dataList1);
res.put("list2", dataList2);
res.put("list3", dataList3);
returnres; }}Copy the code
3.2. Basic principles
The principle and implementation of Stream is very complicated, but this is just for simple learning and understanding. For further understanding, please click on this article, which is very good.
www.cnblogs.com/CarpenterLe…
When using list.stream(), it returns a stream object. This is because in JDK 1.8, the developers changed the top-level interface of Collection, the Collection framework, and added a hook method to it:
- Collection
public interface Collection<E> extends 可迭代<E> {
default Stream<E> stream(a) {
return StreamSupport.stream(spliterator(), false); }}Copy the code
The hook method is stream(), and the corresponding stream object is obtained through the StreamSupport class.
- StreamSupport
public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
Objects.requireNonNull(spliterator);
return new ReferencePipeline.Head<>(spliterator,
StreamOpFlag.fromCharacteristics(spliterator),
parallel);
}
Copy the code
As you can see, StreamSupport creates a new ReferencePipeline.Head class, does Head look familiar to you? Yes, the entire Stream is organized by a two-way linked list, with one Stream for each stage: