一、介绍
(1)概念
stream流操作是Java 8提供一个重要新特性,它允许开发人员以声明性方式处理集合,其核心类库主要改进了对集合类的
API和新增Stream操作。(2)使用场景
常用于集合对象的计算,与Lambda表达式结合,可以提高编程效率、间接性和程序可读性。
(3)实现原理
①stream不是一种数据结构,它只是某种数据源的一个视图(集合就相当于数据表中的数据,表中的数据就是元数据的每一个元素),数据源可以是一个数组,Java容器或I/O
channel等;
②stream流的操作过程遵循着创建 -->操作 -->获取结果的过程;
③Stream流支持序列与并行两种操作方式,对于现在调用的方法,本身都是一种高层次构件,与线程模型无关,线程和锁在Stream内部都已经做好了;
④在Stream中的操作每一次都会产生新的流,内部不会像普通集合操作一样立刻获取值,而是“惰性取值”,只有等到用户真正需要结果的时候才会执行。(4)优点
①代码简洁:函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环
②多核友好:Java函数式编程使得编写并行程序如此简单,就是调用一下方法,如果流中的数据量足够大,使用并行流可以加快处速度。
二、操作使用API
(1)流的创建
//1)Stream创建
Stream<String> stream1 = Stream.of("bad","habit","flowers");
//2)Collection集合创建(常用)
List<String> list = new ArrayList<>();
list.add("bad");
list.add("habit");
list.add("flowers");
Stream<String> stream2 = list.stream();
//并行流,默认是顺序流
Stream<String> stream3 = list.parallelStream();
//顺序流转换成并行流
Stream<String> stream4 = list.stream().parallel();
//3)Array数组创建
String[] arr = {"bad","habit","flowers"};
Stream<String> stream5 = Arrays.stream(arr);
//4)文件创建
try {
Stream<String> stream6 = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());
} catch (IOException e) {
e.printStackTrace();
}
//5)函数创建
//①iterator
// 1为初始值 n+1是函数计算操作 limit是限制大小
Stream<Integer> iterateStream = Stream.iterate(1, n -> n + 1).limit(5);
//②generator
Stream<Double> generateStream = Stream.generate(Math::random).limit(5);
(2)操作符使用
中间操作符:通常对于Stream的中间操作,可以视为是源的查询,并且是懒惰式的设计,对于源数据进行的计算只有在需要时才会被执行,与数据库中视图的原理相似
终端操作符:一个流有且只能有一个终端操作。Stream流执行完终端操作之后,无法再执行其他动作,否则会报状态异常,提示该流已经被执行操作或者被关闭,想要再次执行操作必须重新创建Stream流
1)中间操作符使用示例
@Data
@AllArgsConstructor
@ToString
class Animals implements Supplier<Animals> {
private String name;
private int num;
@Override
public Animals get() {
return null;
}
}
//数据准备
List<Integer> list1 = Arrays.asList(1,2,3,4,5,7,1,2,4);
List<Animals> list2 = Arrays.asList(
new Animals("panda",1),new Animals("monkey",12),new Animals("elephant",5)
,new Animals("tiger",3),new Animals("dolphin",4),new Animals("penguin",4)
);
//①filter:根据条件过滤
System.out.println("-----------filter start-----------");
list1.stream().filter(v -> v >= 3).forEach(System.out::println);
list2.stream().filter(v -> v.getNum() >= 3).forEach(System.out::println);
System.out.println("-----------filter end-----------");
//②distinct:去重
System.out.println("-----------distinct start-----------");
list1.stream().distinct().forEach(System.out::println);
System.out.println("-----------distinct end-----------");
//③sorted:排序
System.out.println("-----------sorted start-----------");
list1.stream().sorted().forEach(System.out::println);
list2.stream().sorted(Comparator.comparing(Animals::getNum)).forEach(System.out::println);
System.out.println("-----------sorted end-----------");
//④limit:截取至某长度的流(不够长则有多少取多少)
System.out.println("-----------limit start-----------");
list1.stream().limit(6).forEach(System.out::println);
list2.stream().limit(20).forEach(System.out::println);
System.out.println("-----------limit end-----------");
//⑤skip:获得去掉前几个元素后的流
System.out.println("-----------skip start-----------");
list1.stream().skip(3).forEach(System.out::println);
System.out.println("-----------skip end-----------");
//⑥peek:遍历处理
System.out.println("-----------peek start-----------");
list2.stream().peek(v -> v.setName("my"+v.getName())).forEach(System.out::println);
System.out.println("-----------peek end-----------");
//⑦map:对流中每一个元素进行处理后生成新的流
System.out.println("-----------map start-----------");
Stream<String> list2streamMap = list2.stream().map(Animals::getName);
list2streamMap.forEach(System.out::println);
System.out.println("-----------map end-----------");
//⑧flatMap:流扁平化,让你把一个流中的“每个值”都换成另一个流,然后把所有的流连接起来成为一个流
//与map本质区别:map是对一级元素进行操作,flatmap是对二级元素操作map返回一个值;flatmap返回一个流,多个值
//应用场景:map对集合中每个元素加工,返回加工后结果;flatmap对集合中每个元素加工后,做扁平化处理后(拆分层级,放到同一层)然后返回
System.out.println("-----------flatMap start-----------");
Stream<String> list2streamFlatMap = list2.stream().flatMap(v -> Arrays.stream(v.getName().split("a")));
list2streamFlatMap.forEach(System.out::println);
System.out.println("-----------flatMap end-----------");
2)终端操作符
//数据准备
List<Animals> list0 = Arrays.asList(
new Animals("panda",1),new Animals("monkey",12),new Animals("elephant",5)
,new Animals("tiger",3),new Animals("dolphin",4),new Animals("penguin",4)
);
//①collect:收集器,将流转换为其他形式
System.out.println("-----------collect start-----------");
Set set = list0.stream().collect(Collectors.toSet());
set.add(new Animals("viper",38));
set.stream().forEach(System.out::println);
System.out.println("-----------collect end-----------");
//②forEach:遍历流--略
//③findFirst/findAny:将返回当前流中的第一个/任意元素
System.out.println("----findAny---"+list0.stream().findAny().get());
//④count:返回流中元素总数
System.out.println("----count---"+list0.stream().filter(v -> v.getNum() > 10).count());
//⑤sum/max/min:求和/最大值/最小值
System.out.println("----sum---"+list0.stream().filter(v -> v.getNum() > 3).mapToInt(Animals::getNum).sum());
System.out.println("----max---"+list0.stream().max(Comparator.comparingInt(Animals::getNum)));
//⑥anyMatch/allMatch/noneMatch 检查是否 至少匹配一个/所有/没有匹配 所有元素,返回boolean
System.out.println("----anyMatch?panda---"+list0.stream().anyMatch(v -> v.getName().equals("panda")));
System.out.println("----allMatch?nameIsNOtNull---"+list0.stream().allMatch(v -> !v.getName().isEmpty()));
System.out.println("----noneMatch?baby---"+list0.stream().noneMatch(v -> v.getName().equals("baby")));
//⑦reduce:可以将流中元素反复结合起来,得到一个值
//这里的Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional reduce = list0.stream().reduce((animals1, animals2) -> {
return new Animals(animals1.getName()+" and "+animals2.getName(),animals1.getNum()+animals2.getNum());
});
if(reduce.isPresent()) System.out.println("----reduce---"+reduce.get());
(3)Collect收集器使用
介绍:Collector:结果收集策略的核心接口,具备将指定元素累加存放到结果容器中的能力;并在Collectors工具中提供了Collector接口的实现类
public static Animals test(String s){
System.out.println(s);
return new Animals(s,0);
}
//数据准备
List<Animals> list3 = Arrays.asList(
new Animals("panda",1),new Animals("monkey",12),new Animals("elephant",5)
,new Animals("tiger",3),new Animals("dolphin",4),new Animals("penguin",4)
);
//①toList/toMap/toSet:将指定数据存放到List/Map/set集合中
List<String> nameList = list3.stream().map(Animals::getName).collect(Collectors.toList()) ;
Set<String> nameSet = list3.stream().map(Animals::getName).collect(Collectors.toSet()) ;
Map<String,Integer> map = list3.stream().collect(Collectors.toMap(Animals::getName,Animals::getNum)) ;
//②counting:符合条件的数量
System.out.println("----counting---"+list3.stream().filter(v-> v.getNum()> 4).collect(Collectors.counting()));
//③summingInt:求和
System.out.println("----summingInt---"+list3.stream().filter(v-> v.getNum()> 4).collect(Collectors.summingInt(Animals::getNum)));
//④minBy:筛选元素中最小的数据
Optional optional = list3.stream().collect(Collectors.minBy(Comparator.comparingInt(Animals::getNum)));
System.out.println("----minBy---"+optional.get());
//⑤joining:连接
System.out.println("----joining---"+list3.stream().map(Animals::getName).collect(Collectors.joining(" and ")));
//⑥groupingBy:分组
Map<Integer,List<Animals>> groupAnimal = list3.stream().collect(Collectors.groupingBy(Animals::getNum));
System.out.println("----groupingBy:num---"+groupAnimal);
//⑦orElse(null)/orElseGet(null):表示如果一个都没找到返回null(orElse/orElseGet()中可以塞默认值,如果找不到就会返回该默认值)
//orElse(null)和orElseGet(null)区别:
//Ⅰ.orElse() 接受类型T的 任何参数,而orElseGet()接受类型为Supplier的函数接口,该接口返回类型为T的对象
//Ⅱ.当返回Optional的值是空值null时,都会执行;而当返回的Optional有值时,orElse会执行,而orElseGet不会执行
System.out.println("-----------orElse/orElseGet start-----------");
//没值
Animals a = list3.stream().filter(v -> v.getName().equals("bear")).findFirst().orElse(test("orElse notNull"));
System.out.println(a);
Animals b = list3.stream().filter(v -> v.getName().equals("bear")).findFirst().orElseGet(test("orElse notNull"));
System.out.println(b);
//有值
Animals c = list3.stream().filter(v -> v.getName().equals("panda")).findFirst().orElse(test("orElse null"));
System.out.println(c);
Animals d = list3.stream().filter(v -> v.getName().equals("panda")).findFirst().orElseGet(test("orElse null"));
System.out.println(d);
System.out.println("-----------orElse/orElseGet end-----------");
三、练习
1.将数量最少的前三名的动物挑选出来,作为“濒危动物”,将他的名字面前加上“danger_”前缀,并且要相同物种的不同分支需要进行合并(_之前相同如panda/dolphin的说明是相同物种),
如果数量相同,则以首字母排序(A最大,这里默认认为不会出现首字母相同的数据),最终结果只保留姓名
数据如下:
List<Animals> list4 = Arrays.asList(
new Animals("monkey",12),new Animals("elephant",4),new Animals("dolphin_Z",3)
,new Animals("tiger",3),new Animals("dolphin_M",4),new Animals("penguin",4)
,new Animals("panda_C",1), new Animals("panda_A",1),new Animals("dolphin_T",1)
);
提示:使用顺序–peek/groupBy/reduce/sorted/sorted/limit/peek/map
四、练习答案
1.解题思路:首先要进行确定相同物种-peek,然后将相同物种合并-groupBy/reduce,然后排序(排序器先比较数量,再比较名称)–sorted, 接着取前三名–limit,再进行_danger处理–peek,最后保留姓名–map
Stream<Animals> streamPeek1 = list4.stream().peek(v -> v.setName(v.getName().split("_")[0]));
Map<String,List<Animals>> group = streamPeek1.collect(Collectors.groupingBy(Animals::getName));
List<Animals> newGroup = new ArrayList<>();
group.forEach((k,v) ->{
Optional<Animals> optionalReduce = v.stream().reduce((v1, v2) -> {
if(v1.getName().equals(v2.getName())){
return new Animals(v1.getName(),v1.getNum()+v2.getNum());
}
return v1;
});
newGroup.add(optionalReduce.get());
});
//newGroup.stream().forEach(System.out::println);
Stream<Animals> streamSorted = newGroup.stream().sorted((v1,v2) -> {
if(v1.getNum() == v2.getNum()){
char v1char = v1.getName().charAt(0);
char v2char = v2.getName().charAt(0);
return (int)v1char-(int)v2char;
}else{
return v1.getNum()-v2.getNum();
}
}).limit(3);
streamSorted.forEach(System.out::println);
streamSorted.peek(v -> v.setName("danger_"+v.getName())).map(Animals::getName).forEach(System.out::println);