重走Java基礎(chǔ)之Streams(四)

來源:重走Java基礎(chǔ)之Streams(四)
作者:知秋
博客:一葉知秋
轉(zhuǎn)載請注明來源和作者狐蜕!


接上篇重走Java基礎(chǔ)之Streams(三)

使用Map.Entry的流在轉(zhuǎn)換后保留初始值

當(dāng)你有一個Stream痢法,你需要映射轉(zhuǎn)換但是想保留初始值,你可以使用下面的實用程序方法將'Stream`映射到Map.Entry:

public static <K, V> Function<K, Map.Entry<K, V>> 
entryMapper(Function<K, V> mapper)
{
return (k)->new AbstractMap.SimpleEntry<>(k, mapper.apply(k));
}

然后你可以使用你的有權(quán)訪問原始值和映射轉(zhuǎn)換后值的轉(zhuǎn)換器來處理Streams:

Set<K> mySet;
Function<K, V> transformer = SomeClass::transformerMethod;
Stream<Map.Entry<K, V>> entryStream = mySet.stream()
    .map(entryMapper(transformer));

然后卫枝,您可以繼續(xù)正常處理Stream。 這避免了創(chuàng)建中間集合的開銷。

將迭代器轉(zhuǎn)換為流

Iterator<String> iterator = Arrays.asList("A", "B", "C").iterator();    
Iterable<String> iterable = () -> iterator;
Stream<String> stream = StreamSupport.stream(iterable.spliterator(), false);

基于流來創(chuàng)建一個map

沒有重復(fù)鍵的簡單情況

Stream<String> characters = Stream.of("A", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(element -> element.hashCode(), element -> element));
// map = {65=A, 66=B, 67=C}

可能存在重復(fù)鍵的情況

Collectors.toMapjavadoc的描述:

如果映射的鍵包含重復(fù)的(根據(jù)Object.equals(Object))璧帝,則會在執(zhí)行收集操作時會拋出IllegalStateException夺艰。 如果映射的鍵可能有重復(fù)芋哭,請使用toMap(Function,F(xiàn)unction郁副,BinaryOperator)减牺。

Stream<String> characters = Stream.of("A", "B", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(
                element -> element.hashCode(),
                element -> element,
                (existingVal, newVal) -> (existingVal + newVal)));

// map = {65=A, 66=BB, 67=C}

傳遞給Collectors.toMap(...)BinaryOperator生成在發(fā)生重復(fù)沖突情況下要存儲的值。 它可以:

  • 返回舊值存谎,以流中的第一個值優(yōu)先拔疚,
  • 返回新值,以流中的最后一個值優(yōu)先既荚,
  • 組合舊值和新值

按值分組

當(dāng)你需要執(zhí)行等效的一個數(shù)據(jù)庫級聯(lián)“group by”操作(意思就是和此效果一樣的需求)時你可以使用 Collectors.groupingBy 稚失。 為了說明,以下內(nèi)容創(chuàng)建了一個map恰聘,其中人們的姓名分別映射到姓氏:

List<Person> people = Arrays.asList(
    new Person("Sam", "Rossi"),
    new Person("Sam", "Verdi"),
    new Person("John", "Bianchi"),
    new Person("John", "Rossi"),
    new Person("John", "Verdi")
);

Map<String, List<String>> map = people.stream()
        .collect(
                // function mapping input elements to keys
                Collectors.groupingBy(Person::getName, 
                // function mapping input elements to values,
                // how to store values
                Collectors.mapping(Person::getSurname, Collectors.toList()))
        );

// map = {John=[Bianchi, Rossi, Verdi], Sam=[Rossi, Verdi]}

Live on Ideone

查找有關(guān)數(shù)值流的統(tǒng)計信息

Java 8提供了IntSummaryStatistics句各,DoubleSummaryStatisticsLongSummaryStatistics這些類,它們給出用于收集統(tǒng)計數(shù)據(jù)對象的狀態(tài)憨琳,例如count诫钓,minmax篙螟,sumaverage菌湃。

Java SE 8

List naturalNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = naturalNumbers.stream()
.mapToInt((x) -> x)     
.summaryStatistics();
System.out.println(stats);

運行結(jié)果如下:

Java SE 8

IntSummaryStatistics{count=10, sum=55, min=1, max=10, average=5.500000}

可能還有疑問,還是來張運行截圖吧:

獲取一個流的片段

skip: 返回一個丟棄原Stream的前N個元素后剩下元素組成的新Stream遍略,如果原Stream中包含的元素個數(shù)小于N惧所,那么返回空Stream骤坐;


limit: 對一個Stream進(jìn)行截斷操作,獲取其前N個元素下愈,如果原Stream中包含的元素個數(shù)小于N纽绍,那就獲取其所有的元素;

