JDK8新特性之Steam流

这里写目录标题

一、Stream流概述

Java 中,Stream 是一个来自java.util.stream包的接口,用于对集合(如List、Set等)或数组等数据源进行操作的一种抽象层。

Stream流(和IO流没有任何关系)主要是对数据进行加工处理的。Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约。

1.1、传统写法

现在有需求:对list中姓张,名字长度为3的信息打印:

java 复制代码
	public static void main(String[] args) {
        //定义一个List集合
		List<String> list = Arrays.asList("张三","张三丰","张无忌","李四","王五");
		//获取姓张,名字长度为3的信息,添加到列表中
		List<String> list1=new ArrayList<>();
		for (String s : list) {
			if(s.startsWith("张")&&s.length()==3){
				list1.add(s);
			}
		}
		for (String s : list1) {
			System.out.println(s);
		}
	}

1.2、Stream写法

java 复制代码
    public static void main(String[] args) {
        //定义一个List集合
        List<String> list = Arrays.asList("张三","张三丰","张无忌","李四","王五");
        list.stream().filter(s -> s.startsWith("张"))
                .filter(s -> s.length()== 3)
                .forEach(System.out::println);
    }

上面的SteamAPI代码的含义:获取流,过滤张,过滤长度,逐一打印。代码相比于上面的案例更加的简洁直观。

1.3、Stream流操作分类

  • 生成操作
    通过数据源(集合、数组等)生成流。
  • 中间操作
    对流进行某种程度的过滤/映射,并返回一个新的流。
  • 终结操作
    执行某个终结操作,一个流只能有一个终结操作。

二、Stream流获取方式

2.1、根据Collection获取

java.util.Collection 接口中加入了default方法 stream,也就是说Collection接口下的所有的实现都可以通过steam方法来获取Stream流。(java集合框架主要包括两种类型的容器,一种是集合,存储一个元素集合(Collection),另一种是图(Map),存储键/值对映射)。

java 复制代码
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        Stream<String> stream=list.stream();
        Set<String> set=new HashSet<>();
        Stream<String> stream1=set.stream();
        Vector vector=new Vector();
        vector.stream();
    }

Map接口别没有实现Collection接口,这时我们可以根据Map获取对应的key value的集合。

java 复制代码
    public static void main(String[] args) {
        Map<String,Object> map=new HashMap<>();
        Stream<String> stream=map.keySet().stream();//key
        Stream<Object> stream1=map.values().stream();//value
        Stream<Map.Entry<String,Object>> stream2=map.entrySet().stream();//entry
    }

2.2、通过Stream的of方法

由于数组对象不可能添加默认方法,所有Stream接口中提供了静态方法of操作到数组中的数据

java 复制代码
    public static void main(String[] args) {
        Stream<String> a1= Stream.of("a1","a2","a3");
        String[] arr1 = {"aa","bb","cc"};
        Stream<String> arr11 = Stream.of(arr1);
        Integer[] arr2 = {1,2,3,4};
        Stream<Integer> arr21 = Stream.of(arr2);
        arr21.forEach(System.out::println);
        // 注意:基本数据类型的数组是不行的
        int[] arr3 = {1,2,3,4};
        Stream.of(arr3).forEach(System.out::println);
    }

三、Stream常用方法介绍

Stream常用方法

方法名 方法作用 返回值类型 防范种类
count 统计个数 long 终结
forEach 逐一处理 void 终结
filter 过滤 Stream 函数拼接
limit 取用前几个 Stream 函数拼接
skip 跳过前几个 Stream 函数拼接
map 映射 Stream 函数拼接
concat 组合 Stream 函数拼接
注意:
  1. 这里把常用的API分为"终结方法"和"非终结方法"
  2. "终结方法":返回值类型不再是 Stream 类型的方法,不再支持链式调用。
  3. "非终结方法":返回值类型仍然是 Stream 类型的方法,支持链式调用。
  4. Stream方法返回的是新的流。
  5. Stream不调用终结方法,中间的操作不会执行。

3.1、forEach

forEach用来遍历流中的数据的。

java 复制代码
void forEach(Consumer<? super T> action);

该方法接受一个Consumer接口,会将每一个流元素交给函数处理。

java 复制代码
    public static void main(String[] args) {
        Stream<String> a1=Stream.of("aa","bb","cc");
        a1.forEach(System.out::println);
    }

3.2、count

Stream流中的count方法用来统计其中的元素个数的。

java 复制代码
  long count();

该方法返回一个long值,代表元素的个数。

