Stream流详细入门教程 (包含练习题)

一、介绍

(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);

我的写法是这样的

public static void test5() {
    List<Anim> list4 = Arrays.asList(
            new Anim("monkey", 12), new Anim("dolphin_M", 4), new Anim("dolphin_Z", 3)
            , new Anim("tiger", 3), new Anim("elephant", 4), new Anim("penguin", 4)
            , new Anim("panda_C", 1), new Anim("panda_A", 1), new Anim("dolphin_T", 1)
    );

    list4.stream().map(v -> {
        v.setName(v.getName().split("_")[0]);
        return v;
    }).forEach(System.out::println);

    Stream<Anim> streamPeek = list4.stream().peek(v -> v.setName(v.getName().split("_")[0]));
    Map<String, List<Anim>> animMap = streamPeek.collect(Collectors.groupingBy(Anim::getName));

    List<Anim> res = new ArrayList();
    animMap.forEach((key, value) -> {
        System.out.println(key + "==============" + value);
        Optional<Anim> reduce = value.stream().reduce((v1, v2) -> {
            return new Anim(v1.getName(), v1.getNum() + v2.getNum());
        });
        res.add(reduce.get());
    });
    res.stream().forEach(System.out::println);
    System.out.println("----------------");
    res.stream().sorted(Comparator.comparing(Anim::getNum)).forEach(System.out::println);
    List<Anim> collect = res.stream().sorted(Comparator.comparing(Anim::getNum)).limit(3).collect(Collectors.toList());
    res.stream().sorted(Comparator.comparing(Anim::getNum))
            .limit(3)
            .map(Anim::getName)
            .map(x->"danger_"+x)
            .forEach(System.out::println);
}

这里顺便引申一个概念:

peek和map、foreach有什么区别,都各自能干什么事情?

  • 两个函数都是中间操作,都非常的‘懒’,没有对Stream的终止操作,两个函数都不会工作。
  • peek函数的存在仅仅是为了debug,而map是Stream的一个核心函数,两个函数的地位不同。
  • 两个函数的返回值都是一个新的Stream,但是两个函数的参数(peek是Consumer,map是Function)起作用的时机不同。map的Function在生成新的Stream之前被执行,新Stream中的元素是上游Stream中元素经Function作用后的值。peek函数的Consumer工作在生成Stream之后,下一节详细讲解两个函数执行时机。

 Java Stream peek vs. map - 简书

Stream的peek和map的区别_embelfe_segge的博客-CSDN博客https://www.jianshu.com/p/4fabc8a7abcaStream的peek和map的区别_embelfe_segge的博客-CSDN博客

Java8 Stream中 peek、map、foreach区别_peek和foreach的区别_dmfrm的博客-CSDN博客

https://aiqinhai.blog.csdn.net/article/details/124909043

————————————————
版权声明:本文为CSDN博主「TTTALK」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tttalk/article/details/129418447

Stream流详细入门教程 (包含练习题)_java stream流练习_TTTALK的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/Alex_81D/article/details/130525845
今日推荐