寫(xiě)在前面
Java8中有兩大最為重要的改變:
- Lambda表達(dá)式
- Stream API(java.util.stream.*)
Stream是Java8中處理集合的關(guān)鍵抽象概念,它可以指定你希望對(duì)集合進(jìn)行的操作迅矛,可以執(zhí)行非常復(fù)雜的查找浦译、過(guò)濾荡碾、映射數(shù)據(jù)等操作皂岔。使用Stream API對(duì)集合數(shù)據(jù)進(jìn)行操作咽斧,就類似于使用SQL執(zhí)行數(shù)據(jù)庫(kù)查詢喂走。也可以使用Stream API來(lái)并行執(zhí)行操作捺弦。簡(jiǎn)而言之饮寞,Stream API提供了一種高效且易于使用的處理數(shù)據(jù)的方式。
Stream是什么
流是數(shù)據(jù)渠道列吼,用于操作數(shù)據(jù)源(集合幽崩、數(shù)組等)所生成的元素序列。
即流這個(gè)數(shù)據(jù)渠道冈欢,在數(shù)據(jù)傳輸過(guò)程中歉铝,對(duì)數(shù)據(jù)源做一系列流水線式的中間操作,然后產(chǎn)生一個(gè)新的流凑耻,這個(gè)流不會(huì)改變?cè)瓉?lái)的流
集合講的是數(shù)據(jù)太示,流講的是計(jì)算
注意
- Stream 自己不會(huì)存儲(chǔ)元素。
- Stream 不會(huì)改變?cè)磳?duì)象香浩。相反类缤,他們會(huì)返回一個(gè)持有結(jié)果的新Stream。
- Stream 操作是延遲執(zhí)行的邻吭。這意味著他們會(huì)等需要結(jié)果的時(shí)候才執(zhí)行餐弱。
Stream操作的三個(gè)步驟
1. 創(chuàng)建Stream
1.1 通過(guò)Collection集合
通過(guò)Collection系列集合提供的Stream()串行流或parallelStream()并行流。
其中,Java8中的Collection接口擴(kuò)展了膏蚓,提供了兩個(gè)獲取流的方法:
- 返回一個(gè)順序流
/*
* @since 1.8
*/
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
- 返回一個(gè)并行流
/*
* @since 1.8
*/
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
下面我們寫(xiě)個(gè)通過(guò)Collection系列集合流創(chuàng)建流的例子瓢谢。
@Test
public void test01(){
//通過(guò)Collection系列集合提供的Stream()或parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> stringStream = list.parallelStream();
}
1.2 通過(guò)數(shù)組Arrays
//通過(guò)數(shù)組Arrays 中的靜態(tài)方法stream()獲取數(shù)組
Employee[] employees = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(employees);
通過(guò)Stream流中的of()方法來(lái)創(chuàng)建
//通過(guò)Stream類中的靜態(tài)方法of()
Stream<String> stream3 = Stream.of("aa", "bb", "cc", "dd");
1.3 創(chuàng)建無(wú)限流
創(chuàng)建無(wú)限流的兩種方法:迭代、生成
我們先來(lái)看一下迭代Stream.iterate()方法的定義
我們?cè)賮?lái)看一下生成的方式驮瞧, Stream.generate()這里需要一個(gè)供給型的參數(shù)
再來(lái)寫(xiě)下創(chuàng)建無(wú)限流的例子
//創(chuàng)建無(wú)限流
//迭代 Stream.iterate()傳倆參數(shù)氓扛,第一個(gè)是種子即起始值,第二個(gè)參數(shù)是Lambda表達(dá)式即對(duì)起始值進(jìn)行的操作论笔,
//這里是生成偶數(shù)
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
//無(wú)限流就是沒(méi)有限制采郎,需要一個(gè)終止操作限制
stream4.limit(4).forEach(System.out::println);
//生成的方式
Stream<Double> generate5 = Stream.generate(() -> Math.random());
generate5.limit(5).forEach(System.out::println);
運(yùn)行結(jié)果
0
2
4
6
0.3204608858702849
0.495403965457605
0.9188968488509007
0.18726624455121932
0.3791774193868236
2. 中間操作
一個(gè)中間操作鏈,對(duì)數(shù)據(jù)源的數(shù)據(jù)進(jìn)行處理狂魔。
多個(gè)中間操作可以連接起來(lái)形成一個(gè)流水線蒜埋,除非流水線上觸發(fā)了終止操作,否則中間操作不會(huì)執(zhí)行任何的處理最楷,而在終止操作時(shí)一次性處理整份,稱為“惰性求值”或者“延遲加載”。
2.1 篩選與切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda籽孙,從流中排除某些元素 |
distinct() | 篩選皂林,通過(guò)流所生成元素的hashCode()和equal()去除重復(fù)元素 |
limit(long maxSize) | 截?cái)嗔鳎蛊湓夭怀^(guò)給定數(shù)量 |
skip(long n) | 跳過(guò)元素蚯撩,返回一個(gè)扔掉了前n個(gè)元素的流。若流中元素不足n個(gè)烛占,則返回一個(gè)空流胎挎。與limit(n)互補(bǔ) |
下面我們一個(gè)個(gè)來(lái)分析:
2.1.1 filter(Predicate p)
接收Lambda,從流中排除某些元素
@Test
public void test02(){
List<Employee> employees = Arrays.asList(
new Employee("張三",68,9000),
new Employee("李四",38,8000),
new Employee("王五",50,4000),
new Employee("趙六",18,3000),
new Employee("田七",8,1000));
//中間操作忆家,不會(huì)執(zhí)行任何操作
//filter(Predicate p) 接收Lambda犹菇,從流中排除某些元素
Stream<Employee> employeeStream = employees.stream().filter((e) -> {
System.out.println("StreamAPI的中間操作");
return e.getAge() > 35;
});
//終止操作 當(dāng)只有中間操作時(shí)執(zhí)行是沒(méi)有結(jié)果的,因?yàn)橹挥袌?zhí)行終止操作以后所有的中間操作才一次性全部處理芽卿,即惰性求值
employeeStream.forEach(System.out::println);
}
運(yùn)行結(jié)果
StreamAPI的中間操作
Employee{name='張三', age=68, salary=9000.0}
StreamAPI的中間操作
Employee{name='李四', age=38, salary=8000.0}
StreamAPI的中間操作
Employee{name='王五', age=50, salary=4000.0}
StreamAPI的中間操作
StreamAPI的中間操作
從運(yùn)行結(jié)果可以看出揭芍,迭代操作不是我們做的,是由Stream API 幫我們完成的卸例,再也不需要我們自己完成這個(gè)迭代操作了称杨,這也叫內(nèi)部迭代。與內(nèi)部迭代相對(duì)應(yīng)的是外部迭代筷转,也就是我們自己寫(xiě)的迭代姑原。
//外部迭代,我們自己寫(xiě)的迭代
@Test
public void test03(){
Iterator<Employee> iterator = employees.iterator();
if(iterator.hasNext()){
System.out.println(iterator.next());
}
}
2.1.2 limit(long maxSize)
limit()呜舒,下面我們過(guò)濾公司中薪資大于5000的雇員之后锭汛,獲取其中前2個(gè)
//limit()過(guò)濾公司中薪資大于5000的雇員之后,獲取其中前2個(gè)雇員的信息
@Test
public void test04(){
employees.stream()
.filter((e) -> {
System.out.println("===短路===");
return e.getSalary()>5000;
})
.limit(2)
.forEach(System.out::println);
}
運(yùn)行結(jié)果
從運(yùn)行結(jié)果我們看出,迭代操作只執(zhí)行了兩次唤殴,也就是說(shuō)只要找到滿足條件的結(jié)果之后般婆,就不再進(jìn)行迭代了,這個(gè)過(guò)程就叫“短路”朵逝。所以說(shuō)它也可以提高效率蔚袍,跟我們之前學(xué)的短路&&和||有點(diǎn)類似。
2.1.3 skip(long n)
扔掉也即跳過(guò)前幾個(gè)元素
List<Employee> employees = Arrays.asList(
new Employee("張三",68,9000),
new Employee("李四",38,8000),
new Employee("王五",50,4000),
new Employee("趙六",18,3000),
new Employee("田七",8,1000));
//skip()過(guò)濾公司中薪資大于5000的雇員之后廉侧,跳過(guò)前2個(gè)雇員的信息
@Test
public void test05(){
employees.stream()
.filter((e) -> e.getSalary()>2000)
.skip(2)
.forEach(System.out::println);
}
運(yùn)行結(jié)果页响,跳過(guò)了8000和9000的工資
Employee{name='王五', age=50, salary=4000.0}
Employee{name='趙六', age=18, salary=3000.0}
2.1.4 distinct()
我們首先給employees集合中添加幾個(gè)重復(fù)的元素趙六
@Test
public void test06(){
List<Employee> employees1 = Arrays.asList(
new Employee("張三",68,9000),
new Employee("李四",38,8000),
new Employee("王五",50,4000),
new Employee("趙六",18,3000),
new Employee("趙六",18,3000),
new Employee("趙六",18,3000),
new Employee("趙六",18,3000),
new Employee("田七",8,1000));
employees1.stream()
.filter((e) -> e.getSalary()>2000)
.distinct()
.forEach(System.out::println);
}
運(yùn)行結(jié)果
我們發(fā)現(xiàn)結(jié)果并沒(méi)有去重,因?yàn)槎翁埽峭ㄟ^(guò)流所生成元素的hashCode()和equals()來(lái)去除重復(fù)元素的闰蚕。所以,要想去除重復(fù)元素连舍,必須得重寫(xiě)Employee實(shí)體類中的hashCode()和equals()這兩個(gè)方法没陡。
重寫(xiě)后的Employee如下
package com.cqq.java8.lambda;
import java.util.Objects;
/**
* @author caoqianqian
* @Description:
* @date 2021/3/6 9:24
*/
public class Employee {
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee() {
}
public Employee(int age) {
this.age = age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Double.compare(employee.salary, salary) == 0 &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary);
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
我們?cè)龠\(yùn)行一下結(jié)果,已經(jīng)是去重了的索赏。
2.2 映射
方法 | 描述 |
---|---|
map(Function f) | 接收一個(gè)函數(shù)作為參數(shù)盼玄,該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上,并將其映射成一個(gè)新的元素 |
mapToDouble(ToDoubleFunction f) | 接收一個(gè)函數(shù)作為參數(shù)潜腻,該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上埃儿,并將其映射成一個(gè)新的DoubleStream |
mapToInt(ToIntFunction f) | 接收一個(gè)函數(shù)作為參數(shù),該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上融涣,并將其映射成一個(gè)新的IntStream |
mapToLong(ToLongFunction f) | 接收一個(gè)函數(shù)作為參數(shù)童番,該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上,并將其映射成一個(gè)新的LongStream |
flatMap(Function f) | 接收一個(gè)函數(shù)作為參數(shù),將流中的每個(gè)值都換成另一個(gè)流威鹿,然后把左右流連成一個(gè)流 |
2.2.1 map(Function f)
接收Lambda剃斧,將元素轉(zhuǎn)換成其他形式或提取信息。接收一個(gè)函數(shù)作為參數(shù)忽你,該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上幼东,并將其映射成一個(gè)新的元素
@Test
public void test07(){
//將字母轉(zhuǎn)大寫(xiě)
List<String> list = Arrays.asList("aa","bb","cc");
list.stream()
.map(str -> str.toUpperCase())
.forEach(System.out::println);
//提取員工名字
employees.stream()
.map((e) -> e.getName())
.forEach(System.out::println);
}
運(yùn)行結(jié)果
AA
BB
CC
張三
李四
王五
趙六
田七
2.2.2 flatMap(Function f)
接收一個(gè)函數(shù)作為參數(shù),將流中的每個(gè)值都換成另一個(gè)流科雳,然后把所有流連成另一個(gè)流根蟹。
我們先來(lái)寫(xiě)一個(gè)方法來(lái)解析字符串,并把字符串中的一個(gè)一個(gè)的字符給單獨(dú)提取出來(lái)糟秘,放到集合中娜亿。
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character c:str.toCharArray()) {
list.add(c);
}
return list.stream();
}
再來(lái)看這個(gè)測(cè)試?yán)?/p>
//faltMap
@Test
public void test08(){
//提取list里的一個(gè)個(gè)字符串的每一個(gè)字符
List<String> list = Arrays.asList("aa","bb","cc");
//通過(guò)map提取到的是一個(gè)stream流里還是stream流
Stream<Stream<Character>> streamStream = list.stream()
.map(TestStreamAPI::filterCharacter);
//這里得嵌套遍歷才可以循環(huán)出每個(gè)字符串的結(jié)果
streamStream.forEach(
s -> s.forEach(System.out::println)
);
//通過(guò)flatMap提取到的是一個(gè)stream流
Stream<Character> streamStream2 = list.stream()
.flatMap(TestStreamAPI::filterCharacter);
//這樣就不用了嵌套遍歷了
streamStream2.forEach(System.out::println);
}
其實(shí)map方法就相當(dāng)于Collaction的add方法,如果add的是個(gè)集合的話就會(huì)變成二維數(shù)組蚌堵,而flatMap 的話就相當(dāng)于Collaction的addAll方法买决,參數(shù)如果是集合的話沛婴,只是將2個(gè)集合合并,而不是變成二維數(shù)組督赤。
2.3 排序
方法 | 描述 |
---|---|
sorted() | 產(chǎn)生一個(gè)新流嘁灯,其中按自然順序排序 |
sorted(Comparator comp) | 產(chǎn)生一個(gè)新流,其中按比較器順序排序 |
下面我們編寫(xiě)測(cè)試?yán)佣闵啵紫任覀兿葎?chuàng)建一個(gè)員工的集合:
List<Employee> employees = Arrays.asList(
new Employee("張三",68,9000),
new Employee("李四",38,8000),
new Employee("王五",50,4000),
new Employee("趙六",18,3000),
new Employee("田七",8,1000));
用sort() 自然排序?qū)崿F(xiàn)一串字符list的自然排序丑婿。用sorted(Comparator comp) 比較器排序,實(shí)現(xiàn)員工按年齡排序没卸,如果年齡相等按姓名排羹奉。
//排序
//sort() 自然排序
//sorted(Comparator comp) 比較器排序
@Test
public void test09(){
//sort() 將字母按自然醒順序排序
List<String> list = Arrays.asList("bb","aa","cc");
list.stream()
.sorted()
.forEach(System.out::println);
//sorted(Comparator comp) 比較器排序 按年齡排序,如果年齡相等按姓名排
employees.stream()
.sorted((e1,e2) -> {
if(e1.getAge()== e2.getAge()){
return e1.getName().compareTo(e2.getName());
}else {
return Integer.compare(e1.getAge(),e2.getAge());
}
})
.forEach(System.out::println);
}
運(yùn)行結(jié)果
aa
bb
cc
Employee{name='田七', age=8, salary=1000.0}
Employee{name='趙六', age=18, salary=3000.0}
Employee{name='李四', age=38, salary=8000.0}
Employee{name='王五', age=50, salary=4000.0}
Employee{name='張三', age=68, salary=9000.0}
3. 終止操作(終端操作)
一個(gè)終止操作约计,執(zhí)行中間操作鏈诀拭,并產(chǎn)生結(jié)果。
即終止操作會(huì)從流的流水線生成結(jié)果煤蚌,其結(jié)果可以是任何不是流的值耕挨,例如List、Integer尉桩、甚至是void筒占。
3.1 查找與匹配
方法 | 描述 |
---|---|
allMatch(Predicate p) | 檢查是否匹配所有元素 |
anyMatch(Predicate p) | 檢查是否至少匹配一個(gè)元素 |
noneMatch(Predicate p) | 檢查是否沒(méi)有匹配所有元素 |
findFirst() | 返回第一個(gè)元素 |
findAny() | 返回當(dāng)前流中的任意元素 |
count() | 返回流中元素的總個(gè)數(shù) |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 內(nèi)部迭代(使用Collection接口需要用戶去做迭代,成為外部迭代蜘犁。相反翰苫,Stream API使用內(nèi)部迭代--它幫你把迭代做好了) |
3.1.1 allMatch(Predicate p)檢查是否匹配所有元素
下面寫(xiě)一個(gè)例子,檢查公司中所有員工是否處于空閑狀態(tài)这橙。
@Test
public void test10(){
List<Employee> employees1 = Arrays.asList(
new Employee("張三",68,9000, Employee.Status.FREE),
new Employee("李四",38,8000,Employee.Status.BUSY),
new Employee("王五",50,4000,Employee.Status.VOCATION),
new Employee("趙六",18,3000,Employee.Status.BUSY),
new Employee("田七",8,1000,Employee.Status.FREE));
boolean b = employees1.stream()
.allMatch(e -> e.getStatus().equals(Employee.Status.FREE));
System.out.println("===allMatch===="+b);
}
Employee 的實(shí)體類中加個(gè)狀態(tài)和對(duì)應(yīng)添加這個(gè)參數(shù)的構(gòu)造方法
package com.cqq.java8.lambda;
import java.util.Objects;
public class Employee {
private String name;
private int age;
private double salary;
private Status status;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(String name, int age, double salary,Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
public Employee() {
}
public Employee(int age) {
this.age = age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Double.compare(employee.salary, salary) == 0 &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary);
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public enum Status{
FREE,
BUSY,
VOCATION;
}
}
3.1.2 anyMatch(Predicate p)檢查是否至少匹配一個(gè)元素
//anyMatch
@Test
public void test11(){
boolean c = employees1.stream()
.anyMatch(e -> e.getStatus().equals(Employee.Status.FREE));
System.out.println("===anyMatch===="+c);
}
3.1.3 noneMatch(Predicate p)檢查是否沒(méi)有匹配所有元素
//noneMatch
@Test
public void test12(){
boolean d = employees1.stream()
.noneMatch(e -> e.getStatus().equals(Employee.Status.FREE));
System.out.println("===anyMatch===="+d);
}
3.1.4 findFirst()返回第一個(gè)元素
//findFirst
@Test
public void test13(){
Optional<Employee> first = employees1.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst();
System.out.println(first.get());
}
注意:上面findFirst返回的是一個(gè)Optional對(duì)象革骨,它將我們的Employee封裝了一層,這是為了避免了空指針析恋。而且這個(gè)對(duì)象為我們提供了orElse方法,也就是當(dāng)我們拿到的這個(gè)對(duì)象為空的時(shí)候盛卡,我們可以傳入一個(gè)新的對(duì)象去代替它助隧。
3.1.5 findAny()返回當(dāng)前流中任意元素
//findAny
@Test
public void test14(){
Optional<Employee> first = employees1.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findAny();
System.out.println(first.get());
}
3.1.6 count()返回流中的總個(gè)數(shù)
//count
@Test
public void test15(){
long count = employees1.stream()
.count();
System.out.println(count);
}
3.1.7 max()返回流中最大的元素
//max
@Test
public void test16(){
//兩種方法
Optional<Employee> max1 = employees1.stream()
.max((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()));
Optional<Employee> max2 = employees1.stream()
.max(Comparator.comparingInt(Employee::getAge));
System.out.println(max1);
}
3.1.8 min()返回流中最小的元素
//min
@Test
public void test17(){
//兩種方法
Optional<Employee> max1 = employees1.stream()
.min((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()));
Optional<Employee> max2 = employees1.stream()
.min(Comparator.comparingInt(Employee::getAge));
System.out.println(max1);
}
3.1.9 forEach(Consumer c)
這個(gè)我們很熟悉了,每個(gè)測(cè)試?yán)佣佳h(huán)打印輸出
3.2 歸約
方法 | 描述 |
---|---|
reduce(T identity,Binaryoperator b) | 可以將流中元素反復(fù)結(jié)合在一起滑沧,得到一個(gè)值并村。返回T |
reduce(Binaryoperator b) | 可以將流中元素反復(fù)結(jié)合在一起,得到一個(gè)值滓技。返回Optional<T> |
3.2.1 reduce(T identity,Binaryoperator b)
//reduce()
@Test
public void test18(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//首先哩牍,需要傳一個(gè)起始值,然后令漂,傳入的是一個(gè)二元運(yùn)算膝昆。
Integer count = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(count);
}
reduce 歸約丸边,把流中的元素按照(x, y) -> x + y的形式進(jìn)行累加操作。首先0是一個(gè)起始值荚孵,然后從流中取出第一個(gè)元素1作為y進(jìn)行累加即結(jié)果為1妹窖,再將這個(gè)結(jié)果1作為x,再?gòu)牧髦腥〕鲆粋€(gè)元素2作為y進(jìn)行累加收叶,結(jié)果為3骄呼,一次類推,反復(fù)結(jié)合判没,最終得到一個(gè)新值蜓萄。
3.2.2 reduce(Binaryoperator b)
//reduce()
@Test
public void test19(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//沒(méi)有起始值,則有可能結(jié)果為空澄峰,所以返回的值會(huì)被封裝到Optional中嫉沽。
Optional<Integer> reduce = list.stream().reduce((x, y) -> x + y);
//另一種寫(xiě)法
Optional<Integer> reduce1 = list.stream().reduce(Integer::sum);
System.out.println(reduce);
System.out.println(reduce1);
}
沒(méi)有起始值,則有可能結(jié)果為空摊阀,所以返回的值會(huì)被封裝到Optional中耻蛇。
備注:map和reduce的連接通常稱為map-reduce模式,因Google用它來(lái)進(jìn)行網(wǎng)絡(luò)搜索而出名胞此。
3.3 收集
方法 | 描述 |
---|---|
collect(Collect c) | 將流轉(zhuǎn)化為其他形式臣咖,接收一個(gè)Collector接口的實(shí)現(xiàn),用于給Stream中元素做匯總的方法 |
Collector接口中方法的實(shí)現(xiàn)漱牵,決定了如何對(duì)流執(zhí)行收集操作(如收集到List夺蛇、Set及Map中),而且Collectors實(shí)用類中提供了許多靜態(tài)方法酣胀,可以方便的創(chuàng)建常見(jiàn)的收集器實(shí)例刁赦。Collectors實(shí)用類中常用的靜態(tài)方法如下表所示:
方法 | 返回類型 | 作用 |
---|---|---|
toList() | List<T> | 把流中的元素收集到List集合中 |
toSet() | Set<T> | 把流中的元素收集到Set集合中 |
toCollection(...) | Collection<T> | 把流中的元素收集到創(chuàng)建好的集合中 |
counting() | Long | 計(jì)算流中元素個(gè)數(shù) |
averagingDouble(...) | Double | 計(jì)算流中元素Double類型的平均值 |
summingDouble(...) | Double | 計(jì)算流中元素Double類型的總和 |
maxBy(...) | Optional<T> | 根據(jù)比較器選擇最大值 |
minBy(...) | Optional<T> | 根據(jù)比較器選擇最小值 |
groupingBy(...) | Map<K,List<T>> | 根據(jù)某屬性的值對(duì)流分組,屬性為K闻镶,結(jié)果為V |
partitioningBy(...) | Map<Boolean,List<T>> | 根據(jù)True或false進(jìn)行分區(qū) |
summarizingBy(...) | DoubleSummaryStatistics | 收集流(流中元素為Double類型)的統(tǒng)計(jì)值甚脉。如:平均值 |
joining(...) | String | 連接流中每個(gè)字符串 |
3.3.1 toList() 將流轉(zhuǎn)換成List
//toList()
@Test
public void test20(){
List<Integer> list = employees.stream().map(Employee::getAge).collect(Collectors.toList());
list.forEach(System.out::println);
}
3.3.2 toSet() 將流轉(zhuǎn)換成Set
//toSet()
@Test
public void test21(){
Set<Integer> collect = employees.stream().map(Employee::getAge).collect(Collectors.toSet());
collect.forEach(System.out::println);
}
3.3.3 toCollection() 將流轉(zhuǎn)換成其他類型的集合
將流轉(zhuǎn)換成HashSet
//toCollection()
@Test
public void test22(){
//換成HashSet
HashSet<Integer> collect = employees.stream().map(Employee::getAge).collect(Collectors.toCollection(HashSet::new));
collect.forEach(System.out::println);
}
3.3.4 counting()計(jì)算流中元素個(gè)數(shù)
//counting()
@Test
public void test23(){
//總數(shù)
Long collect = employees.stream().collect(Collectors.counting());
System.out.println(collect);
}
3.3.5 averagingDouble(...)計(jì)算流中元素Double類型的平均值
//averagingDouble()
@Test
public void test24(){
//平均值
Double collect1 = employees.stream().collect(Collectors.averagingDouble(Employee::getAge));
System.out.println(collect1);
}
3.3.6 summingDouble(...)計(jì)算流中元素Double類型的總和
IntSummaryStatistics里包含{count=, sum=, min=, average=, max=}這幾項(xiàng)需要哪個(gè)值,通過(guò)get獲取就行
summarizingInt
//summingDouble()
@Test
public void test25(){
//平均值
DoubleSummaryStatistics collect1 = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
IntSummaryStatistics collect = employees.stream().collect(Collectors.summarizingInt(Employee::getAge));
System.out.println(collect);
System.out.println(collect.getSum());
}
再來(lái)看下summarizingInt()
//summarizingInt()
@Test
public void test26(){
//平均值 IntSummaryStatistics里包含{count=5, sum=182, min=8, average=36.400000, max=68}這幾項(xiàng)需要哪個(gè)獲取就行
IntSummaryStatistics collect = employees.stream().collect(Collectors.summarizingInt(Employee::getAge));
System.out.println(collect);
//從IntSummaryStatistics里取sum
System.out.println(collect.getSum());
}
執(zhí)行結(jié)果
IntSummaryStatistics{count=5, sum=182, min=8, average=36.400000, max=68}
182
Collectors.summingLong()同上一樣铆农,傳輸傳入Long型即可牺氨。
3.3.7 minBy(...)根據(jù)比較器選擇最小值
//minBy()
@Test
public void test28(){
Optional<Employee> min = employees.stream().collect(Collectors.minBy((x, y) -> Integer.compare(x.getAge(), y.getAge())));
System.out.println(min.get());
}
3.3.8 maxBy(...)根據(jù)比較器選擇最小值
//maxBy()
@Test
public void test27(){
Optional<Employee> max = employees.stream().collect(Collectors.maxBy((x, y) -> Integer.compare(x.getAge(), y.getAge())));
System.out.println(max.get());
//等價(jià)于這種形式
Optional<Employee> max1 = employees.stream().max((x, y) -> Integer.compare(x.getAge(), y.getAge()));
System.out.println(max1.get());
}
3.3.9 groupingBy(...)根據(jù)某屬性的值對(duì)流分組,屬性為K墩剖,結(jié)果為V
分組分為一級(jí)分組和多級(jí)分組猴凹,下面例子分別進(jìn)行了演示:
一級(jí)分組的例子
//groupingBy()一級(jí)分組
@Test
public void test29(){
Map<Employee.Status, List<Employee>> collect = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(collect);
}
運(yùn)行結(jié)果
{
BUSY=[Employee{name='張三', age=68, salary=9000.0}, Employee{name='李四', age=38, salary=8000.0}, Employee{name='趙六', age=18, salary=3000.0}],
VOCATION=[Employee{name='王五', age=50, salary=4000.0}],
FREE=[Employee{name='田七', age=8, salary=1000.0}]
}
多級(jí)分組的例子
//多級(jí)分組
@Test
public void test30(){
Map<Employee.Status,Map<String,List<Employee>>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus,Collectors.groupingBy(e -> {
if(e.getAge()<35){
return "青年";
}else if(e.getAge()<50){
return "中年";
}else {
return "老年";
}
})));
System.out.println(map);
}
運(yùn)行結(jié)果
{
FREE={青年=[Employee{name='田七', age=8, salary=1000.0}]},
BUSY={青年=[Employee{name='趙六', age=18, salary=3000.0}], 老年=[Employee{name='張三', age=68, salary=9000.0}], 中年=[Employee{name='李四', age=38, salary=8000.0}]},
VOCATION={老年=[Employee{name='王五', age=50, salary=4000.0}]}
}
3.3.10 partitioningBy(...)根據(jù)True或false進(jìn)行分區(qū)
//partitioningBy()
@Test
public void test31(){
Map<Boolean, List<Employee>> map = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 5000));
System.out.println(map);
}
運(yùn)行結(jié)果
{
false=[Employee{name='王五', age=50, salary=4000.0}, Employee{name='趙六', age=18, salary=3000.0}, Employee{name='田七', age=8, salary=1000.0}],
true=[Employee{name='張三', age=68, salary=9000.0}, Employee{name='李四', age=38, salary=8000.0}]
}
3.3.12 joining(...)連接流中每個(gè)字符串
//joining()
@Test
public void test32(){
String collect = employees.stream().map(Employee::getName)
.collect(Collectors.joining(","));
System.out.println(collect);
}
運(yùn)行結(jié)果
張三,李四,王五,趙六,田七
至此,我們Stream的基本操作差不多就結(jié)束了岭皂,我們只需要多加練習(xí)郊霎,熟練使用就能提高效率啦。