java 复制代码
    public static void main(String[] args) {
        long count = Stream.of("a1", "a2", "a3").count();
        System.out.println(count);
    }

3.3、filter

filter方法的作用是用来过滤数据的。返回符合条件的数据。

可以通过filter方法将一个流转换成另一个子集流。

java 复制代码
    public static void main(String[] args) {
        Stream<String> stream =
                Stream.of("aa", "ab", "bc","a1","b2","c3").
                        filter(s -> s.contains("a"));
        stream.forEach(System.out::println);
    }

该接口接收一个Predicate函数式接口参数作为筛选条件。

3.4、limit

limit方法可以对流进行截取处理,支取前n个数据,

java 复制代码
  Stream<T> limit(long maxSize);

参数式一个long类型的数值,如果集合当前长度大于参数就进行截取,否则不操作:

java 复制代码
      public static void main(String[] args) {
        Stream.of("a1", "a2", "a3","bb","cc","aa","dd")
                .limit(3)
                .forEach(System.out::println);
    }

3.5、skip

如果希望跳过前面几个元素,可以使用skip方法获取一个截取之后的新流:

java 复制代码
  Stream<T> skip(long n);

示例:

java 复制代码
    public static void main(String[] args) {
       Stream.of("a1","b2","c3","aa","bb","cc")
               .skip(3).forEach(System.out::println);
    }

3.6、map

如果需要将流中的元素映射到另一个流中,可以使用map方法:

java 复制代码
  <R> Stream<R> map(Function<? super T, ? extends R> mapper);

该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据

java 复制代码
    public static void main(String[] args) {
        Stream.of("1","2","3","4","5")
                //.map(s -> Integer.parseInt(s))
                .map(Integer::parseInt)
                .forEach(System.out::println);
    }

3.7、sorted

如果需要将数据排序,可以使用sorted方法:

java 复制代码
  Stream<T> sorted();

在使用的时候可以根据自然规则排序,也可以通过比较强来指定对应的排序规则

java 复制代码
      public static void main(String[] args) {
        Stream.of("1","2","7","9","3","4","6")
                .map(Integer::parseInt)
                .sorted((o1, o2) -> (o1 - o2))
                .forEach(System.out::println);
    }

3.8、distinct

如果要去掉重复数据,可以使用distinct方法

java 复制代码
	Stream<T> distinct();  

Stream流中的distinct方法对于基本数据类型式可以直接去重的,但是对于自定义类型,我们需要重写hashCode和equals方法来移除重复数据。

java 复制代码
    public static void main(String[] args) {
        Stream.of("1","2","4","5","2","3","4")
                .sorted()
                .distinct().forEach(System.out::println);
        System.out.println("----------------");
        Stream.of(
                new Person("张三",18),
                new Person("李四",18),
                new Person("王五",20),
                new Person("张三",18)
            ).distinct().forEach(System.out::println);
    }  

3.9、match

如果需要判断数据是否匹配指定的条件,可以使用match相关的方法:

java 复制代码
boolean anyMatch(Predicate<? super T> predicate); // 元素是否有任意一个满足条件
boolean allMatch(Predicate<? super T> predicate); // 元素是否都满足条件
boolean noneMatch(Predicate<? super T> predicate); // 元素是否都不满足条件

使用:

java 复制代码
    public static void main(String[] args) {
        boolean b = Stream.of("1","2","3","4","5","7")
                .map(Integer::parseInt)
                //.anyMatch(s-> s > 4);
                //.allMatch(s-> s > 4);
                .noneMatch(s-> s > 4);
        System.out.println(b);
    }  

注:match是一个终结方法。

3.10、find

如果我们需要找到某些数据,可以使用find方法来实现

java 复制代码
	Optional<T> findFirst();
	Optional<T> findAny();

使用:

java 复制代码
    public static void main(String[] args) {
        Optional<String> first = Stream.of("a", "b", "c")
                .findFirst();
        System.out.println(first.get());

        Optional<String> any = Stream.of("a", "b", "c","d")
                .findAny();
        System.out.println(any.get());
    }

注:

3.11、max和min

如果我们想要获取最大值和最小值,那么可以使用max和min方法

java 复制代码
    public static void main(String[] args) {
        Optional<Integer> max= Stream.of(1,2,3,4).max(Integer::compareTo);
        System.out.println(max.get());
        Optional<Integer> min= Stream.of(1,2,3,4).min(Integer::compareTo);
        System.out.println(min.get());
    }  

3.12、reduce方法

如果需要将所有数据归纳得到一个数据,可以使用reduce方法:

