Item 45: Use streams judiciously(明智地使用流)

The streams API was added in Java 8 to ease the task of performing bulk operations, sequentially or in parallel. This API provides two key abstractions: the stream, which represents a finite or infinite sequence of data elements, and the stream pipeline, which represents a multistage computation on these elements. The elements in a stream can come from anywhere. Common sources include collections, arrays, files, regular expression pattern matchers, pseudorandom number generators, and other streams. The data elements in a stream can be object references or primitive values. Three primitive types are supported: int, long, and double.

在 Java 8 中添加了流 API,以簡化序列或并行執(zhí)行批量操作的任務(wù)俩檬。這個 API 提供了兩個關(guān)鍵的抽象:流(表示有限或無限的數(shù)據(jù)元素序列)和流管道(表示對這些元素的多階段計算)来破。流中的元素可以來自任何地方。常見的源包括集合德频、數(shù)組、文件缩幸、正則表達式的 Pattern 匹配器壹置、偽隨機數(shù)生成器和其他流。流中的數(shù)據(jù)元素可以是對象的引用或基本數(shù)據(jù)類型表谊。支持三種基本數(shù)據(jù)類型:int钞护、long 和 double。

A stream pipeline consists of a source stream followed by zero or more intermediate operations and one terminal operation. Each intermediate operation transforms the stream in some way, such as mapping each element to a function of that element or filtering out all elements that do not satisfy some condition. Intermediate operations all transform one stream into another, whose element type may be the same as the input stream or different from it. The terminal operation performs a final computation on the stream resulting from the last intermediate operation, such as storing its elements into a collection, returning a certain element, or printing all of its elements.

流管道由源流爆办、零個或多個 Intermediate 操作和一個 Terminal 操作組成难咕。每個 Intermediate 操作以某種方式轉(zhuǎn)換流,例如將每個元素映射到該元素的一個函數(shù)距辆,或者過濾掉不滿足某些條件的所有元素余佃。中間操作都將一個流轉(zhuǎn)換為另一個流,其元素類型可能與輸入流相同挑格,也可能與輸入流不同咙冗。Terminal 操作對最后一次 Intermediate 操作所產(chǎn)生的流進行最終計算,例如將其元素存儲到集合中漂彤、返回特定元素雾消、或打印其所有元素。

Stream pipelines are evaluated lazily: evaluation doesn’t start until the terminal operation is invoked, and data elements that aren’t required in order to complete the terminal operation are never computed. This lazy evaluation is what makes it possible to work with infinite streams. Note that a stream pipeline without a terminal operation is a silent no-op, so don’t forget to include one.

流管道的計算是惰性的:直到調(diào)用 Terminal 操作時才開始計算挫望,并且對完成 Terminal 操作不需要的數(shù)據(jù)元素永遠不會計算立润。這種惰性的求值機制使得處理無限流成為可能。請注意媳板,沒有 Terminal 操作的流管道是無動作的桑腮,因此不要忘記包含一個 Terminal 操作。

The streams API is fluent: it is designed to allow all of the calls that comprise a pipeline to be chained into a single expression. In fact, multiple pipelines can be chained together into a single expression.

流 API 是流暢的:它被設(shè)計成允許使用鏈式調(diào)用將組成管道的所有調(diào)用寫到單個表達式中蛉幸。實際上破讨,可以將多個管道鏈接到一個表達式中。

By default, stream pipelines run sequentially. Making a pipeline execute in parallel is as simple as invoking the parallel method on any stream in the pipeline, but it is seldom appropriate to do so (Item 48).

默認情況下奕纫,流管道按順序運行提陶。讓管道并行執(zhí)行與在管道中的任何流上調(diào)用并行方法一樣簡單,但是這樣做不一定合適(Item-48)匹层。

The streams API is sufficiently versatile that practically any computation can be performed using streams, but just because you can doesn’t mean you should. When used appropriately, streams can make programs shorter and clearer; when used inappropriately, they can make programs difficult to read and maintain. There are no hard and fast rules for when to use streams, but there are heuristics.