Example:獲取一個包含30個元素的“Stream”势似,包含集合的第21到第50個(包含)元素拌夏。

final long n = 20L; // the number of elements to skip
final long maxSize = 30L; // the number of elements the stream should be limited to
final Stream<T> slice = collection.stream().skip(n).limit(maxSize);

Notes:

  • 如果n為負(fù)或maxSize為負(fù),則拋出IllegalArgumentException
  • skip(long)limit(long)都是中間操作
  • 如果流包含少于n個元素履因,則skip(n)將返回一個空流
  • skip(long)limit(long)都是順序流管道上的廉價操作障簿,但在有序并行管道上可能相當(dāng)昂貴(指性能上)

再貼個例子:

List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
System.out.println(“sum is:”+nums.stream()
.filter(num -> num != null
.distinct()
.mapToInt(num -> num * 2)
.peek(System.out::println)
.skip(2)
.limit(4)
.sum());

Joining a stream to a single String

一個經(jīng)常遇到的用例是從流創(chuàng)建一個String,其中每個流轉(zhuǎn)換出的字符串之間由一個特定的字符分隔栅迄。 Collectors.joining()方法可以用于這個站故,就像下面的例子:

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

String result = fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", "));
           
System.out.println(result);

Output:

APPLE, BANANA, ORANGE, PEAR

Collectors.joining()方法也可以滿足前綴和后綴:

String result = fruitStream.filter(s -> s.contains("e"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", ", "Fruits: ", "."));
           
System.out.println(result);

Output:

Fruits: APPLE, ORANGE, PEAR.

Live on Ideone

Reduction(聚合) with Streams

聚合是將二進(jìn)制操作應(yīng)用于流的每個元素以產(chǎn)生一個值的過程。

IntStreamsum()方法是一個簡化的例子; 它對流的每個項應(yīng)用加法毅舆,得到一個最終值:

這相當(dāng)于(((1+2)+3)+4)

Stream的reduce方法允許創(chuàng)建自定義reduction西篓。 可以使用reduce方法來實現(xiàn)sum()方法:

IntStream istr;
    
//Initialize istr
    
OptionalInt istr.reduce((a,b)->a+b);

返回Optional對象,以便可以恰當(dāng)?shù)靥幚砜盏腟treams憋活。

reduction的另一個示例是將 Stream<LinkedList<T>>組合成單個 LinkedList<T>

Stream<LinkedList<T>> listStream;
    
//Create a Stream<LinkedList<T>>
    
