08.Stream API 15个常用方法【重要】
集合处理数据的弊端
当我们需要对集合的元素进行操作的时候,除了必须的添加、删除、获取外,最典型的就是集合的遍历。
下面代码针对于我们不同的需求,总是会一次次的循环遍历。
【案例】传统方式实现:查询集合中符合条件的元素并遍历出来
package com.lzh;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author:kaiyang.cui
* @Package:com.lzh
* @Project:jdk8
* @name:StreamAPITests
* @Date:2023/4/3 下午4:31
* @Filename:StreamAPITests
* @Description:StreamAPITests
* @Version:1.0
*/
public class StreamAPITests {
@Test
public void test1() {
// 定义一个List集合
List<String> list = Arrays.asList("安欣", "高启强", "高启盛","小兰","高大");
List<String> listSave = new ArrayList<>();
// 获取所有 姓高的信息
for (String s : list) {
if(s.startsWith("高")){
listSave.add(s);
}
}
//2. 在listSave集合中再筛选获取名称长度为2的用户
List<String> listSave2 = new ArrayList<>();
for (String s : listSave) {
if(s.length() == 2){
listSave2.add(s);
}
}
// 3. 输出所有符合条件的用户信息
for (String s : listSave2) {
System.out.println(s);
}
}
}
Result:
高大
使用JDK Stream API优雅的改写
为了解决新增的需求,使用传统的集合操作会一遍一遍循环,这时我们希望有更加高效的处理方式,JDK8 Stream
应运而生。
【案例】JDK8 Stream API实现:查询集合中符合条件的元素并遍历出来
@Test
@DisplayName("JDK8 Stream API实现:查询集合中符合条件的元素并遍历出来")
public void test2() {
// 定义一个List集合
List<String> list = Arrays.asList("安欣", "高启强", "高启盛","小兰","高大");
list.stream()
.filter(s -> s.startsWith("高"))
.filter(s -> s.length() == 2)
.forEach(s -> System.out.println(s));
}
高大
【案例】JDK8 Stream API实现+ 方法引用
版:查询集合中符合条件的元素并遍历出来
@Test
@DisplayName("JDK8 Stream API实现+ 方法引用版:查询集合中符合条件的元素并遍历出来")
public void test3() {
// 定义一个List集合
List<String> list = Arrays.asList("安欣", "高启强", "高启盛","小兰","高大");
list.stream()
.filter(s -> s.startsWith("高"))
.filter(s -> s.length() == 2)
.forEach(System.out::println);
}
上面的StreamAPI代码的含义:获取流-过滤高,过滤长度,然后逐一打印。
StreamAPI核心思想
Stream API 能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复、统计、匹配和规约。
- 根据Collection获取
- 通过Stream的of方法
根据Collection获取
Collection 集合获取流。
首先,java.util.Collection
接口中加入了default方法 stream,也就是说Collection接口下的所有的实现都可以通过stream方法来获取。
@Test
@DisplayName("根据Collection获取Stream流")
public void test4() {
List<String> list = new ArrayList<>();
list.stream();
Set<String> set = new HashSet<>();
set.stream();
Vector<String> vector = new Vector<>();
vector.stream();
}
但是map接口并没有实现Collection接口,这时怎么办?
这时我们可以根据Map获取对应的key value的集合。
@Test
@DisplayName("Map获取流的方式")
public void test5() {
Map<String, Object> map = new HashMap<>();
Stream<String> stream = map.keySet().stream();
Stream<Object> stream1 = map.values().stream();
Stream<Map.Entry<String, Object>> stream2 = map.entrySet().stream();
}
通过Stream的of方法
数组获取流。
我们在实际开发中不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of
@Test
@DisplayName("将一个字符串数组转换成一个Stram流")
public void test6() {
Stream<String> a1 = Stream.of("a1", "b1", "c1", "d1");
String[] arr1 = {"a1", "b1", "c1", "d1"};
Stream<String> arr11 = Stream.of(arr1);
Integer[] arr2 = {1,2,3,4,5,6,7,8};
Stream<Integer> arr21 = Stream.of(arr2);
arr21.forEach(System.out::println);
// 基本数据类型是不可以的,不可以得到想要的遍历结果,应该是用int的包装类Integer
int[] arr3 = {1,2,3,4,5,6,7,8};
Stream.of(arr3).forEach(System.out::println);
}
Result:
1
2
3
4
5
6
7
8
[I@77846d2c
Stream 中常用的方法
**终结方法:**返回值不再是Stream类型的方法,不再支持链式调用。如:count和forEach。
**非终结方法:**返回值类型仍然是Stream类型的方法,支持链式调用。
注意事项
- Stream只能操作一次
- Stream方法返回的是新的流
- Stream不调用终结方法,中间的操作不会执行
【案例】演示:必须要有终结方法才可以完成流的操作
@Test
@DisplayName("演示:必须要有终结方法才可以完成流的操作")
public void test7() {
Stream<String> stream = Stream.of("a1", "a2", "a2");
stream.filter(s -> {
System.out.println("--------------------------------");
return s.contains("a");
}).forEach(System.out::println);
}
Stream所有的方法
1. forEach方法
forEach用来遍历流中的数据
【案例】演示:forEach方法
@Test
@DisplayName("演示:forEach方法")
public void test8() {
String str[] = {"a1","a2","a3"};
Stream.of(str).forEach(System.out::println);
}
2.count方法
Stream流中的count方法用来统计其中的元素的个数。
count方法返回值是long类型的,代表元素的个数。
【案例】演示:count方法
@Test
@DisplayName("演示:count方法")
public void test9() {
String str[] = {"a1","a2","a3"};
long count = Stream.of(str).count();
System.out.println("元素个数是count = " + count);
}
Result:
元素个数是count = 3
3. filter方法
filter方法的作用是用来过滤数据的,返回符合条件的数据。
filter方法的返回值是一个字集Stream流,返回的是新的流,不调用终结方法,中间的操作不会执行!!
该接口接收一个predicate函数式参数,作为筛选条件。
【案例】演示:filter方法
@Test
@DisplayName("演示:filter方法")
public void test10() {
String str[] = {"a1","a2","a3","b1"};
Stream.of(str).filter(s->s.contains("1")).forEach(System.out::println);
}
Result:
a1
b1
4.limit方法
limit方法可以对流进行截取处理,支持前n个数据。
参数是一个long类型的数值,如果集合当前长度大于参数就进行截取,否则不操作,报错
【案例】演示:limit方法截取数组中的元素的前3个
@Test
@DisplayName("演示:limit方法截取数组中的元素的前3个")
public void test11() {
String str[] = {"a1","a2","a3","b1"};
Stream.of(str).limit(3).forEach(System.out::println);
}
5.skip方法
如果希望跳过前面几个元素,可以使用skip方法获取一个截取之后的新流。
【案例】演示:skip方法跳过数组中的元素前3个
@Test
@DisplayName("演示:skip方法跳过数组中的元素前3个")
public void test12() {
String str[] = {"a1","a2","a3","b1"};
Stream.of(str).skip(3).forEach(System.out::println);
}
Result:
b1
6.map方法
如果我们需要将流中的元素映射到另一个流中,可以使用map方法。
该接口需要一个Function函数式接口参数,可以将当前流中的T类型的数据转换给R类型的数据。
【案例】演示:map方法将字符串转换为整形
@Test
@DisplayName("演示:map方法将字符串转换为整形")
public void test13() {
String str[] = {"1","2","3","4"};
Stream.of(str).map(msg -> Integer.parseInt(msg)).forEach(System.out::println);
}
Result:
1
2
3
4
改进方法引用
:类::静态方法
@Test
@DisplayName("演示:map方法将字符串转换为整形")
public void test13() {
String str[] = {"1","2","3","4"};
Stream.of(str).map(Integer::parseInt).forEach(System.out::println);
}
7.sorted方法
如果需要将数据排序,可以使用sorted方法。
【案例】演示:sorted方法升序排序(自然排序)
@Test
@DisplayName("演示:sorted方法升序排序")
public void test13() {
String str[] = {"3","4","3","1"};
Stream.of(str).map(Integer::parseInt).sorted().forEach(System.out::println);
}
演示:sorted方法降序排序
根据比较器指定排序规则。
@Test
@DisplayName("演示:sorted方法降序排序")
public void test14() {
String str[] = {"3","4","3","1"};
Stream.of(str).map(Integer::parseInt).sorted().sorted((o1,o2)->{return o2-o1;}).forEach(System.out::println);
}
Result:
4
3
3
1
8. distinct方法
如果要去掉重复数据,可以使用distinct方法
【案例】演示:distinct方法去重
@Test
@DisplayName("演示:distinct方法去重")
public void test15() {
String str[] = {"3","4","3","1"};
Stream.of(str).map(Integer::parseInt).distinct().forEach(System.out::println);
}
Result:
3
4
1
Stream 流中的distinct方法对于基本的数据类型是可以直接去重的,但是对于自定义类型,我们需要重写equals和hashCode方法。
【案例】演示:distinct方法去重-自定义类型
@Test
@DisplayName("演示:distinct方法去重-自定义类型")
public void test16() {
Stream.of(
new Person("张三",18),
new Person("张三",18),
new Person("李四",18),
new Person("张三",19),
new Person("王五",18)
).distinct().forEach(System.out::println);
}
Result:
Person(name=张三, age=18)
Person(name=李四, age=18)
Person(name=张三, age=19)
Person(name=王五, age=18)
9.match方法
如果需要判断数据是否匹配指定的条件,可以使用match相关的方法。
match相关方法都是终结方法
。
anyMatch---元素是否有任意一个满足条件
@Test
@DisplayName("演示:anyMatch")
public void test18() {
String str[] = {"1", "2", "3", "4", "5", "6","0","-1"};
boolean b = Stream.of(str).map(Integer::parseInt).anyMatch(s -> s > 0);
System.out.println("我只希望数组中有一个大于0就行 : " + b);
}
我只希望数组中有一个大于0就行 : true
allMatch---元素是否都满足条件
allMatch是终结方法。
【案例】演示:allMatch
@Test
@DisplayName("演示:allMatch")
public void test18() {
String str[] = {"1", "2", "3", "4", "5", "6","0","-1"};
boolean b = Stream.of(str).map(Integer::parseInt).allMatch(s -> s > 0);
System.out.println("数组都大于0吗? = " + b);
}
数组都大于0吗? = false
noneMatch---元素是否都不满足条件
【案例】演示:noneMatch
如果数组中所有的元素都小于-2为true,有一个不小于-2 就为false
@Test
@DisplayName("演示:noneMatch")
public void test18() {
String str[] = {"1", "2", "3", "4", "5", "6","0","-1"};
boolean b = Stream.of(str).map(Integer::parseInt).noneMatch(s -> s < -2);
System.out.println(b);
}
Result:
true
10.find方法
如果我们需要,找到某些数据,可以使用find方法来实现。
findFirst - findAny
【案例】演示:findFirst---findAny方法
@Test
@DisplayName("演示:findFirst---findAny方法")
public void test19() {
String str[] = {"3", "4", "5", "6","0","-1"};
Integer integer = Stream.of(str).map(Integer::parseInt).filter(x -> x % 2 == 0).findFirst().get();
System.out.println("integer = " + integer);
Integer integer2 = Stream.of(str).map(Integer::parseInt).filter(x -> x % 2 == 0).findAny().get();
System.out.println("integer2 = " + integer2);
}
11.max和min方法
获取最大值和最小值
【案例】演示:max-min方法
@Test
@DisplayName("演示:max-min方法")
public void test20() {
String str[] = {"3", "4", "5", "6","0","-1"};
Integer max = Stream.of(str).map(Integer::parseInt).max((o1, o2) -> o1 - o2).get();
System.out.println("max = " + max);
Integer min = Stream.of(str).map(Integer::parseInt).max((o1, o2) -> o2 - o1).get();
System.out.println("min = " + min);
}
12.reduce方法
如果需要将所有数据归纳得到一个数据,可以使用reduce方法。
1 T reduce(T identity, BinaryOperator<T> accumulator)
【案例】:演示:T reduce(T identity, BinaryOperator<T> accumulator)
identity是初始化默认值,第一次的时候会将默认值赋值给x,之后每次会将上一次的操作结果赋值给x,y就是每次从原始数组中获取的元素。
@Test
@DisplayName("演示:T reduce(T identity, BinaryOperator<T> accumulator);求和")
public void test21() {
String str[] = {"1", "2", "3", "4"};
Integer reduce = Stream.of(str).map(Integer::parseInt).reduce(0, (x, y) -> {
System.out.println("x =" + x +",y =" + y);
return x + y;
});
System.out.println("reduce = " + reduce);
}
Result:
x =0,y =1
x =1,y =2
x =3,y =3
x =6,y =4
reduce = 10
【案例】获取最大值
@Test
@DisplayName("演示:T reduce(T identity, BinaryOperator<T> accumulator); 求数组中最大值")
public void test22() {
String str[] = {"1", "2", "3", "4"};
Integer max = Stream.of(str).map(Integer::parseInt).reduce(0, (x, y) -> {
return x>y ? x:y;
});
System.out.println("max = " + max);
}
max = 4
13.map和reduce组合使用
在实际开发中,我们经常会将map和reduce一块来使用。
@Test
@DisplayName("演示:T reduce(T identity, BinaryOperator<T> accumulator); 求数组中age年龄的总和")
public void test23() {
Integer reduce = Stream.of(
new Person("张三", 18),
new Person("张三", 18),
new Person("李四", 18),
new Person("张三", 19),
new Person("王五", 18)
).map(p -> p.getAge()).reduce(0, (x, y) -> x + y);
System.out.println("reduce = " + reduce);
}
reduce = 91
【案例】演示:T reduce(T identity, BinaryOperator<T> accumulator); 方法引用版:求数组中age年龄的总和
咱就是说优雅用不过时!!!!!!!!!
@Test
@DisplayName("演示:T reduce(T identity, BinaryOperator<T> accumulator); 方法引用版:求数组中age年龄的总和")
public void test23() {
Integer reduce = Stream.of(
new Person("张三", 18),
new Person("张三", 18),
new Person("李四", 18),
new Person("张三", 19),
new Person("王五", 18)
).map(Person::getAge).reduce(0, Integer::sum);
System.out.println("reduce = " + reduce);
}
方法引用版:求数组中age年龄最大值
@Test
@DisplayName("演示:T 方法引用版:求数组中age年龄最大值")
public void test24() {
Integer ageMax = Stream.of(
new Person("张三", 18),
new Person("张三", 18),
new Person("李四", 18),
new Person("张三", 30),
new Person("王五", 18)
).map(Person::getAge).reduce(0, Integer::max);
System.out.println("ageMax = " + ageMax);
}
map 实现数据类型的转换,符合reduce对数据的要求。 reduce实现数据的处理。
@Test
@DisplayName("演示:方法引用版:统计字符a 出现的次数")
public void test25() {
String str[] = {"a","b","c","d","e","f","a"};
Integer result = Stream.of(str).map(ch -> "a".equals(ch) ? 1 : 0).reduce(0, Integer::sum);
System.out.println("a出现的次数是 = " + result);
}
Result:
a出现的次数是 = 2
14.mapToInt方法
如果需要将Stream中的Integer类型转换成int类型,可以使用mapToInt方法来实现
@Test
@DisplayName("演示:mapToInt方法")
public void test26() {
Integer arr[] = {1,2,3,4,5};
Stream.of(arr).filter(x -> x>0).forEach(System.out::println);
System.out.println("-----------");
// 为了提高程序代码的效率,我们可以先将流中Integer数据转换为int数据,然后再操作
IntStream intStream = Stream.of(arr).mapToInt(Integer::intValue);
intStream.filter(i->i>3).forEach(System.out::println);
}
1
2
3
4
5
-----------
4
5
15.concat方法
如果有两个流,希望合并成一个流,可以使用Stream流的静态方法。
@Test
@DisplayName("演示:concat方法合并成一个流")
public void test27() {
Stream<String> a = Stream.of("a", "b", "c");
Stream<String> b = Stream.of( "d", "e", "f");
Stream.concat(a, b).forEach(System.out::println);
}
a
b
c
d
e
f