Stream
一:相关知识理解
Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。
Stream可以由数组或集合创建,对流的操作分为两种:
- 中间操作,每次返回一个新的流,可以有多个。
- 终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
另外,Stream有几个特性:
- stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
- stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
- stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
stream和parallelStream的简单区分: stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。
如果流中的数据量足够大,并行流可以加快处速度。除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流:
Optional<Integer> findFirst = list.stream().parallel().filter(x->x>6).findFirst();
二:实战
1 创建流
/*通过 java.util.Collection.stream() 方法用集合创建流*/
List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
/*使用Stream的静态方法:of()、iterate()、generate()*/
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);
//0 3 6 9
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);
//0.6796156909271994 0.1914314208854283 0.8116932592396652
2 遍历/匹配(foreach/find/match)
// 遍历输出符合条件的元素
list.stream().filter(x -> x > 6).forEach(System.out::println);
// 匹配第一个
Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
// 匹配任意(适用于并行流)
Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).findAny();
// 是否包含符合特定条件的元素
boolean anyMatch = list.stream().anyMatch(x -> x < 6);
System.out.println("匹配第一个值:" + findFirst.get());
System.out.println("匹配任意一个值:" + findAny.get());
System.out.println("是否存在大于6的值:" + anyMatch);
3 筛选(filter)
//筛选出Integer集合中大于7的元素,并打印出来
stream.filter(x -> x > 7).forEach(System.out::println);
//筛选员工中工资高于8000的人,并形成新的集合
List<String> fiterList = personList.stream().filter(x -> x.getSalary() > 8000).map(Person::getName)
.collect(Collectors.toList());
4聚合(max/min/count)
//获取String集合中最长的元素。
Optional<String> max = list.stream().max(Comparator.comparing(String::length));
System.out.println("最长的字符串:" + max.get());
//获取Integer集合中的最大值。
// 自然排序
Optional<Integer> max = list.stream().max(Integer::compareTo);
// 自定义排序
Optional<Integer> max2 = list.stream().max(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
System.out.println("自然排序的最大值:" + max.get());
System.out.println("自定义排序的最大值:" + max2.get());
}
//获取员工工资最高的人。
Optional<Person> max = personList.stream().max(Comparator.comparingInt(Person::getSalary));
//计算Integer集合中大于6的元素的个数。
long count = list.stream().filter(x -> x > 6).count();
5映射(map/flatMap)
映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:
- map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
- flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
//英文字符串数组的元素全部改为大写。整数数组每个元素+3。
List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());
List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList());
//将员工的薪资全部增加1000。
List<Person> personListNew2 = personList.stream().map(person -> {
person.setSalary(person.getSalary() + 10000);
return person;
}).collect(Collectors.toList());
//将两个字符数组合并成一个新的字符数组。
List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
List<String> listNew = list.stream().flatMap(s -> {
// 将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
}).collect(Collectors.toList());
6 归约(reduce)
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作
//求Integer集合的元素之和、乘积和最大值。
// 求和方式1
Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
// 求和方式2
Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
// 求和方式3
Integer sum3 = list.stream().reduce(0, Integer::sum);
// 求最大值写法2
Integer max2 = list.stream().reduce(1, Integer::max);
7收集(collect)
collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。
collect主要依赖java.util.stream.Collectors类内置的静态方法。
//toList、toSet和toMap:
List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());
Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
.collect(Collectors.toMap(Person::getName, p -> p));
//
// 求总数
Long count = personList.stream().collect(Collectors.counting());
// 求平均工资
Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
// 求最高工资
Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
// 求工资之和
Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
// 一次性统计所有信息
DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
//DoubleSummaryStatistics{count=3, sum=23700.000000,min=7000.000000, average=7900.000000, max=8900.000000}
8分组(partitioningBy/groupingBy)
- 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
- 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
// 将员工按薪资是否高于8000分组
Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
// 将员工按性别分组
Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
// 将员工先按性别分组,再按地区分组
Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
//多字段组合去重
List<CpsFinanceProductExport> distinctFinanceProductImportList = cpsFinanceProductImportList.stream().collect(
Collectors.collectingAndThen(
Collectors.toCollection(
() -> new TreeSet<>(
Comparator.comparing(
o -> o.getChekuanId() + ":" + o.getFormulaType() + ":" + o.getPeriods() + ":" + o.getTag()))), ArrayList::new));
//判断两个list是否存在相同城市,返回重复的城市集合
List<String> collect = cityNameList.stream().filter(item -> cityNameListAll.stream().anyMatch(e -> Objects.equals(item, e))).collect(Collectors.toList());
9接合(joining)
joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
String names = personList.stream().map(p -> p.getName()).collect(Collectors.joining(","));
String string = list.stream().collect(Collectors.joining("-"));
10 排序(sorted)
sorted,中间操作。有两种排序:
- sorted():自然排序,流中元素需实现Comparable接口
- sorted(Comparator com):Comparator排序器自定义排序
// 按工资升序排序(自然排序)
List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
.collect(Collectors.toList());
// 按工资倒序排序
List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
.map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄升序排序。默认升序。工资相同的年龄小的在前
List<String> newList3 = personList.stream()
.sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
# .collect(Collectors.toList());
// 先按工资再按年龄自定义排序(降序)
List<String> newList4 = personList.stream().sorted((p1, p2) -> {
if (p1.getSalary() == p2.getSalary()) {
return p2.getAge() - p1.getAge();
} else {
return p2.getSalary() - p1.getSalary();
}
}).map(Person::getName).collect(Collectors.toList());
11 提取/组合
流也可以进行合并、去重、限制、跳过等操作。
// concat:合并两个流 distinct:去重
List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
// limit:限制从流中获得前n个数据
List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
// skip:跳过前n个数据
List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
集合常用代码
//1:判断是否为null
String demo = Optional.ofNullable(demoBO.getName()).orElse("未知"))
//2:String转list
List idList = Arrays.stream(Ids.split(",")).map(Integer::valueOf).collect(Collectors.toList());
//3:list转String
String ids = skuIds.stream().map(String::valueOf).collect(Collectors.joining(","));
String Tags = Joiner.on(",").join(TagIds.stream().distinct().collect(Collectors.toList()));
//4:Collections.emptyList()
如果一个方法需要返回一个空List,并且后续不用再新增元素进去,我们完全可以直接返回Collections.emptyList()而不是new ArrayList;这样不用每次都去创建一个新对象。Collections.emptyList()可能并不比new ArrayList节省多少空间。
但是毕竟开发了几年了,写代码不能像刚开始写Hello world那时一样吧。
//5:Collections.singletonList()
这个方法主要用于只有一个元素的优化,减少内存分配,无需分配额外的内存,可以从SingletonList内部类看得出来,由于只有一个element,因此可以做到内存分配最小化,相比之下ArrayList的DEFAULT_CAPACITY=10个。
List<Integer> ids = Collections.singletonList(demo.getId());
静态类中并没有重新add、delete、set等方法。所以通过Collections.singletonList初始化的List是不能执行上述方法的。
常用List集合工具类
1:String转List
/**
* @param str "1,2,3"
* @return [1, 2, 3]
*/
public static List<Integer> getIntegerListByStr(String str) throws NumberFormatException {
List<Integer> ids = Lists.newArrayList();
if (StringUtils.isNoneBlank(str)) {
String[] idRaws = StringUtils.split(str, ",");
for (String idRaw : idRaws) {
idRaw = idRaw.trim();
Integer id = Integer.parseInt(idRaw);
ids.add(id);
}
}
return ids;
}
2:list按照指定容量分批
List<List<Integer>> skuIdsList = ListUtils.partial(skuIds, 200);
public static <T> List<List<T>> partial(final List<T> whole, final int size) {
final int length = whole.size();
if (length == 0) {
return Lists.newArrayList();
}
final List<List<T>> results = new ArrayList<>(length / size + 1);
int fromIndex = 0;
while (true) {
int toIndex = fromIndex + size;
if (toIndex >= length) {
break;
}
results.add(whole.subList(fromIndex, toIndex));
fromIndex = toIndex;
}
results.add(whole.subList(fromIndex, length));
return results;
}
Map集合常用代码
//computeIfAbsent如果没有这个key,就生成一个新的map
Map<Integer, demoBO> map = demoMap.computeIfAbsent(demoBO.getCityId(), k -> Maps.newHashMap());
Map<Integer, String> map = new Hashtable<>();
map.put(1, "100RS");
map.put(2, "500RS");
map.put(3, "1000RS");
System.out.println("hashTable: "+ map.toString());
//hashTable:{3 = 1000RS,2 = 500RS,1 = 100RS}
// 不存在健值对的情况
// 使用computeIfAbsent方法
map.computeIfAbsent(4, k -> "600RS");
// 如果存在不会有任何影响
// key为1已经存在了
map.computeIfAbsent(1, k -> "800RS");
System.out.println("new hashTable: " + map);
//new hashTable:{4 = 600RS,3 = 1000RS,2 = 500RS,1 = 100RS}