概述
Java8的兩個重大改變,一個是Lambdab表達(dá)式,另外一個就是Stream API表達(dá)式。Stream是Java8中處理集合的關(guān)鍵抽象概念,它可以對集合進(jìn)行復(fù)雜的查找、過濾、篩選等操作,可以極大的提高Java程序員的生產(chǎn)力,讓程序員寫出高效、干凈、簡潔的代碼。
這種風(fēng)格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節(jié)點上進(jìn)行處理, 比如篩選, 排序,聚合等。
元素流在管道中經(jīng)過中間操作的處理,最后由最終操作得到前面處理的結(jié)果。
Stream 創(chuàng)建
創(chuàng)建流的方法主要有以下幾種:
- 通過集合創(chuàng)建流
- 通過數(shù)組創(chuàng)建流
- 創(chuàng)建空的流
- 創(chuàng)建無限流
- 創(chuàng)建規(guī)律無限流
以上這五種創(chuàng)建流的方法中使用最多的還是“通過集合創(chuàng)建流”,下面一一舉例,來熟悉下怎么通過這五種方式創(chuàng)建流。
通過集合創(chuàng)建流
public void testCollectionStream(){
List<String> list = Arrays.asList("111","xxxx","ccc","222");
//創(chuàng)建普通串行流
Stream<String> stringStream = list.stream();
//創(chuàng)建并行流
Stream<String> parallelStream = list.parallelStream();
}
通過數(shù)組創(chuàng)建流
public void testArrayStream(){
//通過Arrays.stream()創(chuàng)建
int[] arrInt = new int[]{1,2,3,4,5};
IntStream intStream = Arrays.stream(arrInt);
Student[] students = new Student[]{new Student("xcy",18,false),new Student("lvp",3,false)};
Stream<Student> studentStream = Arrays.stream(students);
//通過Stream.of()創(chuàng)建
Stream<Integer> IntegerStream = Stream.of(1,32,3,2312,332);
Stream<int[]> streamInt = Stream.of(arrInt,arrInt);
streamInt.forEach(System.out::println);
}
創(chuàng)建空的流
public void testEmptyStream(){
Stream<Integer> stream = Stream.empty();
}
創(chuàng)建無限流
public void testGenerateStream(){
//通過Stream.generate()創(chuàng)建無限流,通過limit來限制輸出個數(shù)。
Stream.generate(()->"random_"+new Random().nextInt()).limit(100).forEach(System.out::println);
Stream.generate(()->new Student("xcy",new Random(50).nextInt(),false)).limit(10).forEach(System.out::println);
}
創(chuàng)建規(guī)律無限流
public void testGenerateStream1(){
Stream.iterate(0,x->x+1).limit(10).forEach(System.out::println);
}
Stream的操作
Stream上的所有操作都分為兩大類 “中間操作” 和 “結(jié)束操作” 。中間操作只是一種標(biāo)記,只有結(jié)束操作才會觸發(fā)最后的計算。中間操作又可以分為 “無狀態(tài)操作”(Stateless) 和 “有狀態(tài)操作”(Stateful)。
無狀態(tài)中間操作;是指元素的處理不受前面元素的影響,而有狀態(tài)中間操作,必須等到所有元素處理之后才知道最終的結(jié)果。例如,排序操作就是有狀態(tài)操作,只有當(dāng)所有元素都讀取完了,才知道最終排序結(jié)果;結(jié)束操作又分為“短路操作”和
“非短路操作”。短路操作是指不用處理完所有數(shù)據(jù),就可以中斷的操作,例如anyMatch();而非短路操作,則需要操作完所有數(shù)據(jù)才能得出最終結(jié)果,例如forEach();下面是目前整理出的一個大概分類表:
| 操作類型 | Stream API |
|---|---|
| 中間操作-無狀態(tài) | unordered();filter();map();mapToInt();mapToLong();mapToDouble(); flatMap();flatMapToInt();flatMapToDouble();flatMapToLong();peek(); |
| 中間操作-有狀態(tài) | distinct();sorted();limit();skip(); |
| 結(jié)束操作-非短路操作 | forEach();forEachOrdered();toArray();reduce();collect();max();min(); count();sum(); |
| 結(jié)束操作-短路操作 | anyMatch();allMatch();noneMatch();findFirst();findAny(); |
下面通過一些例子來熟悉一下常用的一些方法;
map()
map()是在Stream()流中使用較為頻繁的一個方法,其作用是將一種類型的流轉(zhuǎn)換為另外一種類型的流。
//將Stream<Student>轉(zhuǎn)換為Stream<String>
@Test
public void testMap(){
List<Student> students = Arrays.asList(new Student("xcy",18,false),new Student("lvp",3,false));
List<String> names = students.stream().map(Student::getName).collect(Collectors.toList());
System.out.println(names);
}
filter()
filter,顧名思義,用處是過濾流,將不需要的部分過濾掉。
@Test
public void testFilter(){
List<Integer> integers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
integers = integers.stream().filter(e-> e<5).collect(Collectors.toList());
//result:[1, 2, 3, 4]
System.out.println(integers);
}
sorted()
sorted方法用于排序,
@Test
public void testSorted(){
String[] arr = {"a","abc","bcd","abcd","bc"};
//按照字符串長度排序
System.out.println("字符串按照長度排序===================");
Arrays.stream(arr).sorted((x,y)->{
if(x.length()>y.length()){
return 1;
}else if (x.length() == y.length()){
return 0;
}else{
return -1;
}
}).forEach(System.out::println);
System.out.println("字符串按照自然順序排序,即首字母順序===================");
//按照自然順序排序
Arrays.stream(arr).sorted().forEach(System.out::println);
System.out.println("字符處按自然順序倒序===================");
//按照自然順序倒序
Arrays.stream(arr).sorted(Comparator.reverseOrder()).forEach(System.out::println);
//引用類型集合按照某個屬性正序
System.out.println("將對象按照某個屬性順序排序===================");
List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",3),new Student("小綠",10));
students.stream().sorted(Comparator.comparing(Student::getAge)).forEach(System.out::println);
//引用類型集合按照某個屬性倒序
System.out.println("將對象按照某個屬性倒序排序===================");
students.stream().sorted(Comparator.comparing(Student::getAge).reversed()).forEach(System.out::println);
}
distinct()
類似于sql 中的distinct,都是去重的作用。該方法通過equals()比較兩個對象是否相等。所以對實體對象去重的時候,需要提前定義好equals()方法。
例如:[student(name="xcy",age = 18);student(name="xcy",age=18)],如果沒有定義equals方法,系統(tǒng)就不會去重。
skip()
skip用于跳過前n個元素
@Test
public void testSkip(){
List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",3),new Student("小綠",10));
students = students.stream().skip(1).collect(Collectors.toList());
System.out.println(students);
}
limit()
返回前N個元素,用法如下:
@Test
public void testLimit(){
List<String> strings = Arrays.asList("aaa","bbb","ccc","ddd","eee","fff");
strings = strings.stream().limit(3).collect(Collectors.toList());
System.out.println(strings);
//result:[aaa, bbb, ccc]
}
flatMap()
flatMap的作用是將流中的每個元素映射為一個流,再將每個流連接成為一個流。下面舉一個具體的實例。
給定單詞列表["Hello","World"],輸出["H","e","l","o","W","r","d"]。
解決方案如下:
@Test
public void testFlatMap(){
String[] ss = new String[]{"Hello","World"};
List result = Arrays.stream(ss).map(e->e.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
System.out.println(result);
}
allMatch()/anyMatch()/noneMatch()
這里將這三個方法放一起將,從字面意思就可以看出來;
- allMatch() 判斷流中是否所有元素都匹配給出的boolean條件;
- anyMatch() 判斷流中是否存在元素匹配給出的boolean條件;
- noneMatch() 判斷流中是否所有元素都不匹配給出的boolean條件。
@Test
public void testMatch(){
List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",18),new Student("小綠",18));
boolean allMatch = students.stream().allMatch(e->18 == e.getAge());
System.out.println("allMatch:"+allMatch);
//result allMatch:true
boolean anyMatch = students.stream().anyMatch(e->"小藍(lán)".equals(e.getName()));
System.out.println("anyMatch:"+anyMatch);
//result anyMatch:true
boolean noneMatch = students.stream().noneMatch(e->"小黃".equals(e.getName()));
System.out.println("noneMatch:"+noneMatch);
//result noneMatch:true
}
forEach()
@Test
public void testForEach(){
List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",18),new Student("小綠",18));
students.stream().forEach(System.out::println);//和student.forEach(System.out::println)
//可以forEach中調(diào)用其他類的方法。
students.stream().forEach(StudentMapper::insert);
}
max(),min(),count(),sum(),average()
這是數(shù)據(jù)流對應(yīng)的相關(guān)操錯,具體操作方法如下:
@Test
public void testMath(){
List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",18),new Student("小綠",18));
System.out.println(students.stream().count());
System.out.println(students.stream().mapToInt(Student::getAge).sum());
System.out.println(students.stream().mapToInt(Student::getAge).average());
System.out.println(students.stream().mapToInt(Student::getAge).max());
System.out.println(students.stream().mapToInt(Student::getAge).min());
}
collect()
collect收集器中包含很多方法。下面也舉例一些常用方法。
- collectors.toList() : 轉(zhuǎn)換list集合
- collectors.toSet(): 轉(zhuǎn)換set集合
- collectors.toCollection(TreeSet::new):轉(zhuǎn)換成任意指定類型的集合
- collectors.minBy():求最小值,相對應(yīng)的有求最大值
- collectors.averagingInt():求平均值
- collectors.summing(): 求和
- Collectors.summarizingDouble(x -> x):可以獲取最大值、最小值、平均值、總和值、總數(shù)。
- Collectors.groupingBy(x -> x): 分組,這里有三個方法,下面統(tǒng)一看一下。
- Collectors.partitioningBy(x -> x>2) : 把數(shù)據(jù)分成兩部分,key為ture/false。第一個方法也是調(diào)用第二個方法,第二個參數(shù)默認(rèn)為Collectors.toList().
- Collectors.joining():拼接字符串
- Collectors.collectingAndThen(Collectors.toList(), x -> x.size()):先執(zhí)行collect操作后再執(zhí)行第二個參數(shù)的表達(dá)式。這里是先塞到集合,再得出集合長度
下面是這些方法的示例:
@Test
public void testCollect(){
List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",18),new Student("小綠",18));
LinkedList<Student> students1 = students.stream().collect(Collectors.toCollection(LinkedList::new));
System.out.println("students1:"+students1);
Map<Integer,List<Student>> studentListMap = students.stream().collect(Collectors.groupingBy(Student::getAge));
System.out.println("studentListMap:"+studentListMap);
Map<String,Integer> studentMap = students.stream().collect(Collectors.toMap(Student::getName
,Student::getAge));
System.out.println("studentMap:"+studentMap);
// Map<Integer,String> stringMap1 = students.stream().collect(Collectors.toMap(Student::getAge,Student::getName));
// System.out.println("stringMap1"+stringMap1);
Student student = students.stream().collect(Collectors.minBy((o1, o2) -> {
if(o1.getAge()>o2.getAge()){
return 1;
}else if (o1.getAge() == o2.getAge()){
return 0;
}else{
return -1;
}
})).get();
System.out.println("student : " + student);
DoubleSummaryStatistics doubleSummaryStatistics = students.stream().collect(Collectors.summarizingDouble(Student::getAge));
System.out.println("average:"+ doubleSummaryStatistics.getAverage());
System.out.println("count:"+ doubleSummaryStatistics.getCount());
Map<Integer, List<Integer>> groupByMap = Stream.of(1, 3, 3, 2).collect(Collectors.groupingBy(Function.identity()));
System.out.println("groupByMap:" + groupByMap);
//result:{1=[1], 2=[2], 3=[3, 3]}
Map<Integer, Integer> groupByMap1 = Stream.of(1, 3, 3, 2).collect(Collectors.groupingBy(Function.identity(), Collectors.summingInt(x -> x)));
System.out.println("groupByMap1:"+groupByMap1);
//result:{1=1, 2=2, 3=6}
HashMap<Integer, List<Integer>> groupByMap2 = Stream.of(1, 3, 3, 2).collect(Collectors.groupingBy(Function.identity(), HashMap::new, Collectors.mapping(x -> x + 1, Collectors.toList())));
System.out.println("groupByMap2:" + groupByMap2);
//result :{1=[2], 2=[3], 3=[4, 4]}
Map<Boolean, List<Integer>> partitioningByMap1 = Stream.of(1, 3, 3, 2).collect(Collectors.partitioningBy(x -> x > 2));
Map<Boolean, Long> longMap = Stream.of(1, 3, 3, 2).collect(Collectors.partitioningBy(x -> x > 1, Collectors.counting()));
System.out.println("partitioningByMap1:"+partitioningByMap1);
//result : {false=[1, 2], true=[3, 3]}
System.out.println("longMap:"+longMap);
//result : {false=1, true=3}
Integer integer = Stream.of("1", "2", "3").collect(Collectors.collectingAndThen(Collectors.toList(), x -> x.size()));
System.out.println("integer:"+integer);
}
并行流
并行流就是把一個內(nèi)容分成多個模塊,并用不同的線程處理每個模塊的數(shù)據(jù)的流。Stream API可以聲明性的通過parallel()方法和sequential()方法在并行流和串行流之間進(jìn)行切換。
Java8 的并行流,其底層使用的是Java7引入的fork/join框架,這里簡單說下fork/join,如果想了解的更徹底,可以去翻下資料。
fork/join框架,就是在必要的情況下,將一個大任務(wù),進(jìn)行拆分(fork)成若干個小任務(wù)(拆分到不可拆分)時,再將一個個小任務(wù)的執(zhí)行結(jié)果進(jìn)行整合(join)匯總。

將一個順序流改為并行流只需要調(diào)用parallel()方法:
@Test
public void testParallel(){
Long result = Stream.iterate(1L,i -> i+1).limit(10).parallel().reduce(0L,Long::sum);
System.out.println(result);
}
將一個并行流改為串行流也只需要調(diào)用sequential()方法;
stream.parallel() .filter(...) .sequential() .map(...) .parallel() .reduce();
parallel()和sequential()方法可以交替重復(fù)調(diào)用,只有最后一次調(diào)用,會決定這個流是順序執(zhí)行還是并行執(zhí)行的。并發(fā)執(zhí)行的默認(rèn)線程數(shù)等于機器的處理器核心數(shù)。
事實上,并不是使用并行流的效率就一定比串行的高,這種同理于多線程與單線程的執(zhí)行效率受很多因素的影響。并發(fā)流需要對數(shù)據(jù)進(jìn)行分解,并不是所有的數(shù)據(jù)結(jié)構(gòu)都適合分解,下圖是整理的一些常用數(shù)據(jù)結(jié)構(gòu)的可分解性。
| 數(shù)據(jù)源 | 可分解性 |
|---|---|
| ArrayList | 非常好 |
| LinkedList | 差 |
| IntStream.range | 非常好 |
| Stream.iterate | 差 |
| HashSet | 好 |
| TreeSet | 好 |
除了上面的數(shù)據(jù)源的可分解性會對性能產(chǎn)生影響,還有對流的操作也會有影響。比如findFirst(),limit(n),skip(n)這種對順序有要求的操作,在并發(fā)流中是非常消耗性能的;而findAny(),anyMatch()這種的就非常適合使用并行流。