來源:重走Java基礎之Streams(四)
作者:知秋
博客:一葉知秋
轉(zhuǎn)載請注明來源和作者!
使用Map.Entry的流在轉(zhuǎn)換后保留初始值
當你有一個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));
}
然后你可以使用你的有權訪問原始值和映射轉(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
沒有重復鍵的簡單情況
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}
可能存在重復鍵的情況
Collectors.toMap
在javadoc的描述:
如果映射的鍵包含重復的(根據(jù)Object.equals(Object)),則會在執(zhí)行收集操作時會拋出IllegalStateException。 如果映射的鍵可能有重復,請使用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ā)生重復沖突情況下要存儲的值。 它可以:
- 返回舊值,以流中的第一個值優(yōu)先,
- 返回新值,以流中的最后一個值優(yōu)先,
- 組合舊值和新值
按值分組
當你需要執(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]}
查找有關數(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);
運行結果如下:
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進行截斷操作,獲取其前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
為負或maxSize
為負,則拋出IllegalArgumentException
-
skip(long)
和limit(long)
都是中間操作 - 如果流包含少于n個元素,則skip(n)將返回一個空流
-
skip(long)
和limit(long)
都是順序流管道上的廉價操作,但在有序并行管道上可能相當昂貴(指性能上)
再貼個例子:
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
聚合是將二進制操作應用于流的每個元素以產(chǎn)生一個值的過程。
IntStream
的sum()
方法是一個簡化的例子; 它對流的每個項應用加法,得到一個最終值:
這相當于(((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
對象,以便可以恰當?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元素*。 例如,用于加法的標識元素為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;
});
注意,當提供一個identity元素時,返回值不會被包裝在一個Optional
中 ---- 如果在空流上調(diào)用,reduce()
將返回identity元素。
二元運算符也必須是* associative *,意思是 (a+b)+c==a+(b+c)
。 這是因為元件可以以任何順序進行聚合操作(reduced)。 例如,可以如下執(zhí)行上述加法reduction:
這個reduction(聚合操作)等同于寫((1+2)+(3+4))
。 關聯(lián)性的屬性還允許Java并行地reduction Stream
- 每個處理器可以reduction Stream的一部分并得到結果,最后通過reduction結合每個處理器的結果。
使用流排序
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]
它也可以使用不同的比較機制,因為有一個重載sorted
版本,它使用比較器作為其參數(shù)。
此外,您可以使用lambda表達式進行排序:
List<String> sortedData2 = data.stream().sorted((s1,s2) -> s2.compareTo(s1)).collect(Collectors.toList());
這將輸出[Sydney, New York, Mumbai, London, California, Amsterdam]
你可以使用Comparator.reverseOrder()
,一個對自然排序進行強行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;
});
中間操作是流的常見構造塊,指在數(shù)據(jù)源之后操作鏈,并且通常末端跟隨有觸發(fā)流鏈式執(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這樣的不需要關于流的其他環(huán)節(jié)的信息的操作被認為是無狀態(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)來證明(說的太拗口了,其實就是棧的遞歸操作,下一步的運行依靠上一步的結果來執(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);
這將導致由于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ù)學運算。 例如:
IntStream is = IntStream.of(10, 20, 30);
double average = is.average().getAsDouble(); // average is 20.0
將流的結果收集到數(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
是一種特殊的方法引用:構造函數(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
這個表達式將返回一個帶有結果的OptionalInt
對象。
注意,使用無限的Stream
,Java將繼續(xù)檢查每個元素,直到找到一個結果。 在一個有限的Stream
,如果Java運行檢查了所以元素,但仍然找不到一個結果,它返回一個空的OptionalInt
對象。
使用Streams生成隨機字符串
有時,創(chuàng)建隨機的Strings
有時是有用的,或許作為Web服務的會話ID或在注冊應用程序后的初始密碼。 這可以很容易地使用Stream
s實現(xiàn)。
首先,我們需要初始化一個隨機數(shù)生成器。 為了增強生成的String
s的安全性,使用SecureRandom
是一個好主意。
Note:創(chuàng)建一個SecureRandom
是相當消耗資源的,所以最好的做法是只做一次,并且不時地調(diào)用它的一個setSeed()
方法來重新設置它。
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
當創(chuàng)建隨機的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生成一個特定長度的隨機字符串,包含通過我們的 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;
}
關于Stream系列暫時完結
部分參考示圖源自:http://ifeve.com/stream/