Java Stream流:forEach、filter、map、count、limit、skip、concat、综合案例


Stream流 JDK 1.8

说到Stream容易想到 IO Stream,而流不一定是IO流。在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库有的弊端。


引言 :通过案例展示Stream流的作用

需求分析:

  1. 在ArrayList集合中,筛选出姓张的数据;
  2. 从筛选出姓张的数据中,再筛选出名字长度为3;
  3. 将结果打印出来。

普通操作:

public class PrefaceNormal {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("张杰");
        arrayList.add("张还行");
        arrayList.add("王小");
        arrayList.add("张怡");
        arrayList.add("唐山");

        // 筛选姓张的
        ArrayList<String> listA = new ArrayList<>();
        for(String str: arrayList){
            if(str.startsWith("张")){
                listA.add(str);
            }
        }

        // 筛选3个字的
        ArrayList<String> listB = new ArrayList<>();
        for(String str: arrayList){
            if(str.length() == 3){
                listB.add(str);
            }
        }

        // 遍历结果
        for (String str : listB){
            System.out.println(str); // 输出:张还行
        }
    }
}

可见用了很多次增强for循环,代码量很多,很冗杂,不美观,效率低。

Stream操作:

public class PrefaceStream {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("张杰");
        arrayList.add("张还行");
        arrayList.add("王小");
        arrayList.add("张怡");
        arrayList.add("唐山");

        arrayList.stream()
            	// Lambda实现Predicate接口中的test方法,返回Boolean值
                .filter(s -> s.startsWith("张")) 
                .filter(s -> s.length() == 3)
            	// Lambda实现Customer接口中的accept方法
                .forEach(name -> System.out.println(name)); 
        // 不对原有的集合内容做改变,只是对符合要求的数据进行筛选
    }
}

1、流式思想

在这里插入图片描述

这里的filtermapskip都是对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。

备注:Stream流 其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身不存储任何元素(或地址值)


2、Stream特点

Stream是一个来数据源的元素队列

  • 元素式特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算,使用一次之后,再次调用就会报异常。
public class PrefaceStream {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("张还行", "heroC", "yikeX");
        stream.filter(name -> name.startsWith("张"))
              .forEach(name -> System.out.println(name));
        
        // stream使用了一次了,再使用一次,会报IllegalStateException异常
        // 说明之前的流已经关闭,不能再使用了。
        stream.forEach(name -> System.out.println(name));
    }
}
  • 数据源 流的来源。可以是集合,数组等。

Stream操作两个基础的特征:

  • Pipelining(流水线):中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格。这样做可以对操作进行优化,比如延迟执行和短路。
  • 内部迭代:以前对集合遍历都是通过Iterator或者增强for的方式。显示的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法。

当使用一个流的时候,通常包括三个基本步骤:获取一个数据源 → 数据转换 → 执行操作获取想要的结果,每次转换原有Stream对象不改变,返回一个新的Stream对象,这就允许对其操作可以像链条一样排列,变成一个管道。


3、获取Stream流对象

java.util.stream Interface Stream<T> 接口

获取流的方式:

  • 所有Collection集合都可以通过stream默认方法获取流
  • stream的静态方法of可以获取对应的流,of方法是可变参数,可变参数底层就一数组
public class CollectionStream {
    public static void main(String[] args) {
        // List集合获取流
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();
        // Set集合获取流
        Set<String> set = new HashSet<>();
        Stream<String> stream1 = set.stream();
        // 键获取流
        Map<String,String> map1 = new HashMap<>();
        Collection<String> collKey = map1.keySet();
        Stream<String> stream2 = collKey.stream();
        // 值获取流
        Map<String,String> map2 = new HashMap<>();
        Collection<String> collValue = map2.values();
        Stream<String> stream3 = collValue.stream();
        // 键值对获取流
        Map<String,String> map3 = new HashMap<>();
        Collection<Map.Entry<String, String>> entries = map3.entrySet();
        Stream<Map.Entry<String, String>> stream4 = entries.stream();
        // 流的静态方法of,可变参数获取流
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
        // 流的静态方法of,数组获取流
        Integer[] num = {1,2,3,4};
        Stream<Integer> num1 = Stream.of(num);
        String[] str = {"a","b"};
        Stream<String> str1 = Stream.of(str);
    }
}

4、常用方法

在这里插入图片描述

流模型的操作很丰富,这里介绍一些常用的API。这些方法可以分为两种:

  • 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。
  • 终结方法:返回类型不再是Stream接口自身类型的方法,因此不再支持链式调用。常见countforEach方法,更多见API文档。

逐一处理:forEach

forEach与增强for是不同的

void forEach(Consumer<? super T> action)

public class PrefaceStream {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("张还行", "heroC", "yikeX");
        stream.forEach(name -> System.out.println(name)); // 对流中的每一个数据进行处理
    }
}

过滤:filter

