如果有任何问题请指出,感谢。
对集合进行处理操作的方法
stream流的特性如下:
Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。
这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。
Stream不保存数据,故每个Stream流只能使用一次。
stream流的方法有很多中,大致分为延迟方法和终结方法。Stream流属于管道流,只能被消费(使用)一次
第一个stream流调用完毕方法,数据就会流转到下一个Stream上(如果没有下一个流则直接关闭)
而这时第一个stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法延迟方法 :返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
终结方法 :返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调
用。终结方法包括 count 和 forEach 方法
public static void main(String[] args) throws InterruptedException {
// 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
// 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
// 1.拿到所有姓张的
ArrayList<String> zhangList = new ArrayList<>(); // {"张无忌", "张强", "张三丰"}
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
// 2.拿到名字长度为3个字的
ArrayList<String> threeList = new ArrayList<>(); // {"张无忌", "张三丰"}
for (String name : zhangList) {
if (name.length() == 3) {
threeList.add(name);
}
}
// 3.打印这些数据
for (String name : threeList) {
System.out.println(name);
}
}
结果
张无忌
张三丰
public static void main(String[] args) throws InterruptedException {
// 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
// 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
// 1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
list.stream()
.filter((s) -> {
return s.startsWith("张");
})
.filter((s) -> {
return s.length() == 3;
})
.forEach((s) -> {
System.out.println(s);
});
System.out.println("-----------");
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
System.out.println("-----------");
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
System.out.println("-----------");
}
结果
张无忌
张三丰
张无忌
张三丰
张无忌
张三丰
可以看出相同情况下,Stream流对集合的处理更加的简单并且便于理解,Stream流通常与lambda表达式配合使用。
例子
public static void main(String[] args) {
// 方式1 : 根据Collection获取流
// Collection接口中有一个默认的方法: default Stream stream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String, String> map = new HashMap<>();
Stream<String> stream3 = map.keySet().stream();
Stream<String> stream4 = map.values().stream();
Stream<Map.Entry<String, String>> stream5 = map.entrySet().stream();
// 方式2 : Stream中的静态方法of获取流
// static Stream of(T... values)
Stream<String> stream6 = Stream.of("aa", "bb", "cc");
String[] strs = {"aa", "bb", "cc"};
Stream<String> stream7 = Stream.of(strs);
// 基本数据类型的数组行不行?不行的,会将整个数组看做一个元素进行操作.
int[] arr = {11, 22, 33};
Stream<int[]> stream8 = Stream.of(arr);
// 装箱后的数据类型的数组行不行?可以,操作的元素就是数组里面的每个元素
Integer[] ar = {11, 22, 33};
Stream<Integer> stream9 = Stream.of(ar);
}
几个注意点:
创建方式有 集合.stream(),Stream.of(元素集合)
基本数据类型的集合 如果创建流操作的是这个集合本身 也就是集合是一个元素。
装箱后的数据类型的集合 创建流操作的是这个集合里面的元素。
因为map不是集合,所有操作map只能先取出map的key或value集合 然后再进行流操作
例子
public static void main(String[] args) {
Stream<String> stream = Stream.of("aa", "bb", "cc");
// 1. Stream只能操作一次
// long count = stream.count();
// long count2 = stream.count();
// 2. Stream方法返回的是新的流
// Stream limit = stream.limit(1);
// System.out.println("stream" + stream);
// System.out.println("limit" + limit);
// 3. Stream不调用终结方法,中间的操作不会执行
stream.filter((s) -> {
System.out.println(s);
return true;
}).count();
}
注意点
上述例子中有三个注意点
Stream只能操作一次。这个的意思是指同一个集合的流只能被操作一次,一个流只能被连续操作 也就是链式编程,而不能再次调用这个流
Stream方法返回的是新的流。也就是Stream流处理后返回的流是新的流可以再被操作
Stream不调用终结方法,中间的操作不会执行。
补充终结方法
count(),用于记录集合元素个数
forEach(),遍历集合
**作用:**遍历集合元素
例子
@Test
public void testForEach() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
/*// 得到流
// 调用流中的方法
one.stream().forEach((String str) -> {
System.out.println(str);
});
// Lambda可以省略
one.stream().forEach(str -> System.out.println(str));*/
// Lambda可以转成方法引用
one.stream().forEach(System.out::println);
}
可以看出利用lambda表达式可以把遍历操作大幅度简化
**作用:**返回集合元素个数
例子
@Test
public void testCount() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
long count = one.stream().count();
System.out.println(count);
}
**作用:**过滤集合中不符合要求的元素
例子
@Test
public void testFilter() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
// 得到名字长度为3个字的人(过滤)
// filter(Predicate super T> predicate)
/*one.stream().filter((String s) -> {
return s.length() == 3;
}).forEach(System.out::println);*/
// one.stream().filter(s -> s.length() == 3).forEach(System.out::println);// 简化
}
**作用:**获取集合前n个元素
例子
@Test
public void testLimit() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
// 获取前3个数据
one.stream()
.limit(3)
.forEach(System.out::println);
}
**作用:**跳过前n个元素
例子
@Test
public void testSkip() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
// 跳过前两个数据
one.stream()
.skip(2)
.forEach(System.out::println);
}
**作用:**把一种数据类型映射为另一种数据类型
**例子:**把String类型映射为int类型
@Test
public void testMap() {
Stream<String> original = Stream.of("11", "22", "33");
// Map可以将一种类型的流转换成另一种类型的流
// 将Stream流中的字符串转成Integer
/*Stream stream = original.map((String s) -> {
return Integer.parseInt(s);
});*/
// original.map(s -> Integer.parseInt(s)).forEach(System.out::println);
original.map(Integer::parseInt).forEach(System.out::println);
}
**作用:**集合元素排序
例子
public void testSorted() {
// sorted(): 根据元素的自然顺序排序
// sorted(Comparator super T> comparator): 根据比较器指定的规则排序
Stream<Integer> stream = Stream.of(33, 22, 11, 55);
// stream.sorted().forEach(System.out::println);// 直接排序默认升序
/*stream.sorted((Integer i1, Integer i2) -> {
return i2 - i1;
}).forEach(System.out::println);*/
stream.sorted((i1, i2) -> i2 - i1).forEach(System.out::println);// 自定义排序
}
**作用:**去重
例子1
@Test
public void testDistinct() {
Stream<Integer> stream = Stream.of(22, 33, 22, 11, 33);
stream.distinct().forEach(System.out::println);
Stream<String> stream1 = Stream.of("aa", "bb", "aa", "bb", "cc");
stream1.distinct().forEach(System.out::println);
}
例子2:自定义类型的去重
@Test
public void testDistinct2() {
Stream<Person> stream = Stream.of(
new Person("貂蝉", 18),
new Person("杨玉环", 20),
new Person("杨玉环", 20),
new Person("西施", 16),
new Person("西施", 16),
new Person("王昭君", 25)
);
stream.distinct().forEach(System.out::println);
}
这里我们可以看到有一个Person类,如果我们不重写Person类的hashCode方法与equals方法的话是去重不了的
Person类代码
@Data
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
**作用:**匹配是否有符合的元素
例子
@Test
public void testMatch() {
Stream<Integer> stream = Stream.of(5, 3, 6, 1);
// boolean b = stream.allMatch(i -> i > 0); // allMatch: 匹配所有元素,所有元素都需要满足条件
// boolean b = stream.anyMatch(i -> i > 5); // anyMatch: 匹配某个元素,只要有其中一个元素满足条件即可
boolean b = stream.noneMatch(i -> i < 0); // noneMatch: 匹配所有元素,所有元素都不满足条件
System.out.println(b);
}
补充
- allMatch: 匹配所有元素,所有元素都需要满足条件
- anyMatch: 匹配某个元素,只要有其中一个元素满足条件即可
- noneMatch: 匹配所有元素,所有元素都不满足条件
**作用:**查询指定元素
例子
@Test
public void testFind() {
Stream<Integer> stream = Stream.of(33, 11, 22, 5);
Optional<Integer> first = stream.findFirst();
// Optional first = stream.findAny();
System.out.println(first.get());
}
补充
- findFirst方法的作用是返回集合中的第一个元素
- findAny方法的作用是返回集合中的任意一个元素,不一定是第一个
**作用:**查询最大、最小元素
例子
@Test
public void testMax_Min() {
// 获取最大值
// 1, 3, 5, 6
Optional<Integer> max = Stream.of(5, 3, 6, 1).max((o1, o2) -> o1 - o2);
System.out.println("最大值: " + max.get());
// 获取最小值
// 1, 3, 5, 6
Optional<Integer> min = Stream.of(5, 3, 6, 1).min((o1, o2) -> o1 - o2);
System.out.println("最小值: " + min.get());
}
**作用:**将所有数据归纳得到一个数据
例子
@Test
public void testReduce() {
// T reduce(T identity, BinaryOperator accumulator);
// T identity: 默认值
// BinaryOperator accumulator: 对数据进行处理的方式
// reduce如何执行?
// 第一次, 将默认值赋值给x, 取出集合第一元素赋值给y
// 第二次, 将上一次返回的结果赋值x, 取出集合第二元素赋值给y
// 第三次, 将上一次返回的结果赋值x, 取出集合第三元素赋值给y
// 第四次, 将上一次返回的结果赋值x, 取出集合第四元素赋值给y
int reduce = Stream.of(4, 5, 3, 9).reduce(0, (x, y) -> {
System.out.println("x = " + x + ", y = " + y);
return x + y;
});
System.out.println("reduce = " + reduce); // 21
// 获取最大值
Integer max = Stream.of(4, 5, 3, 9).reduce(0, (x, y) -> {
return x > y ? x : y;
});
System.out.println("max = " + max);
}
结果
reduce = 21
x = 0, y = 4
x = 4, y = 5
x = 9, y = 3
x = 12, y = 9
reduce = 21
max = 9
可以看出第一个求和的例子是 把返回值赋值给第一个参数 当前值赋值给第二个参数,完成累加
第二个例子是 把每次的返回值与当前值进行比较 返回值赋值给第一个参数 当前值赋值给第二个参数,完成求最大值
**作用:**有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
例子
@Test
public void testContact() {
Stream<String> streamA = Stream.of("张三");
Stream<String> streamB = Stream.of("李四");
// 合并成一个流
Stream<String> newStream = Stream.concat(streamA, streamB);
// 注意:合并流之后,不能操作之前的流啦.
// streamA.forEach(System.out::println);
newStream.forEach(System.out::println);
}
结果
张三
李四
**补充:**需要注意 两个流合并后 原先的流就不能再用了 因为原先的流已经关闭,流转到新的流了。
**作用:**把流转化为指定集合或者数组中
例子1:流转化为集合
@Test
public void testStreamToCollection() {
Stream<String> stream = Stream.of("aa", "bb", "cc", "bb");
// 将流中数据收集到集合中
// collect收集流中的数据到集合中
// List list = stream.collect(Collectors.toList());
// System.out.println("list = " + list);
// Set set = stream.collect(Collectors.toSet());
// System.out.println("set = " + set);
// 收集到指定的集合中ArrayList
// ArrayList arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
// System.out.println("arrayList = " + arrayList);
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
System.out.println("hashSet = " + hashSet);
}
**补充:**前两个例子没有指定具体的集合类型,后两个例子用toCollection方法 指定了具体的集合类型。
例子2:流转化为数组
@Test
public void testStreamToArray() {
Stream<String> stream = Stream.of("aa", "bb", "cc");
// 转成Object数组不方便
// Object[] objects = stream.toArray();
// for (Object o : objects) {
// System.out.println("o = " + o);
// }
// String[]
String[] strings = stream.toArray(String[]::new);
for (String string : strings) {
System.out.println("string = " + string + ", 长度: " + string.length());
}
}
**补充:**使用toArray方法可以把流转化为数组,但默认不指定具体类型转换为Object类。
例子3:其他收集流中数据的方式
@Test
public void testStreamToOther() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
// 获取最大值
// Optional max = studentStream.collect(Collectors.maxBy((s1, s2) -> s1.getSocre() - s2.getSocre()));
// System.out.println("最大值: " + max.get());
// 获取最小值
// Optional min = studentStream.collect(Collectors.minBy((s1, s2) -> s1.getSocre() - s2.getSocre()));
// System.out.println("最小值: " + min.get());
// 求总和
// Integer sum = studentStream.collect(Collectors.summingInt(s -> s.getAge()));
// System.out.println("总和: " + sum);
// 平均值
// Double avg = studentStream.collect(Collectors.averagingInt(s -> s.getSocre()));
// Double avg = studentStream.collect(Collectors.averagingInt(Student::getSocre));
// System.out.println("平均值: " + avg);
// 统计数量
Long count = studentStream.collect(Collectors.counting());
System.out.println("统计数量: " + count);
}
**补充:**可以看出collect可以实现很多常见的功能,如:最大值、最小值、求和、平均值、数量等
例子4:分组
@Test
public void testGroup() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33));
// Map> map = studentStream.collect(Collectors.groupingBy(Student::getAge));
// 将分数大于60的分为一组,小于60分成另一组
Map<String, List<Student>> map = studentStream.collect(Collectors.groupingBy((s) -> {
if (s.getSocre() > 60) {
return "及格";
} else {
return "不及格";
}
}));
map.forEach((k, v) -> {
System.out.println(k + "::" + v);
});
}
结果:
不及格::[Student{name=‘迪丽热巴’, age=56, socre=55}, Student{name=‘柳岩’, age=52, socre=33}]
及格::[Student{name=‘赵丽颖’, age=52, socre=95}, Student{name=‘杨颖’, age=56, socre=88}]
可以看出通过groupingBy方法可以实现根据规则进行分组
例子5:多级分组
@Test
public void testCustomGroup() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33));
// 先根据年龄分组,每组中在根据成绩分组
// groupingBy(Function super T, ? extends K> classifier, Collector super T, A, D> downstream)
Map<Integer, Map<String, List<Student>>> map = studentStream.collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy((s) -> {
if (s.getSocre() > 60) {
return "及格";
} else {
return "不及格";
}
})));
// 遍历
map.forEach((k, v) -> {
System.out.println(k);
// v还是一个map,再次遍历
v.forEach((k2, v2) -> {
System.out.println("\t" + k2 + " == " + v2);
});
});
}
结果:
52
不及格 == [Student{name=‘柳岩’, age=52, socre=33}]
及格 == [Student{name=‘赵丽颖’, age=52, socre=95}]
56
不及格 == [Student{name=‘迪丽热巴’, age=56, socre=55}]
及格 == [Student{name=‘杨颖’, age=56, socre=88}]
可以看出这个例子是先根据年龄分组 再根据分数分组
例子6:分区
@Test
public void testPartition() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33));
Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(s -> {
return s.getSocre() > 60;
}));
map.forEach((k , v) -> {
System.out.println(k + " :: " + v);
});
}
结果
false :: [Student{name=‘迪丽热巴’, age=56, socre=55}, Student{name=‘柳岩’, age=52, socre=33}]
true :: [Student{name=‘赵丽颖’, age=52, socre=95}, Student{name=‘杨颖’, age=56, socre=88}]
分区就是特别的分组,分区的key只有 false与true
例子7:拼接
@Test
public void testJoining() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
// 根据一个字符串拼接: 赵丽颖__杨颖__迪丽热巴__柳岩
// String names = studentStream.map(Student::getName).collect(Collectors.joining("__"));
// 根据三个字符串拼接
String names = studentStream.map(Student::getName).collect(Collectors.joining("__", "^_^", "V_V"));
System.out.println("names = " + names);
}
结果
names = ^ _ ^赵丽颖__ 杨颖 __ 迪丽热巴__柳岩V_V
可以看出这个方法的作用是在每一个字符串之前(也可以是前后) 拼接字符串
**优点:**parallelStream其实就是一个并行执行的流,效率更高
**缺点:**有线程安全问题
- 直接获取并行的流
- 将串行流转成并行流
代码
@Test
public void testgetParallelStream() {
// 掌握获取并行Stream流的两种方式
// 方式一:直接获取并行的Stream流
List<String> list = new ArrayList<>();
Stream<String> stream = list.parallelStream();
// 方式二:将串行流转成并行流
Stream<String> parallel = list.stream().parallel();
}
@Test
public void testParallel() {
Stream.of(4, 5, 3, 9, 1, 2, 6)
.parallel() // 转成并行流
.filter(s -> {
System.out.println(Thread.currentThread() + "::" + s);
return s > 3;
})
.count();
}
结果
Thread[main,5,main]::1
Thread[ForkJoinPool.commonPool-worker-3,5,main]::5
Thread[ForkJoinPool.commonPool-worker-15,5,main]::2
Thread[ForkJoinPool.commonPool-worker-9,5,main]::9
Thread[ForkJoinPool.commonPool-worker-11,5,main]::3
Thread[ForkJoinPool.commonPool-worker-7,5,main]::4
Thread[ForkJoinPool.commonPool-worker-5,5,main]::6
消耗时间:46
可以看出不同线程再操作这个流
// 并行的Stream : 消耗时间:137
@Test
public void testParallelStream() {
LongStream.rangeClosed(0, times).parallel().reduce(0, Long::sum);
}
// 串行的Stream : 消耗时间:260
@Test
public void testStream() {
// 得到5亿个数字,并求和
LongStream.rangeClosed(0, times).reduce(0, Long::sum);
}
// 使用for循环 : 消耗时间:162
@Test
public void testFor() {
int sum = 0;
for (int i = 0; i < times; i++) {
sum += i;
}
}
结果
可以看出并行流的效率更加优秀
// parallelStream线程安全问题
@Test
public void parallelStreamNotice() {
ArrayList<Integer> list = new ArrayList<>();
/*IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> {
list.add(i);
});
System.out.println("list = " + list.size());*/
// 解决parallelStream线程安全问题方案一: 使用同步代码块
/*Object obj = new Object();
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> {
synchronized (obj) {
list.add(i);
}
});*/
// 解决parallelStream线程安全问题方案二: 使用线程安全的集合
// Vector v = new Vector();
/*List synchronizedList = Collections.synchronizedList(list);
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> {
synchronizedList.add(i);
});
System.out.println("list = " + synchronizedList.size());*/
// 解决parallelStream线程安全问题方案三: 调用Stream流的collect/toArray
List<Integer> collect = IntStream.rangeClosed(1, 1000)
.parallel()
.boxed()
.collect(Collectors.toList());
System.out.println("collect.size = " + collect.size());
}
结果
如果不考虑线程安全问题 第一个例子的三次结果如下
list = 920
消耗时间:22list = 949
消耗时间:21list = 916
消耗时间:23
可以看出因为ArrayList与parallelStream是线程不安全的 所以不能保证每次都能成功的add,所以导致list的长度每次都不同
解决办法:
- 使用同步代码块,这个是最容易想到的方法
- 使用线程安全的集合,因为刚才出问题的原因是ArrayList是线程不安全的 所以我们只要换一个线程安全的集合就好
- 调用Stream流的collect/toArray