java 复制代码
    public static void main(String[] args) {
        Integer sum = Stream.of(4,5,3,7)
                //identity默认值
                //第一次的时候会将默认值赋给x
                //之后每次将上一次的操作结果赋值给x y,就是每次从数据中获取的元素
                .reduce(0, (x, y) -> {
                    System.out.println("x="+x+",y="+y);
                    return x + y;
                });
        System.out.println("sum = " + sum);
        // 获取最大值
        Integer max = Stream.of(4,5,3,9)
                .reduce(0,(x,y) ->{
                    return x>y?x:y;
                });
        System.out.println("max = " + max);
    }  

3.12.1、 map和reduce的组合

在实际开发中我们经常会将map和reduce一块来使用:

java 复制代码
    public static void main(String[] args) {
        //1、求出所有年龄总和
        Integer sumAge= Stream.of(
                new Person("张三", 20),
                new Person("李四", 21),
                new Person("王五", 22),
                new Person("赵六", 23)
        ).map(Person::getAge)
                .reduce(0,Integer::sum);
        System.out.println(sumAge);

        //2、求出所有年龄最大值
        Integer maxAge= Stream.of(
                new Person("张三", 20),
                new Person("李四", 21),
                new Person("王五", 22),
                new Person("赵六", 23)
        ).map(Person::getAge)
                .reduce(Integer::max).get();
        System.out.println(maxAge);

        //3、统计字符 a 出现的次数
        Integer countA= Stream.of("a","b","c","d","e","a","a","a")
                .map(ch -> ch.equals("a")?1:0)
                .reduce(0,Integer::sum);
        System.out.println(countA);
    }  

3.13、mapToInt

如果需要将Sream中的Integer类型转换成int类型,可以使用mapToInt方法来实现

java 复制代码
    public static void main(String[] args) {
        //Integer 占用的内存比int多很多,在Stream流操作中会自动装修和拆箱操作
        Integer arr[] ={1,2,3,4,6,7,8};
        Stream.of(arr).filter(x -> x > 0).forEach(System.out::println);
        //为了提高程序代码效率,可以先将流中Integer数据转为为int数据
        IntStream intStream = Stream.of(arr).mapToInt(Integer::intValue);
        intStream.forEach(System.out::println);
    }  

3.14、concat

如果有两个流,希望合并成为一个流,那么可以使用Stream接口防范concat

java 复制代码
    public static void main(String[] args) {
        Stream<String> steam1=Stream.of("a","b","c");
        Stream<String> steam2=Stream.of("x","y","z");
        //通过concat方法将两个流合并成一个新的流
        Stream.concat(steam1,steam2).forEach(System.out::println);
    }  

3.15、综合案例

4.1、结果收集到集合中

java 复制代码
    public static void main(String[] args) {
        List<String> list1 = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子",
                "庄子", "孙子", "洪七公");
        List<String> list2 = Arrays.asList("古力娜扎", "张无忌", "张三丰", "赵丽颖",
                "张二狗", "张天爱", "张三");
        Stream<String> stream1 = list1.stream().filter(s -> s.length() == 3).limit(3);
        Stream<String> stream2 =list2.stream().filter(s -> s.startsWith("张")).skip(2);
        Stream.concat(stream1,stream2)
                //.map(s -> new Person(s))
                .map(Person::new)
                .forEach(System.out::println);
    }

四、Stream结果手集

4.1、结果收集到集合中

java 复制代码
    @Test
    public void test01(){
        //收集到list集合中
        List<String> list= Stream.of("a","b","c").collect(Collectors.toList());
        System.out.println(list);

        //收集到set集合中
        Set<String> set = Stream.of("aa","bb","cc","dd")
                .collect(Collectors.toSet());
        System.out.println(set);

        //如果需要获取的类型为具体实现
        ArrayList<String> arrayList = Stream.of("aaa","bbb","ccc")
                //.collect(Collectors.toCollection(() -> new ArrayList<>()));
                        .collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);
    }

4.2、结构收集到数组中

Stream中提供了toArray方法来将结果放到一个数组中,返回值类型是Object[]。

如果我们要指定返回的类型,那么可以使用另一个重载的toArray(IntFunction f)方法。

java 复制代码
    @Test
    public void test02(){
        //收集结果到数组中,数据类型是Object
        Object[] objects = Stream.of("aaa","bbb","ccc")
                .toArray();
        System.out.println(Arrays.toString(objects));

        //收集结构到数组中,数据类型是String
        String[] strings = Stream.of("aaa","bbb","ccc")
                .toArray(String[]::new);
        System.out.println(Arrays.toString(strings));
    }