可通过filter将一个流转换成另一个流

Stream<T> filter(Predicate<? super T> predicate)

public class PrefaceStream {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("张还行", "heroC", "yikeX");
        stream.filter(name -> name.startsWith("张")) // 可以将流过滤转换成另一个只符合要求的流
              .forEach(name -> System.out.println(name)); // 对新的流每个数据进行遍历处理
    }
}
// 输出:张还行

映射:map

将流中的方法映射到另一个流中

<R> Stream<R> map(Function<? super T,? extends R> mapper)

T类型的数据转换成R类型的数据就是“映射”。

public class PrefaceStream {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1", "2", "3", "4");
        Stream<Integer> stream1 = stream.map(str -> Integer.parseInt(str));
        stream1.forEach(num -> System.out.print(num+" "));
    }
}
// 输出:1 2 3 4 

统计个数:count

long count() 返回long类型数据,统计流中元素的个数

public class PrefaceStream {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1", "12", "23", "1234");
        long count = stream.filter(str -> str.startsWith("1")).count();
        System.out.println(count);
    }
}
// 输出:3

取用前几个:limit

limit方法可以对流中的数据进行截取,只取用前n个。

Stream<T> limit(long maxSize)

public class PrefaceStream {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1", "12", "23", "1234");
        stream.limit(3).forEach(str -> System.out.print(str+" "));
    }
}
// 输出:1 12 23

跳过前几个:skip

如果流的当前长度大于n,则跳过前n个元素;否则将会得到一个长度为0的空流。

Stream<T> skip(long n)

public class PrefaceStream {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1", "12", "23", "1234");
        stream.skip(2).forEach(str-> System.out.print(str+" "));
    }
}
// 输出:23 1234 

组合:concat

将有两个流,合并成一个流

static <T> Stream<T> concat(Stream<? extends T> a,Stream<? extends T> b)

备注:这是一个静态方法,与java.lang.String当中的concat方法是不同的。

public class PrefaceStream {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("heroC");
        Stream<String> stream2 = Stream.of("yikeX");
        Stream.concat(stream1,stream2).forEach(str -> System.out.print(str+" "));
    }
}
// 输出:heroC yikeX 

练习:集合元素的处理

题目:

现在有两个ArrayList集合存储队伍当中的多个成员姓名,使用Stream流完成以下操作:

  1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
  2. 第一个队伍筛选后只要前3个人名;存储到一个新集合中。
  3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
  4. 第二个队伍筛选之后不要前2个人名;存储到一个新集合中。
  5. 将两个筛选后的队伍合并为一个队伍;存储到一个新集合中。
  6. 根据姓名创建Person对象;存储到一个新集合中。
  7. 打印整个队伍的Person对象信息。
public class StreamExample {
    public static void main(String[] args) {
        ArrayList<String> one = new ArrayList<>();
        one.add("张还行");
        one.add("张还");
        one.add("张行");
        one.add("施柏宇");
        one.add("林俊杰");
        one.add("张一山");

        // 1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
        Collection<String> nameThree = new ArrayList<>();
        one.stream().filter(name -> name.length()==3).forEach(name -> nameThree.add(name));

        // 2. 第一个队伍筛选后只要前3个人名;存储到一个新集合中。
        Collection<String> nameThird = new ArrayList<>();
        nameThree.stream().limit(3).forEach(name -> nameThird.add(name));

        ArrayList<String> two = new ArrayList<>();
        two.add("张一山");
        two.add("黄轩");
        two.add("张行");
        two.add("张若昀");
        two.add("罗晋");
        two.add("张子枫");

        // 3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
        Collection<String> nameZhang = new ArrayList<>();
        two.stream().filter(name -> name.startsWith("张")).forEach(name -> nameZhang.add(name));

        // 4. 第二个队伍筛选之后不要前2个人名;存储到一个新集合中。
        Collection<String> nameSkip = new ArrayList<>();
        nameZhang.stream().skip(2).forEach(name -> nameSkip.add(name));

        // 5. 将两个筛选后的队伍合并为一个队伍;存储到一个新集合中。
        Collection<String> nameConcat = new ArrayList<>();
        Stream.concat(nameThird.stream(), nameSkip.stream()).forEach(name -> nameConcat.add(name));

        // 6. 根据姓名创建`Person`对象;存储到一个新集合中。
        Collection<Person> namePerson = new ArrayList<>();
        nameConcat.stream().forEach(name -> namePerson.add(new Person(name)));

        // 7. 打印整个队伍的`Person`对象信息。
        long count = namePerson.stream().count();
        System.out.println("筛选后的队伍总人数为:"+count);
        namePerson.stream().forEach(namePer -> System.out.println(namePer));
    }
}

输出:

筛选后的队伍总人数为:5
Person{name='张还行'}
Person{name='施柏宇'}
Person{name='林俊杰'}
Person{name='张若昀'}
Person{name='张子枫'}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值