java8-collecting data with streams (1)

Source: Internet
Author: User

Before that, we used the collect terminal operation, which was mainly used to combine all the elements of the stream into a list. Here you will find that collect is a normalization operation, like reduce, which can receive various practices as parameters, aggregating the results in a stream. The specific approach is defined by defining a new collector interface, so it is important to differentiate between collection, collector, and collect.

Are you excited? Well, let's take a look at an example of using a collector. Imagine that you have a list of transaction, and you want to group by currency category. In Java without lambda, even a simple use case can be very verbose, just like the following.

1Map<currency, list<transaction>> transactionsbycurrencies =NewHashmap<>();2list<transaction> transactions =NewArraylist<>();3          for(Transaction t:transactions) {4Currency Currency =t.getcurrency ();5List<transaction> Transactions2 =transactionsbycurrencies.get (currency);6             if(Transactions2 = =NULL){7Transactions2 =NewArraylist<>();8 transactionsbycurrencies.put (currency, transactions2);9             }Ten Transactions2.add (t); One}

If you're an experienced Java programmer, it might be handy to write something like this, but you have to admit that you have to write a lot of code for such a simple thing. Worse, it's harder to read than to write! The purpose of the code is not easy to see, although it is straightforward to do white work: "Put the list of transactions in the currency group!" ”。 Here, you will learn that with a more general collector parameter of the Collect method in stream, you can achieve exactly the same result in one sentence without the special case of the tolist used before:

1         Map<currency, list<transaction>> transactionsbycurrencies = Transactions.stream (). Collect ( Collectors.groupingby (transaction::getcurrency));

That's a lot worse than that, isn't it?

1. Introduction to Collectors

The previous example clearly demonstrates one of the main advantages of functional programming over instruction programming: You only need to point out the desired outcome-"What to do" without worrying about the steps taken-"How to Do". In the previous example, the arguments passed to the Collect method are an implementation of the Collector interface, which is the method of summarizing the elements in the stream. The previous tolist just said "generate a list for each element in order"; Here, Groupingby says "generate a map" whose key value is the (currency) bucket, and the value is the list of those elements in the bucket.

If multilevel grouping, the difference between the instruction and the functional programming is more obvious: as many layers of nested loops and conditions are required, the instruction code quickly becomes harder to read, harder to maintain, and harder to modify. In contrast, a functional version can be easily enhanced with a single collector.

(1). Collector used as advanced attribution

The just conclusion is that there is another benefit of good functional API design: it's easier to compound and reuse. The collector is useful because it provides a concise and flexible way to define the criteria that collect uses to produce a result set. More specifically, the convection call collect method triggers a normalization operation (parameterized by collect) for elements in the stream. , it iterates through each element in the stream and lets collector handle it.

In general, collector will apply a function to the element (many times an identity transformation that does not reflect any effect, such as tolist), and accumulate the result in a data interface, resulting in the final output of the process. For example, in the example of the trade grouping shown earlier, the conversion function extracts the currency of the eyebrow pencil transaction, and then uses the currency as the key to accumulate the transaction itself in the generated map.

As shown in the currency example, the implementation of the method in the Collector interface determines how the convection performs the normalization operation. We'll look at how to create a custom collector later. However, the Collectors utility class provides a number of static methods that can be easily used as examples of collectors, as long as they are available. The most direct and most commonly used collector is the ToList static method, which collects all the elements in the stream into a list:

1 list<transaction> transactions = Transactionstream.collect (Collectors.tolist ());

(2), pre-defined collector

Next, we focus on the functionality of the predefined collector, which is the collector that can be created from the factory method (Groupingby) provided by the Collectors class. They provide three major functions:

A.aggregating and summarizing flow elements into one value

B.element grouping

C. Element partitioning

Let's take a look at the collectors that can be normalized and aggregated. They are very handy in many situations, such as the one mentioned in the previous example, which deals with the total volume of transactions.

You will then see how the elements in the stream are grouped, while the previous example is extended to a multi-layered grouping, or a collection of different points is combined to further the operation of each subgroup. We will also talk about the special case "partitioning" of the grouping, which is the use of predicates (single-argument functions that return a Boolean value) as grouping functions.

2. Attribution and Summary

To illustrate how many collector instances can be created from the Collectors factory class, let's reuse the example from the previous chapter: include a list of delicacies!

As you just saw, the collector (the collect parameter of the stream method) is typically used when a flow project needs to be reorganized into a collection. In a broader sense, it is possible to combine all the items in a stream into one result. The result can be any type that can be complex as a multi-level map representing a tree, or simply as an integer--perhaps representing the sum of the calories in the menu. We'll discuss both of these result types.

Let's start with a simple example, using the collector returned by the counting factory method, to count how many vegetables are in the menu.

1 long howmanydishes = Menu.stream (). Collect (Collectors.counting ());

This can also be written more directly:

1 long howmanydishes = Menu.stream (). Count ();

The counting collector is especially useful when used in conjunction with other collectors, which is discussed later. In later content, we assume that you have imported all the static factory methods of the Collectors class:

Import static java.util.stream.collectors.*;

So you don't have to write counting () without collectors.counting () or something like that.

Let's go on to the simple pre-defined collector and see how to find the maximum and minimum values in the stream.

(1). Find the minimum and maximum values in a stream

Suppose you want to find the highest calorie dish in the menu. You can use two collectors, Collectors.maxby and Collectors.minby, to calculate the maximum or minimum value in a stream. The two collectors receive a comparator parameter to compare the elements in the stream. You can create a comparator to compare the dishes and pass it on to Collectors.maxby:

1         comparator<dish> dishcaloriescomparator = comparator.comparing (dish::getcalories); 2         3                 menu.stream ()4                     . Collect (Maxby (dishcaloriescomparator));

You might be wondering what's going on with optional<dish>. To answer this question, we need to ask: "What if menu is empty". Then there's no food to return! JAVA 8 introduces optional, which is a container that can or does not contain values. Here it is perfectly representative of the situation where the dishes can and cannot be returned. Before, we said the Findany method briefly mentioned it.

Another common return to a single-value-to-sum operation is a numeric field summation of an object in a stream, or you might want to ask for an average. This operation is called a rollup operation. Let's take a look at how to use collectors to express summary operations.

(2). Summary

The Collectors class provides a factory method specifically for summarization: Collectors.summingint. It can receive a function that maps an object to and from the desired int, and returns a collector that performs the summary operation we need after passing it to the normal collect method. For example, you can find the total calories in a menu list.

1 int tltalcalories = Menu.stream (). Collect (Summingint (dish::getcalories));

The collection process here. When traversing a stream, each bets is mapped to its heat, and the number is added to the accumulator (the initial value here is 0).

The Collectors.summinglong and Collectors.summingdouble methods work exactly the same way, and can be used in cases where the summation field is long or double.

But summaries are more than sums, and collectors.averagingint, together with corresponding Averaginglong and averagingdouble, can calculate the average number of values:

1 double tltalcalories = Menu.stream (). Collect (Averagingint (dish::getcalories));

So far, you've seen how to use collectors to count the elements in a stream, find the maximum and minimum values for the numeric attributes of those elements, and calculate their sums and averages. But most of the time, you might want to get two or more of these results, and you want to be able to do it just one operation at a time. In this case, you can use the collector returned by the Summarizingint factory. For example, with one summarizing operation you can count the number of elements in the menu and get the sum, average, maximum and minimum values of the dishes.

1         2                 Menu.stream (). Collect (Summarizingint (dish::getcalories));

This collector collects all this information into a class called Intsummrystatistics, which provides a convenient method of fetching values (getter) to access the results. Printing the Menustatistic object will get the following output:

1 intsummarystatistics{count=9, sum=4200, min=120, average=466.666667, max=800}

(3). Connection string

Joining Factory method The collector that is returned will stitch all the strings from each object in the stream using the ToString method into a single string. This means that you stitch up the names of all the dishes in the menu as follows:

1 String shortmenu = Menu.stream (). Map (Dish::getname). Collect (joining ());

Note that joining uses StringBuilder internally to append the generated strings one by one. Printing results:

1 porkbeefchickenfrench Friesriceseason Fruitpizzaprawnssalmon

But the readability of the string is not very good. Fortunately, the joining factory method has an overloaded version that can receive the decomposition characters between elements, so you can get a comma-separated list of dishes names:

1 String shortmenu = Menu.stream (). Map (Dish::getname). Collect (Joining (","));

As we expected, it will generate:

1 Pork, beef, chicken, French fries, rice, season fruit, pizza, prawns, salmon

So far, we've explored a variety of collectors that return a stream to a value. Next, we will show why all this form of the normalization process is in fact a special case where the Collectors.reduce factory method provides a more generalized imputation collector.

(4). Generalized Regression Summary

In fact, all the collectors we have discussed are a special case of the process of attribution that can be defined using the reducing factory method. Collectors.redcuing factory methods are generalizations of all these special cases. It can be said that the cases discussed earlier were only for the convenience of programmers. (However, please remember that convenience for programmers and readability is a top priority!) For example, you can use the reducing method to create a collector to calculate the total heat of your menu, as follows:

1         int 2                 menu.stream ()3                     . Collect (Reducing (0, dish::getcalories, (i, j), I + j));

It requires three parameters:

a. The first parameter is the starting value of the return operation and the return value when there are no elements in the stream, so it is clear that 0 is an appropriate value for the value.

B. The second parameter is the function--function object you used earlier, converting the dish to an int that represents the heat it contains.

C. The third parameter is a binaryoperator that accumulates two items into a value of the same type. Here it is the sum of two int.

Similarly, you can use the reducing of the single parameter form below to find the highest calorie dish, as shown below:

1         optional<dish> mostcaloriesdish = menu.stream ()2                 . Collect (Reducing (D1, D2) D1.getcalories () > d2.getcalories ()? D1:D2));

You can think of a collector created by the single-parameter reducing factory method as a special case of a three-parameter method, which takes the first item in the stream as the starting point and the identity function (that is, a function simply returns its input parameters) as a conversion function. It also means that if the parameter collector is passed to the Collect method of the empty stream, the collector has no starting point.

collection and Attribution:

So far, we've talked a lot about the content of the attribution. You might wonder how the collect and reduce methods of the stream interface differ, because the two methods usually get the same result. For example, you can use the reduce method to implement the work of Tolistcollector as follows:

1stream<integer> stream = arrays.aslist (1, 2, 3, 4, 5, 6, 7, 8). Stream ();2list<integer> numbers = Stream.reduce (NewArraylist<integer> (), (list<integer> L, Integer i) {3 L.add (i);4             returnl;5}, (list<integer> L1, list<integer> L2), {6 L1.addall (L2);7             returnL1;8});

A. The flexibility of the collection framework: Perform the same actions in different ways

You can further simplify the summation example of the previous reducing collector by referencing the sum method of the integer class without writing a lambda expression that expresses the same action. This will get the program:

1 int totalcarories = Menu.stream (). Collect (Reducing (0, dish::getcalories, integer::sum));

java8-collecting data with streams (1)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.