Optional<LinkedList<T>> bigList = listStream.reduce((LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

您還可以提供* identity元素*岂津。 例如,用于加法的標(biāo)識元素為0余掖,如x + 0 == x寸爆。 對于乘法,identity元素為1盐欺,如x * 1 == x。 在上面的例子中仅醇,identity元素是一個空的LinkedList冗美,因為如果你將一個空列表添加到另一個列表,你“添加”的列表不會改變:

Stream<LinkedList<T>> listStream;

//Create a Stream<LinkedList<T>>

LinkedList<T> bigList = listStream.reduce(new LinkedList<T>(), (LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

注意析二,當(dāng)提供一個identity元素時粉洼,返回值不會被包裝在一個Optional中 ---- 如果在空流上調(diào)用丢早,reduce()將返回identity元素辜限。

二元運算符也必須是* associative *阅仔,意思是 (a+b)+c==a+(b+c)酷誓。 這是因為元件可以以任何順序進(jìn)行聚合操作(reduced)慷垮。 例如往枷,可以如下執(zhí)行上述加法reduction:

這個reduction(聚合操作)等同于寫((1+2)+(3+4))蜂莉。 關(guān)聯(lián)性的屬性還允許Java并行地reduction Stream - 每個處理器可以reduction Stream的一部分并得到結(jié)果碎罚,最后通過reduction結(jié)合每個處理器的結(jié)果会傲。

使用流排序

List<String> data = new ArrayList<>();
data.add("Sydney");
data.add("London");
data.add("New York");
data.add("Amsterdam");
data.add("Mumbai");
data.add("California");

System.out.println(data);

List<String> sortedData = data.stream().sorted().collect(Collectors.toList());

System.out.println(sortedData);

Output:

[Sydney, London, New York, Amsterdam, Mumbai, California]
[Amsterdam, California, London, Mumbai, New York, Sydney]

它也可以使用不同的比較機(jī)制锅棕,因為有一個重載sorted版本拙泽,它使用比較器作為其參數(shù)。

此外裸燎,您可以使用lambda表達(dá)式進(jìn)行排序:

List<String> sortedData2 = data.stream().sorted((s1,s2) -> s2.compareTo(s1)).collect(Collectors.toList());

這將輸出[Sydney, New York, Mumbai, London, California, Amsterdam]

你可以使用Comparator.reverseOrder() 顾瞻,一個對自然排序進(jìn)行強(qiáng)行reverse的比較器(反排序)。

List<String> reverseSortedData = data.stream().sorted(Comparator.reverseOrder()

流操作類別

流操作分為兩個主要類別德绿,中間和終端操作荷荤,以及兩個子類,無狀態(tài)和有狀態(tài)移稳。


中間操作:

一個中間操作總是* lazy *(延遲執(zhí)行)梅猿,例如一個簡單的“Stream.map”。 它不會被調(diào)用秒裕,直到流實際上消耗袱蚓。 這可以很容易地驗證:

Arrays.asList(1, 2 ,3).stream().map(i -> {
    throw new RuntimeException("not gonna happen");
    return i;
});

中間操作是流的常見構(gòu)造塊,指在數(shù)據(jù)源之后操作鏈几蜻,并且通常末端跟隨有觸發(fā)流鏈?zhǔn)綀?zhí)行的終端操作喇潘。


終端操作

終端操作是觸發(fā)流的消耗的。 一些最常見的是 Stream.forEach或“ Stream.collect梭稚。 它們通常放置在一系列中間操作之后颖低,幾乎總是* eager *。


無狀態(tài)操作

無狀態(tài)意味著每個環(huán)節(jié)(可以理解成流的每個處理環(huán)節(jié))在沒有其他環(huán)節(jié)的上下文的情況下被處理弧烤。 無狀態(tài)操作允許流的存儲器高效處理忱屑。 像Stream.map和Stream.filter這樣的不需要關(guān)于流的其他環(huán)節(jié)的信息的操作被認(rèn)為是無狀態(tài)的。


狀態(tài)操作

狀態(tài)性意味著對每個環(huán)節(jié)的操作取決于(一些)流的其他環(huán)節(jié)暇昂。 這需要保留一個狀態(tài)莺戒。 狀態(tài)操作可能會與長流或無限流斷開。 像Stream.sorted 這樣的操作要求在處理任何環(huán)節(jié)之前處理整個流急波,這將在足夠長的流的環(huán)節(jié)中斷開从铲。 這可以通過長流(run at your own risk)來證明(說的太拗口了,其實就是棧的遞歸操作澄暮,下一步的運行依靠上一步的結(jié)果來執(zhí)行名段,假如太深,就可能出現(xiàn)問題泣懊,看下面例子就知道了):

// works - stateless stream
long BIG_ENOUGH_NUMBER = 999999999;
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).forEach(System.out::println);

這將導(dǎo)致由于Stream.sorted的狀態(tài)的內(nèi)存不足:

// Out of memory - stateful stream
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).sorted().forEach(System.out::println);

原始流

Java為三種類型的原語“IntStream”(用于ints)伸辟,LongStream(用于longs)和DoubleStream(用于doubles)提供專用的Stream。 除了是針對它們各自的原語的優(yōu)化實現(xiàn)馍刮,它們還提供了幾個特定的終端方法信夫,通常用于數(shù)學(xué)運算。 例如:

IntStream is = IntStream.of(10, 20, 30);
double average = is.average().getAsDouble(); // average is 20.0

將流的結(jié)果收集到數(shù)組中

可以通過Stream.toArray()方法獲得一個數(shù)組:

List<String> fruits = Arrays.asList("apple", "banana", "pear", "kiwi", "orange");

String[] filteredFruits = fruits.stream()
    .filter(s -> s.contains("a"))
    .toArray(String[]::new);     

// prints: [apple, banana, pear, orange]
System.out.println(Arrays.toString(filteredFruits));

String[]::new是一種特殊的方法引用:構(gòu)造函數(shù)引用。

查找匹配條件的第一個元素

可以找到符合條件的Stream 的第一個元素忙迁。

在這個例子中脐彩,我們將找到第一個平方超過了50000的Integer

IntStream.iterate(1, i -> i + 1) // Generate an infinite stream 1,2,3,4...
    .filter(i -> (i*i) > 50000) // Filter to find elements where the square is >50000
    .findFirst(); // Find the first filtered element

這個表達(dá)式將返回一個帶有結(jié)果的OptionalInt對象姊扔。

注意惠奸,使用無限的Stream,Java將繼續(xù)檢查每個元素恰梢,直到找到一個結(jié)果佛南。 在一個有限的Stream,如果Java運行檢查了所以元素嵌言,但仍然找不到一個結(jié)果嗅回,它返回一個空的OptionalInt對象。

使用Streams生成隨機(jī)字符串

有時摧茴,創(chuàng)建隨機(jī)的Strings有時是有用的绵载,或許作為Web服務(wù)的會話ID或在注冊應(yīng)用程序后的初始密碼。 這可以很容易地使用Streams實現(xiàn)苛白。

首先娃豹,我們需要初始化一個隨機(jī)數(shù)生成器。 為了增強(qiáng)生成的Strings的安全性购裙,使用SecureRandom是一個好主意懂版。

Note:創(chuàng)建一個SecureRandom是相當(dāng)消耗資源的,所以最好的做法是只做一次躏率,并且不時地調(diào)用它的一個setSeed()方法來重新設(shè)置它躯畴。

private static final SecureRandom rng = new SecureRandom(SecureRandom.generateSeed(20)); 
//20 Bytes as a seed is rather arbitrary, it is the number used in the JavaDoc example

當(dāng)創(chuàng)建隨機(jī)的String時,我們通常希望它們只使用某些字符(例如薇芝,只有字母和數(shù)字)蓬抄。 因此,我們可以創(chuàng)建一個返回一個boolean的方法恩掷,稍后可以用它來過濾Stream倡鲸。

//returns true for all chars in 0-9, a-z and A-Z
boolean useThisCharacter(char c){
    //check for range to avoid using all unicode Letter (e.g. some chinese symbols)
    return c >= '0' && c <= 'z' && Character.isLetterOrDigit(c);
}

接下來,我們可以使用RNG生成一個特定長度的隨機(jī)字符串黄娘,包含通過我們的 useThisCharacter檢查的字符集。

public String generateRandomString(long length){
    //Since there is no native CharStream, we use an IntStream instead
    //and convert it to a Stream<Character> using mapToObj.
    //We need to specify the boundaries for the int values to ensure they can safely be cast to char
    Stream<Character> randomCharStream = rng
    .ints(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT)
    .mapToObj(i -> (char)i).filter(c -> this::useThisCharacter)
    .limit(length);

    //now we can use this Stream to build a String utilizing the collect method.
    String randomString = randomCharStream
    .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
    .toString();
    return randomString;
}

關(guān)于Stream系列暫時完結(jié)

部分參考示圖源自:http://ifeve.com/stream/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末克滴,一起剝皮案震驚了整個濱河市逼争,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌劝赔,老刑警劉巖誓焦,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡杂伟,警方通過查閱死者的電腦和手機(jī)移层,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赫粥,“玉大人观话,你說我怎么就攤上這事≡狡剑” “怎么了频蛔?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秦叛。 經(jīng)常有香客問我晦溪,道長,這世上最難降的妖魔是什么挣跋? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任三圆,我火速辦了婚禮,結(jié)果婚禮上避咆,老公的妹妹穿的比我還像新娘舟肉。我一直安慰自己,他們只是感情好牌借,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布度气。 她就那樣靜靜地躺著,像睡著了一般膨报。 火紅的嫁衣襯著肌膚如雪磷籍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天现柠,我揣著相機(jī)與錄音院领,去河邊找鬼。 笑死够吩,一個胖子當(dāng)著我的面吹牛比然,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播周循,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼强法,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了湾笛?” 一聲冷哼從身側(cè)響起饮怯,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嚎研,沒想到半個月后蓖墅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年论矾,在試婚紗的時候發(fā)現(xiàn)自己被綠了教翩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡贪壳,死狀恐怖饱亿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寥袭,我是刑警寧澤路捧,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站传黄,受9級特大地震影響杰扫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膘掰,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一章姓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧识埋,春花似錦凡伊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惠豺,卻和暖如春银还,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洁墙。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工蛹疯, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人热监。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓捺弦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親孝扛。 傳聞我的和親對象是個殘疾皇子列吼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)苦始,斷路器冈欢,智...
    卡卡羅2017閱讀 134,696評論 18 139
  • 本文采用實例驅(qū)動的方式,對JAVA8的stream API進(jìn)行一個深入的介紹盈简。雖然JAVA8中的stream AP...
    浮梁翁閱讀 25,770評論 3 50
  • 來源:重走Java基礎(chǔ)之Streams(二)作者:知秋(極樂科技知乎專欄原創(chuàng)作者)博客:一葉知秋 接上篇重走Jav...
    極樂君閱讀 519評論 0 2
  • 第一章 為什么要關(guān)心Java 8 使用Stream庫來選擇最佳低級執(zhí)行機(jī)制可以避免使用Synchronized(同...
    謝隨安閱讀 1,494評論 0 4
  • 來源:重走Java基礎(chǔ)之Streams(三)作者:知秋(極樂科技知乎專欄原創(chuàng)作者)博客:一葉知秋 接重走Java基...
    極樂君閱讀 6,138評論 0 3