1. 流的兩個重要特點:流水線、內部迭代
<1> 流水線:
很多流操作本身會返回一個流,多個這樣的操作鏈接起來,形成一個大的流水線。
<2> 內部迭代:
與使用迭代器顯式迭代的集合不同,流使用內部迭代,即stream庫內部幫你把迭代實現(xiàn)了。
2. 流與集合的兩個重要區(qū)別:結構、迭代
<1> 結構:
流是概念上固定的數(shù)據(jù)結構,其元素是需計算,即只有在需要的時候才計算值。
集合是一個內存中的數(shù)據(jù)結構,它包含數(shù)據(jù)結構中目前所有的值,即集合中的每個元素都得先算出來才能添加到集合中。
舉列:
集合就像DVD里的電影,它完整的保存了整個電影,是在之前已經(jīng)處理完成,再放到DVD里。
流就像網(wǎng)上在線看電影,不是非得把整個電影都下載完成后再來看,而是根據(jù)需要計算下載。
<2> 迭代:
流使用內部迭代,即stream庫內部幫你把迭代做了。
集合使用外部迭代,即需要自己使用Collection接口做外部顯示迭代。
內部迭代的好處:可以自動選擇一種適合硬件的數(shù)據(jù)表示和并行實現(xiàn),即以更優(yōu)化的順序進行處理。


注意:
流只能遍歷一次,遍歷完之后,這個流已經(jīng)被消費掉了。你可以從原始數(shù)據(jù)源那里再獲取一個新的流來重新遍歷一遍。
如下代碼多次遍歷流,導致拋出異常:
Stream<String> stream = Arrays.asList("a", "b", "c").stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);
多次遍歷流,拋出異常:
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.fan.demo.DemoTest.main(DemoTest.java:115)
3. 流的兩類操作:中間操作、終端操作