流 API 非常通用隙笆,實際上任何計算都可以使用流來執(zhí)行,但這并不意味著你就應(yīng)該這樣做。如果使用得當撑柔,流可以使程序更短瘸爽、更清晰;如果使用不當铅忿,它們會使程序難以讀取和維護剪决。對于何時使用流沒有硬性的規(guī)則,但是有一些啟發(fā)式的規(guī)則辆沦。

Consider the following program, which reads the words from a dictionary file and prints all the anagram groups whose size meets a user-specified minimum. Recall that two words are anagrams if they consist of the same letters in a different order. The program reads each word from a user-specified dictionary file and places the words into a map. The map key is the word with its letters alphabetized, so the key for "staple" is "aelpst", and the key for "petals" is also "aelpst": the two words are anagrams, and all anagrams share the same alphabetized form (or alphagram, as it is sometimes known). The map value is a list containing all of the words that share an alphabetized form. After the dictionary has been processed, each list is a complete anagram group. The program then iterates through the map’s values() view and prints each list whose size meets the threshold:

考慮下面的程序昼捍,它從字典文件中讀取單詞并打印所有大小滿足用戶指定最小值的變位組识虚≈叮回想一下,如果兩個單詞以不同的順序由相同的字母組成担锤,那么它們就是字謎蔚晨。該程序從用戶指定的字典文件中讀取每個單詞,并將這些單詞放入一個 Map 中肛循。Map 的鍵是按字母順序排列的單詞铭腕,因此「staple」的鍵是「aelpst」,而「petals」的鍵也是「aelpst」:這兩個單詞是字謎多糠,所有的字謎都有相同的字母排列形式(有時稱為字母圖)累舷。Map 的值是一個列表,其中包含共享按字母順序排列的表單的所有單詞夹孔。在字典被處理之后被盈,每個列表都是一個完整的字謎組。然后搭伤,該程序遍歷 Map 的 values() 視圖只怎,并打印大小滿足閾值的每個列表:

// Prints all large anagram groups in a dictionary iteratively
public class Anagrams {
    public static void main(String[] args) throws IOException {
        File dictionary = new File(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);
        Map<String, Set<String>> groups = new HashMap<>();
        try (Scanner s = new Scanner(dictionary)) {
            while (s.hasNext()) {
                String word = s.next();
                groups.computeIfAbsent(alphabetize(word),(unused) -> new TreeSet<>()).add(word);
            }
        }
        for (Set<String> group : groups.values())
        if (group.size() >= minGroupSize)
            System.out.println(group.size() + ": " + group);
    }

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}

One step in this program is worthy of note. The insertion of each word into the map, which is shown in bold, uses the computeIfAbsent method, which was added in Java 8. This method looks up a key in the map: If the key is present, the method simply returns the value associated with it. If not, the method computes a value by applying the given function object to the key, associates this value with the key, and returns the computed value. The computeIfAbsent method simplifies the implementation of maps that associate multiple values with each key.

這個程序中的一個步驟值得注意。將每個單詞插入到 Map 中(以粗體顯示)使用 computeIfAbsent 方法怜俐,該方法是在 Java 8 中添加的身堡。此方法在 Map 中查找鍵:如果鍵存在,則該方法僅返回與其關(guān)聯(lián)的值拍鲤。若不存在贴谎,則該方法通過將給定的函數(shù)對象應(yīng)用于鍵來計算一個值,將該值與鍵關(guān)聯(lián)季稳,并返回計算的值擅这。computeIfAbsent 方法簡化了將多個值與每個鍵關(guān)聯(lián)的 Map 的實現(xiàn)。

Now consider the following program, which solves the same problem, but makes heavy use of streams. Note that the entire program, with the exception of the code that opens the dictionary file, is contained in a single expression. The only reason the dictionary is opened in a separate expression is to allow the use of the try-with-resources statement, which ensures that the dictionary file is closed:

