來源:重走Java基礎(chǔ)之Streams(四)
作者:知秋
博客:一葉知秋
轉(zhuǎn)載請注明來源和作者狐蜕!
使用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)換器來處理Stream
s:
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.toMap
在javadoc的描述:
如果映射的鍵包含重復(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]}
查找有關(guān)數(shù)值流的統(tǒng)計信息
Java 8提供了IntSummaryStatistics
句各,DoubleSummaryStatistics
和 LongSummaryStatistics
這些類,它們給出用于收集統(tǒng)計數(shù)據(jù)對象的狀態(tài)憨琳,例如count
诫钓,min
,max
篙螟,sum
和average
菌湃。
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.
Reduction(聚合) with Streams
聚合是將二進(jìn)制操作應(yīng)用于流的每個元素以產(chǎn)生一個值的過程。
IntStream
的sum()
方法是一個簡化的例子; 它對流的每個項應(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”(用于int
s)伸辟,LongStream
(用于long
s)和DoubleStream
(用于double
s)提供專用的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)用程序后的初始密碼。 這可以很容易地使用Stream
s實現(xiàn)苛白。
首先娃豹,我們需要初始化一個隨機(jī)數(shù)生成器。 為了增強(qiáng)生成的String
s的安全性购裙,使用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/