<1> 中間操作:
流的中間操作不執(zhí)行任何處理,它們會返回一個流,多個中間操作一般都可以合并起來,在終端操作時一次性全部處理。
<2> 終端操作:
終端操作會從流的流水線生成結果,其結果不是流,而是如List、Integer、void的值。
4. 流的使用
構建流:
Stream.of -- 通過顯式值創(chuàng)建一個流(接受任意類型數(shù)量的參數(shù))
Stream<String> stream = Stream.of("Hello World", "James", "Ok");
stream.map(String::toUpperCase).forEach(System.out::println);
輸出:
HELLO WORLD
JAMES
OK
創(chuàng)建一個空流:
Stream<String> emptyStream = Stream.empty();
Arrays.stream -- 通過數(shù)組創(chuàng)建一個流(接受一個數(shù)組作為參數(shù))
int[] numbers = {3, 2, 5, 11, 13, 7};
int sum = Arrays.stream(numbers).sum(); --- 總和41
Files.lines -- 由文件生成流(返回一個由指定文件中的各行構成的字符串流)
Stream.iterate、Stream.generate -- 由函數(shù)生成無限流(應該使用limit(n)來加以限制)
Stream.iterate方法接受一個初始值,實際應用在需要依次生成一系列值的時候,比如一些列日期:1月31日,2月1日
注意:Stream.iterate方法是純粹不變的:它沒有修改現(xiàn)有狀態(tài),在每次迭代時會創(chuàng)建新的元組。
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
輸出:
0
2
4
6
8
使用Stream.iterate構建一個斐波那契數(shù)列:(斐波那契數(shù)列中前兩個數(shù)字是0,1,后續(xù)的每個數(shù)字 = 前兩個數(shù)字之和)
先構建一個斐波那契元組,格式為:(數(shù)列中數(shù)字,后續(xù)數(shù)字),如下:
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]}).limit(10).forEach(t -> System.out.println("(" + t[0] + "," + t[1] + ")"));
輸出:
(0,1)
(1,1)
(1,2)
(2,3)
(3,5)
(5,8)
(8,13)
(13,21)
(21,34)
(34,55)
使用map提取每個元組中的第一個元素,則構建的斐波那契數(shù)列為:
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]}).limit(10).map(t -> t[0]).forEach(System.out::println);
輸出:
0
1
1
2
3
5
8
13
21
34
Stream.generate方法接受一個Supplier<T>類型的Lambda提供新的值
Stream.generate(Math::random).limit(5).forEach(System.out::println);
輸出:
0.7017580650263067
0.5551699003184056
0.17273970322460497
0.5153472880449076
0.8041474317735003
篩選:
filter(謂詞) -- 用謂詞篩選(謂詞:一個返回boolean的函數(shù))
找出所有偶數(shù),并返回列表
List<Integer> numbers = Arrays.asList(8, 9, 100, 200, 8, 18, 23, 100);
List<Integer> evenNumList = numbers.stream().filter(i -> i % 2 == 0).collect(Collectors.toList()); --- [8, 100, 200, 8, 18, 100]
distinct -- 去重(根據(jù)流所生成元素的hashCode和equals方法實現(xiàn)篩選各異元素)
List<Integer> numbers = Arrays.asList(8, 9, 100, 200, 8, 18, 23, 100);
numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
輸出:
8
100
200
18
limit(n) -- 截短流(截取前n個元素)
找出所有偶數(shù),并截取走前2兩個
Stream.of(8, 9, 100, 200, 8, 18, 23, 100).filter(i -> i % 2 == 0).limit(2).forEach(System.out::println);
輸出:
8
100
skip(n) -- 跳過元素(扔掉前n個元素,流中元素不足n個,返回空流)
找出所有偶數(shù),并扔掉前兩個
Stream.of(8, 9, 100, 200, 8, 18, 23, 100).filter(i -> i % 2 == 0).skip(2).forEach(System.out::println);
輸出:
200
8
18
100
映射
map(函數(shù)) -- 對流中每一個元素應用函數(shù),將其映射為一個新元素(不去修改,創(chuàng)建新版本)
對每個單詞求其長度并返回列表
List<String> words = Arrays.asList("Hello", "Are", "You", "Ok");
List<Integer> wordLengths = words.stream().map(String::length).collect(Collectors.toList()); --- [5, 3, 3, 2]
flatMap -- 對流中每個元素都轉換成另一個流,并把所有的流連接起來成為一個流,即流的扁平化
對給定的單詞,得到不同字符的列表:
List<String> words = Arrays.asList("Hello", "Are", "You", "Ok");
List<String> uniqueCharacters = words.stream().map(w -> w.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
--- [H, e, l, o, A, r, Y, u, O, k]
查找和匹配
anyMatch(謂詞) -- 流中是否至少有一個元素能匹配謂詞
是否至少有一個元素大于100
boolean flag = Stream.of(8, 9, 100, 200, 8, 18, 23, 100).anyMatch(i -> i > 100); --- true
allMatch(謂詞) -- 流中是否所有的元素都匹配謂詞
是否所有都為偶數(shù)
boolean flag = Stream.of(8, 9, 100, 200, 8, 18, 23, 100).allMatch(i -> i % 2 == 0); --- false
noneMatch(謂詞) -- 是否流中沒有元素與謂詞匹配
是否沒有元素大于200
boolean flag = Stream.of(8, 9, 100, 200, 8, 18, 23, 100).noneMatch(i -> i > 200); --- true
findAny() -- 返回當前流中的任意元素
返回當前流中長度大于2的任意元素
Optional<String> word = Stream.of("Hello World", "James", "Ok").filter(e -> e.length() > 2).findAny(); --- Hello World
如果流中沒有滿足條件的元素,則findAny()返回空(Optional.empty)
找出流中長度大于2的任意元素,如果存在,按如下格式輸出(結合Optional對象的ifPresent(函數(shù)) ):
Stream.of("Hello World", "James", "Ok").filter(e -> e.length() > 2).findAny().ifPresent(s -> System.out.println("[" + s + "]'s length = "+ s.length()));
--- [Hello World]'s length = 11
findFirst() -- 查找第一個元素
對流中元素求平方,找出第一個能被3整除的元素
Optional<Integer> firstElement = Stream.of(1, 2, 3, 4, 5, 6).map(x -> x * x).filter(x -> x % 3 == 0).findFirst(); --- 9
歸約
reduce -- 歸約(將流中元素反復結合起來,得到一個值)
元素求和:
int sum = Stream.of(5, 6, 7, 8).reduce(0, (a, b) -> a + b); --- 26
int sum = Stream.of(5, 6, 7, 8).reduce(0, Integer::sum); --- 26
無初始值,返回Optional對象
Optional<Integer> sumOpt = Stream.of(5, 6, 7, 8).reduce((a, b) -> (a + b)); --- 26
求最大值:
int max = Stream.of(10, 20, 0, 11, 100, 250).reduce(0, Integer::max); --- 250
Optional<Integer> maxOpt = Stream.of(10, 20, 0, 11, 100, 250).reduce(Integer::max); --- 250
求最小值:
int min = Stream.of(10, 20, 0, 11, 100, 250).reduce(0, Integer::min); --- 0
Optional<Integer> minOpt = Stream.of(10, 20, 0, 11, 100, 250).reduce(Integer::min); --- 0
map-reduce模式:(map和reduce的連接)
計算單詞的個數(shù):
int count = Stream.of("Hello", "James", "Ok").map(e -> 1).reduce(0, Integer::sum); --- 3 (將流中每個元素映射成數(shù)字1)
long count = Stream.of("Hello", "James", "Ok").count(); --- 3
特化流 IntStream、DoubleStream、LongStream
特化流IntStream、DoubleStream、LongStream分別將流中的元素特化為int、long和double,從而避免了暗含的裝箱操作。
mapToInt、mapToDouble、mapToLong -- 將流轉化成特化流
計算每個字符串的長度,并返回一個IntStream特化流(不是一個Stream<Integer>),然后調用IntStream接口中的sum方法求和:
如果流為空,則sum默認返回0
int sum = Stream.of("Hello World", "James", "Ok").mapToInt(String::length).sum(); --- 18
數(shù)值范圍:
rangeClosed(參數(shù)1, 參數(shù)2) -- 生成兩個參數(shù)之間的所有數(shù)字,包含第二個參數(shù)
IntStream evenNum = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
System.out.println(evenNum.count()); --- 50
range(參數(shù)1, 參數(shù)2) -- 生成兩個參數(shù)之間的所有數(shù)字,不包含第二個參數(shù)
IntStream evenNum = IntStream.range(1, 100).filter(n -> n % 2 == 0);
System.out.println(evenNum.count()); --- 49
boxed方法 -- 將特化流轉回一般的對象流
IntStream intStream = Stream.of("Hello World", "James", "Ok").mapToInt(String::length);
Stream<Integer> stream = intStream.boxed();
Optional原始類型特化版本:OptionalInt、OptionalDouble、OptionalLong
計算每個字符串的長度,并返回一個IntStream特化流,調用其max方法,返回一個OptionalInt特化對象:
OptionalInt maxLength = Stream.of("Hello World", "James", "Ok").mapToInt(String::length).max(); --- 11
收集器:
為了便于舉例,定義一個Person類,如下:
public class Person {
private final String name;
private final String sex;
private final int age;
private final Job job;
private final boolean partyMember;
public Person(String name, String sex, int age, Job job, boolean partyMember) {
this.name = name;
this.sex = sex;
this.age = age;
this.job = job;
this.partyMember = partyMember;
}
public enum Job { TEACHER, WORKER, DOCTOR, DRIVER, COOK}
...省略get,set方法
}
構建personList列表:
List<Person> personList = Arrays.asList(
new Person("李鐵蛋", "男",31, Person.Job.WORKER, true),
new Person("王翠花", "女", 22, Person.Job.TEACHER, false),
new Person("牛建國", "男", 62, Person.Job.DRIVER, false),
new Person("Lucy Rose", "女", 42, Person.Job.DOCTOR, true),
new Person("尼古拉斯蛋蛋", "男", 51, Person.Job.COOK, false)
);
構建流:
Stream<Person> personStream = personList.stream();
Collectors.toList() -- 把流中所有元素收集到一個List
List<Person> persons = personStream.collect(Collectors.toList());
Collectors.toSet() -- 把流中所有元素收集到一個Set,刪除重復項
Set<Person> persons = personStream.collect(Collectors.toSet());
Collectors.toCollection -- 把流中所有元素收集到給定的供應源創(chuàng)建的集合
Collection<Person> persons = personStream.collection(Collections.toCollection(ArrayList::new));
Collectors.counting() -- 計算流中元素個數(shù)
long count = personStream.collect(counting());
等價于
long count = personStream.count();
對流中元素相應類型的屬性求和
Collectors.summingInt、Collectors.summingLong、Collectors.summingDouble:
對所有人的年齡求和
int ageSum = personStream.collect(Collectors.summingInt(Person::getAge));
計算流中元素的平均值
Collectors.averagingInt、Collectors.averagingLong、Collectors.averagingDouble(返回類型都為double):
求所有人年齡的平均值
double ageAver = personStream.collect(Collectors.averagingInt(Person::getAge));
計算流中元素對應類型屬性的統(tǒng)計值(最大值,最小值,總和,平均值,數(shù)量)
Collectors.summarizingInt、Collectors.summarizingLong、Collectors.summarizingDouble:
對所有人的年齡求統(tǒng)計值
IntSummaryStatistics ageStatistics = personStream.collect(Collectors.summarizingInt(Person::getAge));
輸出:
IntSummaryStatistics{count=5, sum=208, min=22, average=41.600000, max=62}
Collectors.joining -- 連接字符串(內部使用StringBuilder來連接)
String personName = personStream.map(Person::getName).collect(Collectors.joining(", "));
Collectors.maxBy -- 按照指定比較器選出最大元素(被Optional包裹)
選出年齡最大的人
Optional<Person> maxAgePerson = personStream.collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));
Collectors.minBy -- 按照指定比較器選出最小元素(被Optional包裹)
選出年齡最小的人
Optional<Person> minAgePerson = personStream.collect(Collectors.minBy(Comparator.comparingInt(Person::getAge)));
Collectors.reducing -- 從一個作為累加器的初始值開始,利用BinaryOperator與流中的元素逐個結合,從而將流歸約為單個值。
對所有人的年齡求和
int ageSum = personStream.collect(Collectors.reducing(0, Person::getAge, Integer::sum));
Collectors.groupingBy -- 對流中元素進行分組,返回結果Map
根據(jù)元素的一個屬性的值對元素進行分組,分組結果是個Map,Map的key為屬性的值,Map的value為屬于這個屬性的元素列表。
按職業(yè)對人進行分組
Map<Person.Job, List<Person>> personsByJob = personStream.collect(Collectors.groupingBy(Person::getJob));
輸出:
{
COOK=[Person{name='尼古拉斯蛋蛋', sex='男', age=51, job=COOK}],
TEACHER=[Person{name='王翠花', sex='女', age=22, job=TEACHER}],
DRIVER=[Person{name='牛建國', sex='男', age=62, job=DRIVER}],
DOCTOR=[Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR}],
WORKER=[Person{name='李鐵蛋', sex='男', age=31, job=WORKER}]
}
按分的子組收集數(shù)據(jù):即傳遞收集器作為groupingBy的第二個參數(shù)。
例1:按性別分組,并計算每組的人數(shù):
Map<String, Long> sexCount = personStream.collect(Collectors.groupingBy(Person::getSex, Collectors.counting()));
輸出:
{女=2, 男=3}
例2:按性別分組,并查找每組中年齡最大的人:
Map<String, Optional<Person>> maxAgeBySex = personStream.collect(Collectors.groupingBy(Person::getSex, Collectors.maxBy(Comparator.comparingInt(Person::getAge))));
輸出:
{
女=Optional[Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR}],
男=Optional[Person{name='牛建國', sex='男', age=62, job=DRIVER}]
}
說明:這個Map中的值是Optional,是由于maxBy收集器返回的類型,實際上,如果不存在某個性別,就不會對應一個Optional.empty()值,根本不會出現(xiàn)在Map的鍵中,所以這個Optional包裝器在這里不是很有用。
支持多級分組,即可以把一個內層groupingBy傳遞給外層groupingBy,并定義一個為流中元素分類的二級標準。
先按性別分組,然后再按職業(yè)分組:
Map<String, Map<Person.Job, List<Person>>> personBySexAndJob = personStream.collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getJob)));
輸出:
{
女={
DOCTOR=[Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR}],
TEACHER=[Person{name='王翠花', sex='女', age=22, job=TEACHER}]
},
男={
COOK=[Person{name='尼古拉斯蛋蛋', sex='男', age=51, job=COOK}],
DRIVER=[Person{name='牛建國', sex='男', age=62, job=DRIVER}],
WORKER=[Person{name='李鐵蛋', sex='男', age=31, job=WORKER}]
}
}
Collectors.collectingAndThen -- 包裹另一個收集器,對其結果應用轉換函數(shù)
這個工廠方法接受兩個參數(shù),即要轉換的收集器和轉換函數(shù)。該方法相當于對舊收集器的一個包裝,collect操作的最后一步將返回值用轉換函數(shù)做一個映射。
如下例子:collectingAndThen包裹起maxBy收集器,而轉換函數(shù)Optional::get則把返回的Optional中的值提取出來。
按性別進行分組,并找出每組中年齡最大的人:
Map<String, Person> personMap = personStream.collect(Collectors.groupingBy(Person::getSex,
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Person::getAge)),Optional::get)));
輸出:
{
女=Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR, partyMember=true},
男=Person{name='牛建國', sex='男', age=62, job=DRIVER, partyMember=false}
}
Collectors.partitioningBy(謂詞) -- 分區(qū)(分組的特殊情況),即謂詞(返回一個boolean值的函數(shù))作為分類函數(shù),進行分組。
根據(jù)是否為黨員對人進行分組:
Map<Boolean, List<Person>> personMap = personStream.collect(Collectors.partitioningBy(Person::isPartyMember));
輸出:
{
false=[
Person{name='王翠花', sex='女', age=22, job=TEACHER, partyMember=false},
Person{name='牛建國', sex='男', age=62, job=DRIVER, partyMember=false},
Person{name='尼古拉斯蛋蛋', sex='男', age=51, job=COOK, partyMember=false}
],
true=[
Person{name='李鐵蛋', sex='男', age=31, job=WORKER, partyMember=true},
Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR, partyMember=true}
]
}
可以傳遞收集器作為partitioningBy的第二個參數(shù):
例1:統(tǒng)計黨員與非黨員的人數(shù):
Map<Boolean, Long> personCount = personStream.collect(Collectors.partitioningBy(Person::isPartyMember, Collectors.counting()));
輸出:{false=3, true=2}
例2:先按是否為黨員,然后再按性別進行分組:
Map<Boolean, Map<String, List<Person>>> personMap = personStream.collect(Collectors.partitioningBy(Person::isPartyMember, Collectors.groupingBy(Person::getSex)));
輸出:
{
false={
女=[Person{name='王翠花', sex='女', age=22, job=TEACHER, partyMember=false}],
男=[
Person{name='牛建國', sex='男', age=62, job=DRIVER, partyMember=false},
Person{name='尼古拉斯蛋蛋', sex='男', age=51, job=COOK, partyMember=false}
]
},
true={
女=[Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR, partyMember=true}],
男=[Person{name='李鐵蛋', sex='男', age=31, job=WORKER, partyMember=true}]
}
}
例3:先按是否為黨員分組,然后找出年齡最大的人:
Map<Boolean, Person> personMap = personStream.collect(Collectors.partitioningBy(Person::isPartyMember,
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Person::getAge)), Optional::get)));
輸出:
{
false=Person{name='牛建國', sex='男', age=62, job=DRIVER, partyMember=false},
true=Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR, partyMember=true}
}
例4:寫一個方法,接受參數(shù)int n,并將前n個自然數(shù)分為質數(shù)和非質數(shù):
判斷一個數(shù)是否為質數(shù)的方法:
public static boolean isPrime(int candidate) {
int candidateRoot = (int) Math.sqrt(candidate);
return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0);
}
創(chuàng)建一個包含n個自然數(shù)的流,用isPrime方法作為謂詞進行分區(qū):
public static Map<Boolean, List<Integer>> partitionPrimes(int n) {
return IntStream.rangeClosed(1, n).boxed().collect(Collectors.partitioningBy(candidate -> isPrime(candidate)));
}