現(xiàn)在考慮下面的程序绞幌,它解決了相同的問題蕾哟,但是大量使用了流。注意,除了打開字典文件的代碼之外谭确,整個程序都包含在一個表達式中帘营。在單獨的表達式中打開字典的唯一原因是允許使用 try with-resources 語句,該語句確保字典文件是關(guān)閉的:

// Overuse of streams - don't do this!
public class Anagrams {
    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);
        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(
            groupingBy(word -> word.chars().sorted()
            .collect(StringBuilder::new,(sb, c) -> sb.append((char) c),
            StringBuilder::append).toString()))
            .values().stream()
            .filter(group -> group.size() >= minGroupSize)
            .map(group -> group.size() + ": " + group)
            .forEach(System.out::println);
        }
    }
}

If you find this code hard to read, don’t worry; you’re not alone. It is shorter, but it is also less readable, especially to programmers who are not experts in the use of streams. Overusing streams makes programs hard to read and maintain. Luckily, there is a happy medium. The following program solves the same problem, using streams without overusing them. The result is a program that’s both shorter and clearer than the original:

如果你發(fā)現(xiàn)這段代碼難以閱讀逐哈,不要擔心芬迄;不單是你有這樣的感覺。它雖然更短昂秃,但可讀性也更差禀梳,特別是對于不擅長流使用的程序員來說。過度使用流會使得程序難以讀取和維護肠骆。幸運的是算途,有一個折衷的辦法。下面的程序解決了相同的問題蚀腿,在不過度使用流的情況下使用流嘴瓤。結(jié)果,這個程序比原來的程序更短莉钙,也更清晰:

// Tasteful use of streams enhances clarity and conciseness
public class Anagrams {
    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);
        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(groupingBy(word -> alphabetize(word)))
            .values().stream()
            .filter(group -> group.size() >= minGroupSize)
            .forEach(g -> System.out.println(g.size() + ": " + g));
        }
    }
    // alphabetize method is the same as in original version
}

Even if you have little previous exposure to streams, this program is not hard to understand. It opens the dictionary file in a try-with-resources block, obtaining a stream consisting of all the lines in the file. The stream variable is named words to suggest that each element in the stream is a word. The pipeline on this stream has no intermediate operations; its terminal operation collects all the words into a map that groups the words by their alphabetized form (Item 46). This is exactly the same map that was constructed in both previous versions of the program. Then a new Stream<List<String>> is opened on the values() view of the map. The elements in this stream are, of course, the anagram groups. The stream is filtered so that all of the groups whose size is less than minGroupSize are ignored, and finally, the remaining groups are printed by the terminal operation forEach.

即使你以前很少接觸流廓脆,這個程序也不難理解。它在帶有資源的 try 塊中打開字典文件磁玉,獲得由文件中所有行組成的流停忿。流變量名為 words,表示流中的每個元素都是一個單詞蚊伞。此流上的管道沒有 Intermediate 操作席赂;它的 Terminal 操作將所有單詞收集到一個 Map 中,該 Map 按字母順序?qū)卧~分組(Item-46)厚柳。這與在程序的前兩個版本中構(gòu)造的 Map 完全相同氧枣。然后在 Map 的 values() 視圖上打開一個新的 Stream<List<String>>。這個流中的元素當然是字謎組别垮。對流進行過濾便监,以便忽略所有大小小于 minGroupSize 的組,最后碳想,Terminal 操作 forEach 打印其余組烧董。

Note that the lambda parameter names were chosen carefully. The parameter g should really be named group, but the resulting line of code would be too wide for the book. In the absence of explicit types, careful naming of lambda parameters is essential to the readability of stream pipelines.

注意,lambda 表達式參數(shù)名稱是經(jīng)過仔細選擇的胧奔。參數(shù) g 實際上應(yīng)該命名為 group逊移,但是生成的代碼行對于本書排版來說太寬了。在沒有顯式類型的情況下龙填,lambda 表達式參數(shù)的謹慎命名對于流管道的可讀性至關(guān)重要胳泉。

