什麼是Stream?
是數據渠道,用於操作數據源(集合數組等)所生成的元素序列
“集合講的是數據,流講的是計算”
注意:
- Strram自己不會存儲元素。
- Stream不會改變源對象。相反,他們會返回一個持有結果的新Stream
- Stream操作時延遲執行的。這意味着他們會等到需要結果的時候才執行
Stream的三個操作步驟:
- 創建Stream
- 中間操作
- 終止操作(終端操作)
創建Stream
-
可以通過Collection系列集合提供的stream()或parallelStream()
-
stream():串行流
-
parallelStream():並行流
-
List<Object> list = new ArrayList<>(); Stream<Object> stream = list.stream();//得到一個流
-
-
通過Arrays中的靜態方法stream()獲取數組流
-
String[] emps = new String[10]; Stream<String> stream1 = Arrays.stream(emps);
-
-
通過Stream類中的靜態方法 of()
-
Stream<String> stream2 = Stream.of("aa", "bb", "cc"); //可變數組
-
-
創建無限流
-
迭代
-
Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2); //stream3.forEach(System.out::println); //按照一元運算的規律 從0開始,無限+2 //添加限制操作 限制只打印10個 stream3.limit(10).forEach(System.out::println);
-
-
生成
-
Stream<Double> generate = Stream.generate(Math::random); //生成隨機數 generate.limit(2).forEach(System.out::println);
-
-
創建實體類
public class Employee {
private String name;
private Integer age;
private double salary;
private Status status;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public Employee(String name, Integer age, double salary, Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
public Employee() {
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", status=" + status +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee2 = (Employee) o;
return Double.compare(employee2.salary, salary) == 0 &&
Objects.equals(name, employee2.name) &&
Objects.equals(age, employee2.age) &&
status == employee2.status;
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary, status);
}
public enum Status{
FREE, //空閒
BUSY, //忙
VOCATION //休假
}
}
中間操作與終止操作的一些說明
- 如果沒有終止操作,那麼中間操作不會執行
- 多箇中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,
- 否則中間操作不會執行,任何的處理
- 而在終止操作時一次性全部處理,成爲惰性求值
例子:
@Test
public void test3() {
Stream<Employee> stream = employees.stream()//創建流 獲取流
.filter((x) -> {
System.out.println("中間操作");
return x.getSalary() > 6000;
});
}
這段代碼沒有任何運行結果,因爲沒有終止操作。
中間操作
篩選與切片:
方法 | 介紹 |
---|---|
filte | 接收Lambda ,從流中排除某些元素 |
limit | 截斷流,使其元素不超過給定數量。 |
skip(n) | 跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit(n)互補 |
distinct | 篩選,通過流所生成元素的hashCode()和equals()去重複元素 |
filter
- 接收Lambda ,從流中排除某些元素
@Test
public void test2() {
employees.stream()//創建流 獲取流
.filter((x) -> x.getSalary() > 6000)
//入參斷言型接口 boolean TraderTest(T t);
.forEach(System.out::println); //終止操作
}
結果:
Employee{name='張三', age=18, salary=9999.99, status=FREE}
Employee{name='趙六', age=36, salary=6666.66, status=BUSY}
Employee{name='趙六', age=36, salary=6666.66, status=FREE}
Employee{name='田七', age=12, salary=8888.88, status=BUSY}
limit
/**
* limit —— 截斷流,使其元素不超過給定數量。
*/
@Test
public void test5() {
employees.stream()
.filter((x) ->
{
System.out.println("短路");
return x.getSalary() > 6000;
}
)
.limit(2) //篩選完後只打印兩個
.forEach(System.out::println);
//這裏找到符合條件的兩個就停止,後續的操作就沒有了,
//說明所有的中間操作都是同步進行的,增加了效率
}
結果:
短路
Employee{name='張三', age=18, salary=9999.99, status=FREE}
短路
短路
短路
Employee{name='趙六', age=36, salary=6666.66, status=BUSY}
skip(n)
/**
* skip(n) —— 跳過元素,返回一個扔掉了前n個元素的流。
* 若流中元素不足n個,則返回一個空流。與limit(n)互補
*/
@Test
public void test6() {
employees.stream()
.filter((x) ->
{
System.out.println("skip");
return x.getSalary() > 6000;
}
)
.skip(2)
.forEach(System.out::println);
}
結果:
skip
skip
skip
skip
skip
Employee{name='趙六', age=36, salary=6666.66, status=FREE}
skip
Employee{name='田七', age=12, salary=8888.88, status=BUSY}
distinct
/**
* distinct —— 篩選,通過流所生成元素的hashCode()和equals()去重複元素
*
* 需要重寫hashCode和equals
*/
@Test
public void test7() {
employees.stream()
.distinct()
.forEach(System.out::println);
}
結果:
Employee{name='張三', age=18, salary=9999.99, status=FREE}
Employee{name='李四', age=58, salary=5555.55, status=VOCATION}
Employee{name='王五', age=26, salary=3333.33, status=VOCATION}
Employee{name='趙六', age=36, salary=6666.66, status=BUSY}
Employee{name='趙六', age=36, salary=6666.66, status=FREE}
Employee{name='田七', age=12, salary=8888.88, status=BUSY}
映射
方法 | 介紹 |
---|---|
map | 接收Lambda,將元素轉換成其他形式或提取信息。接收一個函數作爲參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。 |
flatMap | 接收一個函數作爲參數,將流中的每個值都換成另一個流,然後把所有的流連成一個流 |
map
/**
* map——接收Lambda,將元素轉換成其他形式或提取信息。接收一個函數作爲參數,
* 該函數會被應用到每個元素上,並將其映射成一個新的元素。
*/
@Test
public void test8(){
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
list.stream()
.map((x) -> x.toUpperCase()) //把集合所有內容轉大寫
.forEach(System.out::println);
//AAA
//BBB
//CCC
//DDD
System.out.println("---------------------");
employees.stream()
.map((x) -> x.getName())
.forEach(System.out::println);
//張三
//李四
//王五
//趙六
//趙六
//田七
}
flatMap
先聲明一個方法
//穿進去一個字符串,把一個字符串中所有的字符一個一個提取出來
static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character character : str.toCharArray()) {
list.add(character);
}
return list.stream();
}
使用flapmap
/**
* flatMap——接收一個函數作爲參數,將流中的每個值都換成另一個流,然後把所有的流連成一個流
*/
@Test
public void test9(){
System.out.println("\n\n---------普通字符串---------");
//普通字符串
String str = "helloworld";
filterCharacter(str).forEach(System.out::print);
//helloworld
System.out.println("\n\n---------集合---------");
//集合
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
Stream<Stream<Character>> stream = list.stream()
.map(demo1::filterCharacter); //{{a,a,a},{b,b,b},{c,c,c},...}
//得到的是流的集合,集合裏還是個流,然後二層遍歷
stream.forEach(
(x) -> x.forEach(System.out::print)
);
//結果:aaabbbcccddd
System.out.println("\n\n---------flatMap---------");
//扁平化,平鋪的意思
Stream<Character> stream1 = list.stream()//{a,a,a,b,b,b,c,c,c,...}
.flatMap((x) -> filterCharacter(x));
stream1.forEach(System.out::print);
//結果:aaabbbcccddd
}
map和flatmap的區別:
- map是把多個流放入一個流 形成{ { }, { }, { } }的形式
- flapmap是把每個流中的元素,作爲單獨的元素加入到新的流中
- 相當於集合add() 和 addAll()方法
測試集合add() 和 addAll()方法
/**
* function:測試集合add() 和 addAll()方法
*/
@Test
public void test10(){
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
List list2 = new ArrayList<>();
list2.add(111);
list2.add(222);
list2.add(list);
System.out.println(list2);
//結果:[111, 222, [aaa, bbb, ccc, ddd]]
List list3 = new ArrayList<>();
list3.add(111);
list3.add(222);
list3.addAll(list);
System.out.println(list3);
//結果:[111, 222, aaa, bbb, ccc, ddd]
}
結論:
- add是整個集合傳進來
- addAll()是將集合中的元素拆分,作爲單獨的元素傳進來
- 所以map相當於把流加到當前的流中
- flatMap相當於把流中的元素,加到新流中
排序
方法 | 介紹 |
---|---|
sorted() | 自然排序 |
sorted(Comparator com) | 定製排序 |
sorted()
/**
排序
sorted() 自然排序
sorted(Comparator com) 定製排序
*/
@Test
public void test11(){
List<String> list = Arrays.asList("eee","ccc","bbb","aaa","ddd");
list.stream()
.sorted()
.forEach(System.out::println);
//結果:
//aaa
//bbb
//ccc
//ddd
//eee
}
sorted(Comparator com)
/**
* sorted(Comparator com) 定製排序
*/
@Test
public void test111(){
List<String> list = Arrays.asList("eee","ccc","bbb","aaa","ddd");
list.stream()
.sorted((x,y) -> -x.compareTo(y))
.forEach(System.out::println);
//結果:
//eee
//ddd
//ccc
//bbb
//aaa
//定製排序:按照年齡排序,年齡一樣按照姓名排
employees.stream()
.sorted((x,y) -> {
if(x.getAge().equals(y.getAge())){
return x.getName().compareTo(y.getName());
} else {
return x.getAge().compareTo(y.getAge());
}
}).forEach(System.out::println);
}
結果:
Employee{name='田七', age=12, salary=8888.88, status=BUSY}
Employee{name='張三', age=18, salary=9999.99, status=FREE}
Employee{name='王五', age=26, salary=3333.33, status=VOCATION}
Employee{name='趙六', age=36, salary=6666.66, status=BUSY}
Employee{name='趙六', age=36, salary=6666.66, status=FREE}
Employee{name='李四', age=58, salary=5555.55, status=VOCATION}
終止操作
查找與匹配
方法 | 介紹 |
---|---|
allMatch(Predicate p) | 檢查是否匹配所有元素 |
anyMatch(Predicate p) | 檢查是否至少匹配一個元素 |
noneMatch(Predicate p) | 檢查是否沒有匹配所有元素 |
findFirst() | 返回第一個元素 |
findAny() | 返回當前流中的任意元素 |
count() | 返回流中元素的總個數 |
max() | 返回流中最大值 |
min() | 返回流中最小值 |
allMatch(Predicate p)
anyMatch(Predicate p)
noneMatch(Predicate p)
findFirst()
findAny()
/**
allMatch(Predicate p)
anyMatch(Predicate p)
noneMatch(Predicate p)
findFirst()
findAny()
*/
@Test
public void test12(){
//判斷是否所有員工都是BUSY狀態
boolean b = employees.stream()
.allMatch((x) -> x.getStatus().equals(Employee.Status.BUSY));
System.out.println("b = " + b); //b = false
//判斷是否至少有一位員工是BUSY狀態
boolean b1 = employees.stream()
.anyMatch((x) -> x.getStatus().equals(Employee.Status.BUSY));
System.out.println("b1 = " + b1);//b1 = true
//判斷是否沒有休假的員工
boolean b2 = employees.stream()
.noneMatch((x) -> x.getStatus().equals(Employee.Status.VOCATION));
System.out.println("b2 = " + b2);//b2 = false
//返回工資最低的員工 findFirst()
Optional<Employee> first = employees.stream()
.sorted((x,y) -> Double.compare(x.getSalary(),y.getSalary()))
.findFirst();
System.out.println("first = " + first.get());
//first = Employee{name='王五', age=26, salary=3333.33, status=VOCATION}
//任意找一個空閒狀態的員工(串行流,挨個兒找)
Optional<Employee> any = employees.stream()
.filter((x) -> x.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println("any = " + any.get());
//any = Employee{name='趙六', age=36, salary=6666.66, status=FREE}
//任意找一個空閒狀態的員工(並行流,多個線程一起找,誰先找到算誰)
Optional<Employee> any2 = employees.parallelStream()
.filter((x) -> x.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println("any2 = " + any2.get());
//any = Employee{name='趙六', age=36, salary=6666.66, status=FREE}
}
count()
max()
min()
/**
* count() 返回流中元素的總個數
* max() 返回流中最大值
* min() 返回流中最小值
*/
@Test
public void test13(){
long count = employees.stream()
.count();
System.out.println("count = " + count); //count = 6
//獲取工資最高的
Optional<Employee> max = employees.stream()
.max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
System.out.println("max = " + max.get().getSalary()); //max = 9999.99
//獲取工資最低的
Optional<Double> min = employees.stream()
.map(Employee::getSalary)
.min(Double::compareTo);
System.out.println("min = " + min.get()); //min = 3333.33
}
歸約
- 可以將流中元素反覆結合起來,得到一個值
方法 | 介紹 |
---|---|
reduce(T identity, BinaryOperator) | 返回T identity 起始值 BinaryOperator二元運算 |
reduce(BinaryOperator) | 返回Optional |
reduce(T identity, BinaryOperator)
reduce(BinaryOperator)
/**
* function:
*/
@Test
public void test14(){
List<Integer> list = Arrays.asList(1,2,3,4,5);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println("sum = " + sum); //sum = 15
Integer sum2 = list.stream()
.reduce(10, (x, y) -> x + y);
System.out.println("sum2 = " + sum2); //sum2 = 25
//首先將起始值作爲x,然後從流中取出一個元素給y,
//然後把運算的結果給x,再從集合中取值運算,直到最後
System.out.println("----------工資的總和-------------");
Optional<Double> sumSalary = employees.stream()
.map((x) -> x.getSalary())
.reduce((x, y) -> x + y);
System.out.println("sumSalary= " + sumSalary.get());
//sumSalary = 41111.07
}
這裏想一下,爲什麼上面的返回值是原始值,而工資的返回值是Optional
上面的有初始值,一定不會爲空,,而下面的工資沒有初始值,可能爲空
Optional的原理就是,只要返回的對象有可能爲空,那麼就封裝到Optional中去
備註:map和reduce的連接稱爲map-reduce模式,因Google用它類進行網絡搜索而出名
收集
collect(Collector c)——將流轉換爲其他形式。接收一個Collector接口的實現,用於給Stream中元素做彙總的方法
Collector接口中方法的實現決定了如何對流執行收集操作(如蒐集到List,Set,Map)
但是Collectors實用類提供了很多靜態方法,可以方便地創建常見收集器實例,
Collectors.toList()
/**
* function:把當前公司員工的名字提取出來,並放到一個集合中
*/
@Test
public void test15(){
//收集到list集合中
List<String> list = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println("list = " + list);
//list = [張三, 李四, 王五, 趙六, 趙六, 田七]
//收集到set集合中,可去重
Set<String> set = employees.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
System.out.println("set = " + set);
//set = [李四, 張三, 王五, 趙六, 田七]
//可以在Collectors.toCollection()中指定任意集合
HashSet<String> hashSet = employees.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));
System.out.println("hashSet = " + hashSet);
}
操作數字
/**
* function:操作數字
*/
@Test
public void test16(){
//總數
Long size = employees.stream()
.collect(Collectors.counting());
System.out.println("集合總數 = " + size);
//平均值
Double avg = employees.stream()
.collect(Collectors.averagingDouble((x) -> x.getSalary()));
System.out.println("工資平均值"+avg);
//計算總和
Double total = employees.stream()
.collect(Collectors.summingDouble((x) -> x.getSalary()));
System.out.println("工資總數: = " + total);
//最大值 方法一、
Optional<Employee> max = employees.stream()
.collect(Collectors.maxBy((x, y) -> Double.compare(x.getSalary(), y.getSalary())));
System.out.println("max = " + max.get());
//max = Employee{name='張三', age=18, salary=9999.99, status=FREE}
//最大值 方法二、
Optional<Employee> max1 = employees.stream()
.max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
System.out.println("max1 = " + max1.get());
//max = Employee{name='張三', age=18, salary=9999.99, status=FREE}
//最小值
Optional<Double> min = employees.stream()
.map((x) -> x.getSalary())
.collect(Collectors.minBy(Double::compare));
System.out.println("min = " + min.get());
//min = 3333.33
}
Collectors.groupingBy()
/**
* function: 分組
*/
@Test
public void test17(){
//按照狀態分組 返回map,key-狀態 value-狀態下的內容
//map沒有foreach方法
Map<Employee.Status, List<Employee>> map = employees.stream()
.collect(Collectors.groupingBy((x) -> x.getStatus()));
System.out.println("map = " + map);
}
結果:
map = {
BUSY=[Employee{name='趙六', age=36, salary=6666.66, status=BUSY}, Employee{name='田七', age=12, salary=8888.88, status=BUSY}],
VOCATION=[Employee{name='李四', age=58, salary=5555.55, status=VOCATION}, Employee{name='王五', age=26, salary=3333.33, status=VOCATION}],
FREE=[Employee{name='張三', age=18, salary=9999.99, status=FREE}, Employee{name='趙六', age=36, salary=6666.66, status=FREE}]
}
多級分組
/**
* function:多級分組
* 可以在Collectors.groupingBy後再跟一個Collectors.groupingBy
* 先按照狀態分,分完之後按照年齡分
*/
@Test
public void test18(){
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() > 35 && e.getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})
));
System.out.println("map = " + map);
/*
結果:
map = {
BUSY={
青年=[Employee{name='田七', age=12, salary=8888.88, status=BUSY}],
中年=[Employee{name='趙六', age=36, salary=6666.66, status=BUSY}]
},
VOCATION={
青年=[Employee{name='王五', age=26, salary=3333.33, status=VOCATION}],
老年=[Employee{name='李四', age=58, salary=5555.55, status=VOCATION}]
},
FREE={
青年=[Employee{name='張三', age=18, salary=9999.99, status=FREE}],
中年=[Employee{name='趙六', age=36, salary=6666.66, status=FREE}]
}
}
*/
}
分片,分區
/**
* function: 分片,分區
*
* 符合條件的一個區,不滿足條件的一個區
*/
@Test
public void test19(){
Map<Boolean, List<Double>> map = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.partitioningBy((x) -> x > 6000));
System.out.println(map);
//結果:
//{
// false=[5555.55, 3333.33],
// true=[9999.99, 6666.66, 6666.66, 8888.88]
// }
}
Collectors.summarizingDouble
/**
* function: 計算數字總和平均值最大最小
*/
@Test
public void test20(){
DoubleSummaryStatistics dss = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
double sum = dss.getSum();
System.out.println("sum = " + sum); //sum = 41111.07
double max = dss.getMax();
System.out.println("max = " + max); //max = 9999.99
}
joining()
/**
* function:連接 joining
*/
@Test
public void test21(){
//連接字符串
String collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining());
System.out.println("collect = " + collect);
//collect = 張三李四王五趙六趙六田七
//還可以加逗號,去除前後的逗號
String collect1 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(","));
System.out.println("collect1 = " + collect1);
//collect1 = 張三,李四,王五,趙六,趙六,田七
//如果要首尾添加
String collect2 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(",", "*****", "###"));
System.out.println("collect2 = " + collect2);
//collect2 = *****張三,李四,王五,趙六,趙六,田七###
}