4.3、对流中数据做聚合操作

当我们使用Stream流处理数据后,可以根据某个属性将数据分组

java 复制代码
    @Test
    public void test03(){
        //获取年龄最大值
        List<Person> personList= Arrays.asList(new Person("张三",20),
                new Person("李四",25),
                new Person("王五",30),
                new Person("赵六",35));

        Optional<Person> maxAgePerson = personList.stream()
        //.collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));
        .collect(Collectors.maxBy((p1,p2)->p1.getAge()-p2.getAge()));
        System.out.println("最大年龄: "+maxAgePerson.get());

        //获取年龄最小值
        Optional<Person> minAgePerson = personList.stream()
                .collect(Collectors.minBy((p1,p2)->p1.getAge()-p2.getAge()));
        System.out.println("最小年龄: "+minAgePerson.get());

        //求所有人的年龄之和
        Integer sumAge = personList.stream()
                .collect(Collectors.summingInt(Person::getAge));
        System.out.println("年龄总和:" + sumAge);

        //求所有人的年龄平均值
        Double avgAge = personList.stream()
                .collect(Collectors.averagingInt(Person::getAge));
        System.out.println("年龄的平均值:" + avgAge);

        //统计数量
        Long count = personList.stream().filter(p-> p.getAge() > 20)
                .collect(Collectors.counting());
        System.out.println("满足条件的记录数:" + count);
    }

4.4、对流中数据做分组操作

当我们使用Stream流处理数据后,可以根据某个属性将数据分组

java 复制代码
    public void test04(){
        List<Person> personList= Arrays.asList(
                new Person("张三", 18, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182));
        //根据姓名对数据分组
        Map<String,List<Person>> map1= personList.stream()
                .collect(Collectors.groupingBy(Person::getName));
        map1.forEach((k,v)-> System.out.println("k=" + k +"\t"+ "v=" + v));
        System.out.println("-----------");
        Map<String, List<Person>> map2 = personList.stream()
                .collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年"));
        map2.forEach((k,v)-> System.out.println("k=" + k +"\t"+ "v=" + v));
    }

多级分组