Note also that word alphabetization is done in a separate alphabetize method. This enhances readability by providing a name for the operation and keeping implementation details out of the main program. Using helper methods is even more important for readability in stream pipelines than in iterative code because pipelines lack explicit type information and named temporary variables.

還要注意拐叉,單詞的字母化是在一個單獨的字母化方法中完成的。這通過為操作提供一個名稱并將實現(xiàn)細節(jié)排除在主程序之外扇商,從而增強了可讀性凤瘦。在流管道中使用 helper 方法比在迭代代碼中更重要,因為管道缺少顯式類型信息和命名的臨時變量案铺。

The alphabetize method could have been reimplemented to use streams, but a stream-based alphabetize method would have been less clear, more difficult to write correctly, and probably slower. These deficiencies result from Java’s lack of support for primitive char streams (which is not to imply that Java should have supported char streams; it would have been infeasible to do so). To demonstrate the hazards of processing char values with streams, consider the following code:

本來可以重新實現(xiàn)字母順序方法來使用流蔬芥,但是基于流的字母順序方法就不那么清晰了,更難于正確地編寫控汉,而且可能會更慢笔诵。這些缺陷是由于 Java 不支持基本字符流(這并不意味著 Java 應(yīng)該支持字符流;這樣做是不可行的)姑子。要演示使用流處理 char 值的危害乎婿,請考慮以下代碼:

"Hello world!".chars().forEach(System.out::print);

You might expect it to print Hello world!, but if you run it, you’ll find that it prints 721011081081113211911111410810033. This happens because the elements of the stream returned by "Hello world!".chars() are not char values but int values, so the int overloading of print is invoked. It is admittedly confusing that a method named chars returns a stream of int values. You could fix the program by using a cast to force the invocation of the correct overloading:

你可能希望它打印 Hello world!,但如果運行它壁酬,你會發(fā)現(xiàn)它打印 721011081081113211911111410810033次酌。這是因為 "Hello world!".chars() 返回的流元素不是 char 值恨课,而是 int 值舆乔,因此調(diào)用了 print 的 int 重載。無可否認剂公,一個名為 chars 的方法返回一個 int 值流是令人困惑的希俩。你可以通過強制調(diào)用正確的重載來修復(fù)程序:

"Hello world!".chars().forEach(x -> System.out.print((char) x));

but ideally you should refrain from using streams to process char values. When you start using streams, you may feel the urge to convert all your loops into streams, but resist the urge. While it may be possible, it will likely harm the readability and maintainability of your code base. As a rule, even moderately complex tasks are best accomplished using some combination of streams and iteration, as illustrated by the Anagrams programs above. So refactor existing code to use streams and use them in new code only where it makes sense to do so.

但理想情況下,你應(yīng)該避免使用流來處理 char 值纲辽。當你開始使用流時颜武,你可能會有將所有循環(huán)轉(zhuǎn)換為流的沖動,但是要抵制這種沖動拖吼。雖然這是可實施的鳞上,但它可能會損害代碼庫的可讀性和可維護性。通常吊档,即使是中等復(fù)雜的任務(wù)篙议,也最好使用流和迭代的某種組合來完成,如上面的 Anagrams 程序所示怠硼。因此鬼贱,重構(gòu)現(xiàn)有代碼或是在新代碼中,都應(yīng)該在合適的場景使用流香璃。

As shown in the programs in this item, stream pipelines express repeated computation using function objects (typically lambdas or method references), while iterative code expresses repeated computation using code blocks. There are some things you can do from code blocks that you can’t do from function objects:

如本項中的程序所示这难,流管道使用函數(shù)對象(通常是 lambda 表達式或方法引用)表示重復(fù)計算,而迭代代碼使用代碼塊表示重復(fù)計算葡秒。有些事情你可以對代碼塊做姻乓,而你不能對函數(shù)對象做:

  • From a code block, you can read or modify any local variable in scope; from a lambda, you can only read final or effectively final variables [JLS 4.12.4], and you can’t modify any local variables.

