1. Using a stream to traverse a collection
Brief introduction:
Java's collection framework, such as the list and map interfaces and the ArrayList and HashMap classes, makes it easy to manage both ordered and unordered collections. The collection framework has been continuously improved since the first day of introduction. In Java SE 8, we can manage, traverse, and aggregate collections through the flow's API. A stream-based collection is different from the input-output stream.
How does it work?
It takes a whole new approach to treating data as a single entity rather than as a single individual. When you use a stream, you don't need to worry about looping or traversing the details. You can create a stream directly from a collection. Then you can use this stream to come up with a lot of events, such as traversing, filtering, and gathering. I'll start with an example under the Com.tm.java8.features.stream.traversing package from the project Java8features. Code in a Sequentialstream class, there are two collection flows in Java SE 8, the serial and parallel streams.
list<person> people = new arraylist<> ();
People.add (New person ("Mohamed", 69));
People.add (New person ("DOAA", 25));
People.add (New person ("Malik", 6));
predicate<person> pred = (p), P.getage () > 65;
Displaypeople (people, pred);
...........
private static void Displaypeople (List<person> people, predicate<person> pred) {
System.out.println ("Selected:");
People.foreach (P-, {
if (Pred.test (p)) {
System.out.println (P.getname ());
}
});
}
In both streams, the serial stream is relatively simple and resembles an iterator that processes one element of the collection at a time. But the syntax is different from the previous. In this code, I create an array list of pepole, which is transformed up to list. It contains instances of three person classes. Then we use predicate to declare a condition that only the people that meet this condition will be displayed. Iterate through the collection in rows 48 to 52 of the Displaypeople () method, testing each of them one at a time. To run this code, you will get the following result:
Selected:
Mohamed
I'll show you how to refactor this code using a stream. First of all, I commented on this piece of code. Then, under the code for this comment, I started using the collection object people. Then I call a Stream () method. A stream object, similar to a collection, to declare a generic type. If you get a stream from a collection, the type of each item in the stream is consistent with the collection itself. My collection is an instance of the person class, so the same generic type is used in the stream.
System.out.println ("Selected:");
People.foreach (P-, {
if (Pred.test (p)) {
System.out.println (P.getname ());
// }
// });
People.stream (). ForEach (P-System.out.println (P.getname ()));
}
You can call a stream () method to get a stream object, and then you can do some work on that object. I simply called the ForEach method, which requires a LAMDA expression. I passed a LAMDA expression in the argument. Each item in the list is each item that is processed through the iterator. The process is done through lambda operators and method implementations. I simply use system output to output everyone's name. Save and run the code, and the output is as follows. Because there is no filtering, all elements in the list are output.
Selected:
Mohamed
Doaa
Malik
Now, once you have a stream object, you can easily use the predicate object. I have to show the test method that calls predicate when I use the For each method, but you can call a method named filter when you use the stream. The method receives a predicate object, and all the predicate objects have a test method, so it already knows how to invoke the method. So I made a little change to the code. I moved the. ForEach () method down two lines, and then in the middle of the blank line, I called the filter method.
People.stream ()
. Filter (Pred)
. ForEach (P-System.out.println (P.getname ()));
The filter method receives an instance object of an predicate interface. I'll pass the predicate object in. The Filtr method returns a filtered stream object, on which I can invoke the foreach () method. I run this code, and this time I only show the items in the collection that meet the predefined criteria. You can do more things on the flow object. Check out the doc documentation in the Java SE 8 API.
Selected:
Mohamed
You will see that besides filtering, you can do other things like aggregation, sorting, and so on. Before I summarize this presentation, I want to show you the important differences before the serial and parallel streams. An important goal of the Java SE 8 is to improve the processing power of multi-CPU systems. Java can automatically coordinate the operation of multiple CPUs at run time. All you need to do is simply convert the serial stream to a parallel stream.
Syntactically speaking, there are two ways to convert a stream. I copied a copy of the serial stream class. In the Package view window, I copy and paste the class, and then rename it, Parallelstream, to open the new class. In this release, the code for the comment is removed. I don't need these comments anymore. You can now create parallel streams in two ways. The first way is to call the Parallelstream () method in the collection. Now I have a stream that can automatically allocate the processor.
private static void Displaypeople (List<person> people, predicate<person> pred) {
System.out.println ("Selected:");
People.parallelstream ()
. Filter (Pred)
. ForEach (P-System.out.println (P.getname ()));
}
By running this code, you can see exactly the same results, filter and then return the data.
Selected:
Mohamed
The second way to create a parallel stream. Call the Stream () method again, and then call the parallel () method on the basis of the stream method, which is essentially the same thing. The start is a serial stream, which is then converted to a parallel stream. But it's still a stream. Can be filtered, can be processed in the same way as before. Just now the stream can be decomposed into multiple processing.
People.stream ()
. Parallel ()
. Filter (Pred)
. ForEach (P-System.out.println (P.getname ()));
Summarize
There is no clear provision to explain under what circumstances parallel streams are better than serial streams. This relies on the size and complexity of the data and the processing power of the hardware. And you're running a multi-CPU system. The only advice I can give you is to test your app and data. Establish a baseline, timed operation. Then use the serial and parallel streams separately to see which one is more appropriate for you.
2. Creating a stream from a collection or array
Brief introduction
The Java SE 8 's stream API is designed to help manage data collections, which refer to objects in the collection framework, such as array lists or hash tables. However, you can also create a stream directly from an array.
How does it work?
Under the Eg.com.tm.java8.features.stream.creating package in the Java8features project, I created a class named Arraytostream. In the main method of this class, I created an array containing three elements. Each element is an instance object of the person class.
public static void Main (String args[]) {
Person[] people = {
New Person ("Mohamed", 69),
New Person ("DOAA", 25),
New Person ("Malik", 6)};
for (int i = 0; i < people.length; i++) {
System.out.println (People[i].getinfo ());
}
}
The setters and getters methods, as well as the GetInfo () method, are created for private members in this class, and the method returns a stitched string.
Public String GetInfo () {
Return name + "(" + Age + ")";
}
Now, if you want to use a stream to handle this array, you might think you need to convert the array to an array list and then create the flow from this list. However, you can actually create a flow directly from an array in two ways. The first way, I don't need to process the data of the three lines of code, so first comment out. Then, under this, I declare an object of a stream type.
Stream is an interface under the Java.util.stream. When I press Ctrl+space and select it, the element's generics are prompted, which is the type of flow management. Here, the type of the element is the person, which is consistent with the type of the array element itself. I named my new stream object stream, and all the letters are lowercase. This is the first method of creating a stream, using the interface of the stream, calling the of () method. Note that there are two different versions of this method.
The first is the need for a single object, and the second one requires multiple objects. I use a method of a parameter, so I pass an array named people, and that's all I need to do. Stream.of () means passing in an array and wrapping the array in a stream. Now, I can use lambda expressions, filters, method references, and so on for streaming objects. I'll call the flow's for each method and pass in a lambda expression, passing in the current person object and the lambda operator, and get the information to the person object. This information is obtained through the object's GetInfo () method.
Person[] people = {
New Person ("Mohamed", 69),
New Person ("DOAA", 25),
New Person ("Malik", 6)};
for (int i = 0; i < people.length; i++) {
System.out.println (People[i].getinfo ());
// }
stream<person> stream = Stream.of (people);
Stream.foreach (P-System.out.println (P.getinfo ()));
Save and run the code, and you get the results. The order of the output elements is consistent with the order in which I put them. This is the first way: Use the Stream.of () method.
Mohamed (69)
DOAA (25)
Malik (6)
The other way is actually the same as the one above. Copy the above code and comment out the first way. This time without using the Stream.of () method, we use a class named Arrays, which is located under the Java.util package. On this class, you can call a method named stream. Note that the stream method can wrap arrays of various types, including base types and composite types.
stream<person> stream = Stream.of (people);
stream<person> stream = Arrays.stream (people);
Stream.foreach (P-System.out.println (P.getinfo ()));
Save and run the above code, and the flow of the finished thing is essentially the same as before.
Mohamed (69)
DOAA (25)
Malik (6)
Conclusion
So, whether it's stream.of () or Arrays.stream (), the thing that's done is essentially the same. are converted from an array of a primitive type or compound object type to a Stream object, and then you can use the lambda expression, filter, method reference, and so on.
3, the value of the aggregation stream
Brief introduction
Before, I have described how to iterate a collection using a stream. You can also use streams to aggregate each item in the collection. such as calculating the sum, average, total and so on. When you do these things, it is important to understand the parallel flow characteristics.
How does it work?
I will be demonstrating under the eg.com.tm.java8.features.stream.aggregating package of the Java8features project. First we use the Parallelstreams class. In the main method of this class, I created a list of arrays that contain string elements. I simply added 10,000 elements to the list using a loop. Then, in lines 35 and 36, I created a stream object and output each item in the stream one at a to each method.
public static void Main (String args[]) {
System.out.println ("Creating list");
list<string> strings = new arraylist<> ();
for (int i = 0; i < 10000; i++) {
Strings.add ("Item" + i);
}
Strings.stream ()
. ForEach (STR-System.out.println (str));
}
After running this code, I get a result I expected. The order in which the outputs are printed on the screen is consistent with the order in which they are added to the list.
.........
Item 9982
Item 9983
Item 9984
Item 9985
Item 9986
Item 9987
Item 9988
Item 9989
Item 9990
Item 9991
Item 9992
Item 9993
Item 9994
Item 9995
Item 9996
Item 9997
Item 9998
Item 9999
Now, let's take a look at what happens when you convert to a parallel stream. As I described earlier, I can call the Parallelstream method, or you can call the parallel method on the stream.
I'll take the second approach. Now, I can use the parallel stream, which can be handled based on the load assigned to multiple processors.
Strings.stream ()
. Parallel ()
. ForEach (STR-System.out.println (str));
Run the code again, and then observe what happens. Note that the last element that is currently printed is not the last element in the list, and the last element should be 9999. If I scroll the output, I can see that the process is bouncing in a loop in some way. This is because the data is divided into blocks at run time.
.........
Item 5292
Item 5293
Item 5294
Item 5295
Item 5296
Item 5297
Item 5298
Item 5299
Item 5300
Item 5301
Item 5302
Item 5303
Item 5304
Item 5305
Item 5306
Item 5307
Item 5308
Item 5309
Item 5310
Item 5311
The data block is then assigned to the appropriate processor for processing. The code will be executed only if all the blocks have been processed. Essentially, this is when the ForEach () method is called, and the entire process is divided as needed. Now, doing so may improve performance, or it may not. This depends on the size of the dataset and the performance of your hardware. With this example, it can also be seen that parallel streams may not be appropriate if you need to process each item one by one in the order in which it is added.
The serial flow ensures that the order of each run is consistent. But parallel streams, by definition, are a more efficient way. So the parallel flow is very effective when aggregating operations. It is appropriate to consider a collection as a whole and then perform some aggregation operations on the collection. I will use an example to demonstrate the count of collection elements, averaging, and summing operations.
We count in the main method of this class, starting with the same base code. Creates a list of 10,000 strings. Each item is then cycled through a for each method.
public static void Main (String args[]) {
System.out.println ("Creating list");
list<string> strings = new arraylist<> ();
for (int i = 0; i < 10000; i++) {
Strings.add ("Item" + i);
}
Strings.stream ()
. ForEach (STR-System.out.println (str));
}
In this example, I want to count the elements of the collection directly, rather than dealing with them all at once. So, I commented out the original code, using the following code. Because you don't know exactly how many elements the collection has. So I use long integer variables to store the results.
I name this variable count, which returns a long integer value by calling the collection strings. Stream (),. Count () method. This value is then stitched together with "count:" and then printed through the output of the system.
Strings.stream ()
. ForEach (STR-System.out.println (str));
Long Count = Strings.stream (). Count ();
System.out.println ("Count:" + count);
Save and run the snippet, and the output is the following. The statistics of the number of elements in the collection are almost instantaneously completed.
Creating List
count:10000
Now make a small change to the above code, adding two 0. Now, start processing 1000,000 strings. I ran the code again and soon returned the results.
Creating List
count:1000000
Now, I'm working with a parallel stream to see what happens. I add the parallel method below:
Strings.stream ()
. ForEach (STR-System.out.println (str));
Long Count = Strings.stream (). Parallel (). Count ();
System.out.println ("Count:" + count);
Then I ran the code and found that it took a little longer. Now I'm doing a benchmark to see what's happening by grabbing the time stamp before and after the operation. Then do a little math thing. On different systems, the results may be different. But in my experience, this simple collection of simple types doesn't have much advantage in using parallel streams. However, I encourage you to do your own benchmarking, although it is a bit of a hassle. But it also depends on how you do it.
Let's look at summation and averaging. I will use the Sumandaverage class. This time, I have a list of three person objects, each with a different age value. My goal is to seek an average of three age and age. I added a new line of code after all the person objects were added to the list. Then, I created an integer variable named sum.
First, I get a stream through the Pepole.stream () method. On the basis of this flow, I can call the Maptoint () method. Note that there are also two similar Map method:maptodouble () and Maptolong (). The purpose of these methods is to get simple basic type data from a composite type and create a stream object. You can use lambda expressions to do the work. So, I choose the Maptoint () method, because everyone's age is an integer.
With respect to the LAMBDA expression, the start is a variable representing the current person. An integer is then returned through the lambda operator and the lambda expression (P.getage ()). This return value, which we sometimes call an int string. You can also return a double string or other type. Now, I can call the sum () method because I already know that it is a numeric type value. Now I've added all the age values of the person object in all the collections. With a single statement, I can output the results using System output. I connect the result of the sum to the "total of Ages" output.
list<person> people = new arraylist<> ();
People.add (New person ("Mohamed", 69));
People.add (New person ("DOAA", 25));
People.add (New person ("Malik", 6));
int sum = People.stream ()
. Maptoint (P-p.getage ())
. sum ();
SYSTEM.OUT.PRINTLN ("Total of Ages" + sum);
Save and run the above code. The sum of three ages is 100.
Total of Ages 100
The average value of these values is very similar. However, averaging requires division, so you need to consider the problem of divisor 0, so you can return a optional variable when you are averaging.
You can use a variety of data types. In calculating the average, I want to get a value of type Doule. So, I created a variable of type optionaldouble. Note that there are also Optional Int and Optional Long. I name the average AVG, and the code used is consistent with the sum code, starting with People.stream (). On this basis, the Maptoint () is used again. and pass the same lambda expression, and finally, call the average method.
Now, a variable of type optionaldouble is obtained. Before dealing with this variable, you can make sure it is a double value by IsPresent (). So, I used a section of If/else template code to handle. The condition for determination is avg.ispresent (). If the condition is true, use the System output to export the "Average" label and the average. In the ELSE clause, I simply print "average wasn ' t calculated".
optionaldouble avg = People.stream ()
. Maptoint (P-p.getage ())
. average ();
if (Avg.ispresent ()) {
System.out.println ("Average:" + avg);
} else {
System.out.println ("Average wasn ' t calculated");
}
Now, in this case, I know I can make it, because I've given the age of three people a value. However, this is not always the case. As I said earlier, there are 0 cases where you cannot get a double type return value. I save and run this code, note the optional double class, which is a composite object.
Total of Ages 100
AVERAGE:OPTIONALDOUBLE[33.333333333333336]
So, the real value is contained in the type, goes back to the code, references the object directly, and calls the Getasdouble () method.
if (Avg.ispresent ()) {
System.out.println ("Average:" + avg.getasdouble ());
} else {
System.out.println ("Average wasn ' t calculated");
}
Now I can get a value of type double. I run this code again, and the output is as follows:
Total of Ages 100
average:33.333333333333336
Conclusion
With flow and lambda expressions, you can perform aggregation calculations for a collection with very little code.
Java 8 new features use the stream API to process collections