java 复制代码
    @Test
    public void test05(){
        List<Person> personList= Arrays.asList(
                new Person("张三", 16, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182));
        //先根据name分组,然后根据age(成年和未成年)分组
        Map<String,Map<Object,List<Person>>> map= personList.stream()
                .collect(Collectors.groupingBy(Person::getName,
                        Collectors.groupingBy(p-> p.getAge() >= 18 ? "成年" : "未成年")));
        map.forEach((k,v)->{
            System.out.println("k=" + k);
            v.forEach((kk,vv) -> System.out.println("\t"+"kk="+kk+"\tvv="+vv));
        });

4.5、对流中数据做分区操作

Collectors.partitioningBy会根据值是否为true,把集合中的数据分割为两个列表,一个true列表,一个

false列表

java 复制代码
    @Test
    public void test06(){
        List<Person> personList= Arrays.asList(
                new Person("张三", 16, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182));
        Map<Boolean, List<Person>> map = personList.stream()
                .collect(Collectors.partitioningBy(p -> p.getAge() > 18));
        map.forEach((k,v)-> System.out.println(k+"\t" + v));
    }

4.6、对流中数据做拼接

Collectors.joining会根据指定的连接符,将所有的元素连接成一个字符串

java 复制代码
    @Test
    public void test07(){
        List<Person> personList= Arrays.asList(
                new Person("张三", 18, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182));
        String s1 = personList.stream().map(Person::getName)
                .collect(Collectors.joining());
        // 张三李四张三李四张三
        System.out.println(s1);
        String s2 = personList.stream().map(Person::getName)
                .collect(Collectors.joining("_"));
        // 张三_李四_张三_李四_张三
        System.out.println(s2);
        String s3 = personList.stream().map(Person::getName)
                .collect(Collectors.joining("_", "###", "$$$"));
        // ###张三_李四_张三_李四_张三$$$
        System.out.println(s3);
    }

五、并行Stream流

5.1、串行的Stream流

我们前面使用的Stream流都是串行,也就是在一个线程上面执行。

java 复制代码
    @Test
    public void test01(){
        long count = Stream.of(5,6,7,4,2,1,9)
                .filter(s ->{
                    System.out.println(Thread.currentThread().getName() + ":" + s);
                    return s > 3;
                }).count();
        System.out.println(count);
    }

5.2、并行流

parallelStream其实就是一个并行执行的流,它通过默认的ForkJoinPool,可以提高多线程任务的速

度。

5.2.1、获取并行流

java 复制代码
    @Test
    public void test02(){
        List<Integer> list=new ArrayList<>();
        //通过list接口直接获取并行流
        Stream<Integer> stram=list.parallelStream();
        //将已有的串行流转换为并行流
        Stream<Integer> parallel=Stream.of(1,2,3).parallel();
    }

5.2.3、并行流操作

java 复制代码
    @Test
    public void test03(){
        long count = Stream.of(5,6,7,4,2,1,9)
                .parallel()
                .filter(s ->{
                    System.out.println(Thread.currentThread().getName() + ":" + s);
                    return s > 3;
                }).count();
        System.out.println(count);
    }

5.3、并行流和串行流对比

我们通过for循环,串行Stream流,并行Stream流来对500000000亿个数字求和。来看消耗时间

java 复制代码
public class StresmTest24 {

    private static long times=500000000;

    private long start;

    @Before
    public void before(){
        start=System.currentTimeMillis();
    }

    @After
    public void end(){
        long end=System.currentTimeMillis();
        System.out.println("消耗时间:"+(end - start));
    }

    @Test
    public void test01(){
        System.out.println("普通for循坏:");
        long res= 0;
        for (int i = 0;i<times;i++){
            res+=1;
        }
    }

    @Test
    public void test02(){
        System.out.println("串行流:serialStream");
        LongStream.rangeClosed(0,times).reduce(0,Long::sum);
    }

    @Test
    public void test03(){
        LongStream.rangeClosed(0,times)
                .parallel().reduce(0,Long::sum);
    }


}

5.4、线程安全

在多线程的处理下,肯定会出现数据安全问题。如下:

java 复制代码
    @Test
    public void test01(){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        System.out.println(list.size());
        List<Integer> listNew =new ArrayList<>();
        list.parallelStream()
                .forEach(listNew::add);
        System.out.println(listNew.size());
    }

这个会抛出异常

java 复制代码
	java.lang.ArrayIndexOutOfBoundsException

针对这个问题,我们的解决方案有哪些呢?

  1. 加同步锁
  2. 使用线程安全的容器
  3. 通过Stream中的toArray/collect操作
    实现:
java 复制代码
@Test
    public void test02(){
        List<Integer> listNew = new ArrayList<>();
        Object obj = new Object();
        IntStream.rangeClosed(1,1000)
                .parallel()
                .forEach(i->{
                    synchronized (obj){
                        listNew.add(i);
                    }
                });
        System.out.println(listNew.size());
    }

    @Test
    public void test03(){
        Vector v = new Vector();
        Object obj = new Object();
        IntStream.rangeClosed(1,1000)
                .parallel()
                .forEach(i->{
                    synchronized (obj){
                        v.add(i);
                    }
                });
        System.out.println(v.size());
    }

    @Test
    public void test04(){
        List<Integer> listNew = new ArrayList<>();
        // 将线程不安全的容器包装为线程安全的容器
        List<Integer> synchronizedList = Collections.synchronizedList(listNew);
        Object obj = new Object();
        IntStream.rangeClosed(1,1000)
                .parallel()
                .forEach(i->{
                    synchronizedList.add(i);
                });
        System.out.println(synchronizedList.size());
    }

    @Test
    public void test05(){
        List<Integer> listNew = new ArrayList<>();
        Object obj = new Object();
        List<Integer> list = IntStream.rangeClosed(1, 1000)
                .parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println(list.size());
    }
相关推荐
开开心心就好5 分钟前
电脑扩展屏幕工具
java·开发语言·前端·电脑·php·excel·batch
零叹1 小时前
篇章十 数据结构——排序
java·数据结构·算法·排序算法
一个有女朋友的程序员1 小时前
Spring Boot 整合 Smart-Doc:零注解生成 API 文档,告别 Swagger
java·spring boot·smart-doc
苹果醋32 小时前
AI大模型竞赛升温:百度发布文心大模型4.5和X1
java·运维·spring boot·mysql·nginx
网安INF2 小时前
CVE-2020-1938源码分析与漏洞复现(Tomcat 文件包含/读取)
java·网络·web安全·网络安全·tomcat·漏洞复现
nenchoumi31192 小时前
UE5 学习系列(九)光照系统介绍
java·学习·ue5
张乔242 小时前
spring boot项目整合mybatis实现多数据源的配置
java·spring boot·多数据源
GzlAndy2 小时前
Tomcat调优
java·tomcat
美好的事情能不能发生在我身上2 小时前
苍穹外卖Day11代码解析以及深入思考
java·spring boot·后端·spring·架构