從代碼塊中嵌溢,可以讀取或修改作用域中的任何局部變量;在 lambda 表達式中蹋岩,只能讀取 final 或有效的 final 變量 [JLS 4.12.4]堵腹,不能修改任何局部變量。

  • From a code block, you can return from the enclosing method, break or continue an enclosing loop, or throw any checked exception that this method is declared to throw; from a lambda you can do none of these things.

從代碼塊中星澳,可以從封閉方法返回疚顷、中斷或繼續(xù)封閉循環(huán),或拋出聲明要拋出的任何已檢查異常禁偎;在 lambda 表達式中腿堤,你不能做這些事情。

If a computation is best expressed using these techniques, then it’s probably not a good match for streams. Conversely, streams make it very easy to do some things:

如果使用這些技術(shù)最好地表達計算如暖,那么它可能不適合流笆檀。相反,流使做一些事情變得非常容易:

  • Uniformly transform sequences of elements

元素序列的一致變換

  • Filter sequences of elements

過濾元素序列

  • Combine sequences of elements using a single operation (for example to add them, concatenate them, or compute their minimum)

使用單個操作組合元素序列(例如添加它們盒至、連接它們或計算它們的最小值)

  • Accumulate sequences of elements into a collection, perhaps grouping them by some common attribute

將元素序列累積到一個集合中酗洒,可能是按某個公共屬性對它們進行分組

  • Search a sequence of elements for an element satisfying some criterion

在元素序列中搜索滿足某些條件的元素

If a computation is best expressed using these techniques, then it is a good candidate for streams.

如果使用這些技術(shù)能夠最好地表達計算,那么它就是流的一個很好的使用場景枷遂。

One thing that is hard to do with streams is to access corresponding elements from multiple stages of a pipeline simultaneously: once you map a value to some other value, the original value is lost. One workaround is to map each value to a pair object containing the original value and the new value, but this is not a satisfying solution, especially if the pair objects are required for multiple stages of a pipeline. The resulting code is messy and verbose, which defeats a primary purpose of streams. When it is applicable, a better workaround is to invert the mapping when you need access to the earlier-stage value.

使用流很難做到的一件事是從管道的多個階段同時訪問相應(yīng)的元素:一旦你將一個值映射到另一個值樱衷,原始值就會丟失。一個解決方案是將每個值映射到包含原始值和新值的 pair 對象酒唉,但這不是一個令人滿意的解決方案矩桂,特別是如果管道的多個階段都需要 pair 對象的話。生成的代碼混亂而冗長痪伦,這違背了流的主要目的侄榴。當它適用時,更好的解決方案是在需要訪問早期階段值時反轉(zhuǎn)映射网沾。

For example, let’s write a program to print the first twenty Mersenne primes. To refresh your memory, a Mersenne number is a number of the form 2p ? 1. If p is prime, the corresponding Mersenne number may be prime; if so, it’s a Mersenne prime. As the initial stream in our pipeline, we want all the prime numbers. Here’s a method to return that (infinite) stream. We assume a static import has been used for easy access to the static members of BigInteger:

例如癞蚕,讓我們編寫一個程序來打印前 20 個 Mersenne 素數(shù)。刷新你的記憶,一個 Mersenne 素數(shù)的數(shù)量是一個數(shù)字形式 2p ? 1辉哥。如果 p 是素數(shù)桦山,對應(yīng)的 Mersenne 數(shù)可以是素數(shù);如果是的話证薇,這就是 Mersenne 素數(shù)度苔。作為管道中的初始流,我們需要所有質(zhì)數(shù)浑度。這里有一個返回(無限)流的方法寇窑。我們假設(shè)已經(jīng)使用靜態(tài)導(dǎo)入來方便地訪問 BigInteger 的靜態(tài)成員:

static Stream<BigInteger> primes() {
    return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}

The name of the method (primes) is a plural noun describing the elements of the stream. This naming convention is highly recommended for all methods that return streams because it enhances the readability of stream pipelines. The method uses the static factory Stream.iterate, which takes two parameters: the first element in the stream, and a function to generate the next element in the stream from the previous one. Here is the program to print the first twenty Mersenne primes:

方法的名稱(素數(shù))是描述流元素的復(fù)數(shù)名詞。強烈推薦所有返回流的方法使用這種命名約定箩张,因為它增強了流管道的可讀性甩骏。該方法使用靜態(tài)工廠 Stream.iterate窗市,它接受兩個參數(shù):流中的第一個元素和一個函數(shù),用于從前一個元素生成流中的下一個元素饮笛。下面是打印前 20 個 Mersenne 素數(shù)的程序:

public static void main(String[] args) {
    primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
    .filter(mersenne -> mersenne.isProbablePrime(50))
    .limit(20)
    .forEach(System.out::println);
}

This program is a straightforward encoding of the prose description above: it starts with the primes, computes the corresponding Mersenne numbers, filters out all but the primes (the magic number 50 controls the probabilistic primality test), limits the resulting stream to twenty elements, and prints them out.

這個程序是上述散文描述的一個簡單編碼:它從素數(shù)開始咨察,計算相應(yīng)的 Mersenne 數(shù),過濾掉除素數(shù)以外的所有素數(shù)(魔法數(shù)字 50 控制概率素數(shù)測試)福青,將結(jié)果流限制為 20 個元素摄狱,并打印出來。

Now suppose that we want to precede each Mersenne prime with its exponent (p). This value is present only in the initial stream, so it is inaccessible in the terminal operation, which prints the results. Luckily, it’s easy to compute the exponent of a Mersenne number by inverting the mapping that took place in the first intermediate operation. The exponent is simply the number of bits in the binary representation, so this terminal operation generates the desired result:

現(xiàn)在假設(shè)我們想要在每個 Mersenne 素數(shù)之前加上它的指數(shù) (p)无午,這個值只在初始流中存在媒役,因此在輸出結(jié)果的終端操作中是不可訪問的。幸運的是宪迟,通過對第一個中間操作中發(fā)生的映射求逆酣衷,可以很容易地計算出 Mersenne 數(shù)的指數(shù)。指數(shù)僅僅是二進制表示的比特數(shù)次泽,因此這個終端操作產(chǎn)生了想要的結(jié)果:

.forEach(mp -> System.out.println(mp.bitLength() + ": " + mp));

There are plenty of tasks where it is not obvious whether to use streams or iteration. For example, consider the task of initializing a new deck of cards. Assume that Card is an immutable value class that encapsulates a Rank and a Suit, both of which are enum types. This task is representative of any task that requires computing all the pairs of elements that can be chosen from two sets. Mathematicians call this the Cartesian product of the two sets. Here’s an iterative implementation with a nested for-each loop that should look very familiar to you:

在許多任務(wù)中穿仪,使用流還是迭代并不明顯。例如意荤,考慮初始化一副新紙牌的任務(wù)啊片。假設(shè) Card 是一個不可變的值類,它封裝了 Rank 和 Suit袭异,它們都是 enum 類型钠龙。此任務(wù)代表需要計算可從兩個集合中選擇的所有元素對的任何任務(wù)。數(shù)學(xué)家稱之為這兩個集合的笛卡爾積御铃。下面是一個嵌套 for-each 循環(huán)的迭代實現(xiàn),你應(yīng)該非常熟悉它:

// Iterative Cartesian product computation
private static List<Card> newDeck() {
    List<Card> result = new ArrayList<>();
    for (Suit suit : Suit.values())
    for (Rank rank : Rank.values())
    result.add(new Card(suit, rank));
    return result;
}

And here is a stream-based implementation that makes use of the intermediate operation flatMap. This operation maps each element in a stream to a stream and then concatenates all of these new streams into a single stream (or flattens them). Note that this implementation contains a nested lambda, shown in boldface:

這是一個基于流的實現(xiàn)沈矿,它使用了中間操作 flatMap上真。此操作將流中的每個元素映射到流,然后將所有這些新流連接到單個流中(或?qū)⑵浔馄交└拧W⒁馑ィ@個實現(xiàn)包含一個嵌套 lambda 表達式,用粗體顯示:

// Stream-based Cartesian product computation
private static List<Card> newDeck() {
    return Stream.of(Suit.values())
    .flatMap(suit ->Stream.of(Rank.values())
    .map(rank -> new Card(suit, rank)))
    .collect(toList());
}

Which of the two versions of newDeck is better? It boils down to personal preference and the environment in which you’re programming. The first version is simpler and perhaps feels more natural. A larger fraction of Java programmers will be able to understand and maintain it, but some programmers will feel more comfortable with the second (stream-based) version. It’s a bit more concise and not too difficult to understand if you’re reasonably well-versed in streams and functional programming. If you’re not sure which version you prefer, the iterative version is probably the safer choice. If you prefer the stream version and you believe that other programmers who will work with the code will share your preference, then you should use it.

兩個版本的 newDeck 哪個更好陵像?這可以歸結(jié)為個人偏好和編程環(huán)境就珠。第一個版本更簡單,可能感覺更自然醒颖。大部分 Java 程序員將能夠理解和維護它妻怎,但是一些程序員將對第二個(基于流的)版本感到更舒服。如果你相當精通流和函數(shù)式編程泞歉,那么它會更簡潔逼侦,也不會太難理解匿辩。如果你不確定你更喜歡哪個版本,迭代版本可能是更安全的選擇榛丢。如果你更喜歡流版本铲球,并且相信與代碼一起工作的其他程序員也會分享你的偏好,那么你應(yīng)該使用它晰赞。

In summary, some tasks are best accomplished with streams, and others with iteration. Many tasks are best accomplished by combining the two approaches. There are no hard and fast rules for choosing which approach to use for a task, but there are some useful heuristics. In many cases, it will be clear which approach to use; in some cases, it won’t. If you’re not sure whether a task is better served by streams or iteration, try both and see which works better.

總之稼病,有些任務(wù)最好使用流來完成,有些任務(wù)最好使用迭代來完成掖鱼。許多任務(wù)最好通過結(jié)合這兩種方法來完成溯饵。對于選擇任務(wù)使用哪種方法,沒有硬性的規(guī)則锨用,但是有一些有用的啟發(fā)丰刊。在許多情況下,使用哪種方法是清楚的增拥;在某些情況下很難決定啄巧。如果你不確定一個任務(wù)是通過流還是通過迭代更好地完成,那么同時嘗試這兩種方法掌栅,看看哪一種更有效秩仆。


Back to contents of the chapter(返回章節(jié)目錄)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市猾封,隨后出現(xiàn)的幾起案子澄耍,更是在濱河造成了極大的恐慌,老刑警劉巖晌缘,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齐莲,死亡現(xiàn)場離奇詭異,居然都是意外死亡磷箕,警方通過查閱死者的電腦和手機选酗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岳枷,“玉大人芒填,你說我怎么就攤上這事】辗保” “怎么了殿衰?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盛泡。 經(jīng)常有香客問我闷祥,道長,這世上最難降的妖魔是什么饭于? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任蜀踏,我火速辦了婚禮维蒙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘果覆。我一直安慰自己颅痊,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布局待。 她就那樣靜靜地躺著斑响,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钳榨。 梳的紋絲不亂的頭發(fā)上舰罚,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音薛耻,去河邊找鬼营罢。 笑死,一個胖子當著我的面吹牛饼齿,可吹牛的內(nèi)容都是我干的饲漾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缕溉,長吁一口氣:“原來是場噩夢啊……” “哼考传!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起证鸥,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤僚楞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后枉层,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泉褐,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年返干,在試婚紗的時候發(fā)現(xiàn)自己被綠了兴枯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡矩欠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悠夯,到底是詐尸還是另有隱情癌淮,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布沦补,位于F島的核電站乳蓄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏夕膀。R本人自食惡果不足惜虚倒,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一美侦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧魂奥,春花似錦菠剩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哈蝇,卻和暖如春棺妓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炮赦。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工怜跑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吠勘。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓性芬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親看幼。 傳聞我的和親對象是個殘疾皇子批旺,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容