JAVA8新特性(二)

函数式数据处理

流Stream

概念:从支持数据处理操作的源生成的元素序列

元素序列——比如集合,集合是数据结构,目的是以特定的时间/空间复杂度存储和访问元素,流会对集合进行计算获取元素。

——流会使用一个提供数据的源,比如集合、数组或者输入/输出资源。 从有序集合生产流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。

数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、 map、 reduce、 find、 match、 sort等。流操作可以顺序执行,也可并行执行。

流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。

内部迭代——与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

中间操作——对流进行操作的时候,比如下面的示例filter,sorted返回的是另外一个流,那么就是中间操作,中间操作连接起来就是一个流水线。

终端操作——对流操作的时候想collect、forEache返回的是任何不适流的值,终端操作也是流水线的最后结果。

通过一个示例感受一下流的作用

I、java8以前可能会按照下面的方式处理需求

    /**
     * 获取红色富士苹果并且重量要降序排列,获取重量大于130的重量集合
     */
    public static void main(String[] args) {
        List<Apple> apples = getApples();
        List<Apple> redApples = new ArrayList<>();
        //筛选红色富士苹果
        for(Apple apple:apples){
            if (StringUtils.equals("red",apple.getColor()) && StringUtils.equals("富士",apple.getType())){
                redApples.add(apple);
            }
        }
        
        //按照重量降序排列
        redApples.sort(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o2.getWeight().compareTo(o1.getWeight());
            }
        });

        //获取重量集合
        List<Integer> weightList = new ArrayList<>();
        for(Apple apple:redApples){
            if(130<apple.getWeight()){
				weightList.add(apple.getWeight());
			}
        }

        System.out.println(weightList.toString());

    }

    private static List<Apple> getApples(){
        List<Apple> appleList = new ArrayList<>();
        appleList.add(new Apple("red",120,"富士"));
        appleList.add(new Apple("red",150,"富士"));
        appleList.add(new Apple("red",140,"富士"));
        appleList.add(new Apple("green",125,"富士"));
        appleList.add(new Apple("green",140,"富士"));
        appleList.add(new Apple("green",125,"富士"));
        appleList.add(new Apple("green",100,"青苹果"));
        appleList.add(new Apple("green",140,"青苹果"));
        appleList.add(new Apple("green",130,"青苹果"));
        appleList.add(new Apple("red",110,"蛇果"));
        appleList.add(new Apple("red",170,"蛇果"));
        appleList.add(new Apple("red",130,"蛇果"));
        appleList.add(new Apple("red",120,"蛇果"));
        return appleList;
    }

II、java8通过流的方式处理

    public static void main(String[] args) {

        List<Integer> collect = getApples().stream()
                .filter(apple -> StringUtils.equals("red", apple.getColor()) && StringUtils.equals("富士", apple.getType()))
                .sorted(Comparator.comparing(Apple::getWeight).reversed())
                .map(Apple::getWeight)
                .collect(Collectors.toList());
        System.out.println(collect.toString());

    }
    
输出结果都是:
[150, 140, 120]

当然上面的示例用到了一些流的操作方法,后面会一一讲到,现在可以体会到java8流的操作的简单、干净。

如果相对apples集合根据苹果类型分类,那么又会如何处理呢?

//java8以前
    public static void main(String[] args) {
        List<Apple> apples = getApples();
        Map<String, List<Apple>> typeMap = new HashMap<>();
        for(Apple apple:apples){
            if (CollectionUtils.isEmpty(typeMap.get(apple.getType()))){
                List<Apple> appleList = new ArrayList<>();
                appleList.add(apple);
                typeMap.put(apple.getType(),appleList);
            }else {
                typeMap.get(apple.getType()).add(apple);
            }
        }
        System.out.println(typeMap);

    }


//java8流操作
    public static void main(String[] args) {
         Map<String, List<Apple>> typeMap = getApples().stream()
                .collect(Collectors.groupingBy(Apple::getType));
        System.out.println(typeMap.toString());

    }

注意:此处的流Stream只能遍历一次,遍历完就被消费了,与I/O流类似。

流的操作

筛选和切片

  1. 谓词筛选
    方法filter(Predicate)
    谓词:一个返回boolean的函数
    示例:
List<Apple> appleList = apples.stream().filter((Apple a) -> StringUtils.equals("green", a.getColor())).collect(toList());
  1. 去重
    方法distinct()
    如果是引用对象需要去重需要重写hashCode和equals方法
    示例:
        List<Integer> list = Arrays.asList(1, 2, 4, 4, 5, 2, 1, 6);
        List<Integer> collect = list.stream().distinct().collect(Collectors.toList());
        System.out.println(collect.toString());
        结果:
        [1, 2, 4, 5, 6]
  1. 截取
    方法limit(n)
    该方法返回一个不超过给定长度的流。所需长度作为参数传递给limit,如果流是有序的,则最多返回前n个元素,如果是无序的,返回结果为任意排列
    示例:
        List<Integer> list = Arrays.asList(1, 2, 4, 4, 5, 2, 1, 6);
        List<Integer> collect = list.stream().limit(3).collect(Collectors.toList());
        System.out.println(collect);
        结果:
        [1, 2, 4]
  1. 跳过元素
    方法skip(n)
    返回一个从n+1到结尾的流,如果流不足n个,返回一个空流,limit(n)和skip(n)可以一起使用
    示例:
        List<Integer> list = Arrays.asList(1, 2, 4, 4, 5, 2, 1, 6);
        List<Integer> collect = list.stream()
                .skip(2)
                .limit(3)
                .collect(Collectors.toList());
        System.out.println(collect);
        结果:
        [4, 4, 5]

映射

  1. 从对象中获取信息
    方法map()
    返回流的泛型根据map()的参数返回的结果决定
    示例:
        List<Apple> appleList = new ArrayList<>();
        appleList.add(new Apple("red", 120, "富士"));
        appleList.add(new Apple("red", 150, "富士"));
        List<Integer> collect = appleList.stream()
                .map(Apple::getWeight)
                .collect(Collectors.toList());
        System.out.println(collect);
        结果:
        [120, 150]
  1. 流的扁平化
    方法flatmap()
    将多个流合并成一个流就是流的扁平化
    示例:
给定单词列表
["Hello","World"],你想要返回列表["H","e","l", "o","W","r","d"]
        List<String> list = Arrays.asList("Hello","World");
        Stream<String[]> stream = list.stream().map(arr -> arr.split(""));
        List<String[]> collect = stream.distinct()
                                       .collect(Collectors.toList());
        System.out.println(collect.toString());
        结果:
       [[Ljava.lang.String;@685f4c2e, [Ljava.lang.String;@7daf6ecc]

结果并不是我们想要的,其实通过上面示例的返回泛型能看出来结果是一个数组对象集合,
map方法返回的不是Stream<String>而是Stream<String[]>

        List<String> list = Arrays.asList("hello word");
        Stream<String[]> stream = list.stream().map(arr -> arr.split(""));
        List<String[]> collect = stream.distinct()
                                       .collect(Collectors.toList());
        for(String[] arr:collect){
            for(String a:arr){
                System.out.print(a + ",");
            }
            System.out.println(";");
        }
        结果:
        H,e,l,l,o,;
        W,o,r,l,d,;

通过视图可能更容易理解
在这里插入图片描述

想要得到一个字符流,而不是数组流,那么可以使用 Arrays.stream() 的方法可以接受一个数组并产生一个流

        List<String> list = Arrays.asList("Hello","World");
        Stream<String[]> stream = list.stream()
                .map(arr -> arr.split(""));
        Stream<Stream<String>> streamStream = stream.map(Arrays::stream);
        List<Stream<String>> collect = streamStream.distinct()
        				.collect(Collectors.toList());

但是发现最终的结果仍然不是List<String>而是List<Stream<String>>,也就是需要将流集合List<Stream<String>>转成List<String>,那么就需要将流合并,也就是扁平化

        List<String> list = Arrays.asList("Hello","World");
        Stream<String[]> stream = list.stream()
                .map(arr -> arr.split(""));
        Stream<String> stringStream = stream.flatMap(Arrays::stream);
        List<String> collect = stringStream.distinct().collect(Collectors.toList());
        System.out.println(collect.toString());
        结果:
        [H, e, l, o, W, r, d]
        
        以上合适并不好,应该写成下面这种
        List<String> list = Arrays.asList("Hello","World");
        List<String> collect = list.stream()
                .map(arr -> arr.split(""))
                .flatMap(Arrays::stream)
                .distinct().collect(Collectors.toList());
        System.out.println(collect.toString());

匹配

通常从一个集合或者数组中获取满足某个条件的数据需要经过循环、条件判断来获取。Stream API提供了一些方法allMatch、anyMatch、noneMatch、findFirst、findAny,通过方法名称大概能猜到每个方法的功能。

allMatch

检查谓词是否匹配所有元素

boolean allMatch(Predicate<? super T> predicate);

示例:

    private static List<Apple> getApples() {
        List<Apple> appleList = new ArrayList<>();
        appleList.add(new Apple("red", 120, "富士"));
        appleList.add(new Apple("red", 150, "富士"));
        appleList.add(new Apple("green", 125, "富士"));
        appleList.add(new Apple("green", 140, "富士"));
        appleList.add(new Apple("green", 140, "富士"));
        appleList.add(new Apple("green", 125, "富士"));
        appleList.add(new Apple("green", 100, "青苹果"));
        appleList.add(new Apple("green", 140, "青苹果"));
        appleList.add(new Apple("green", 130, "青苹果"));
        appleList.add(new Apple("red", 110, "蛇果"));
        appleList.add(new Apple("red", 170, "蛇果"));
        appleList.add(new Apple("red", 130, "蛇果"));
        appleList.add(new Apple("red", 120, "蛇果"));
        return appleList;
    }
    
    private static void test() {
        List<Apple> apples = getApples();
        if (apples.stream().allMatch(apple -> StringUtils.equals("red",apple.getColor()))){
            System.out.println(true);
        }else {
            System.out.println(false);
        }
    }
    
    结果:
    false

anyMatch

检查谓词是否至少匹配一个元素

boolean anyMatch(Predicate<? super T> predicate);

同样是上面的示例,如果使用anyMatch则结果不一样了

    private static void test() {
        List<Apple> apples = getApples();
        if (apples.stream().anyMatch(apple -> StringUtils.equals("red",apple.getColor()))){
            System.out.println(true);
        }else {
            System.out.println(false);
        }
    }
    
    结果:
    true

noneMatch

检查谓词没有匹配到任何一个元素

boolean noneMatch(Predicate<? super T> predicate);
    private static void test() {
        List<Apple> apples = getApples();
        if (apples.stream().noneMatch(apple -> StringUtils.equals("red",apple.getColor()))){
            System.out.println(true);
        }else {
            System.out.println(false);
        }
    }
    
    结果:
    false

查找

findAny

返回当前流中的任意元素。返回结果为Optional。

Optional<T> findAny();

大概说一下Optional后面详细说明。
Optional类是一个容器类,代表一个值存在或者不存在。常用的方法:

  • isPresent()将在Optional包含值的时候返回true,否则返回false
  • ifPresent(Consumer block)会在值存在的时候执行给定的代码块。Consumer函数式接口,传递T类型的参数,返回void
  • T get() 会在值存在时返回值,否则抛出一个NoSuchElement异常
  • T orElse(T other)会在值存在时返回值,否则返回一个默认值。
    private static void test() {
        List<Apple> apples = getApples();
        Optional<Apple> any = apples.stream()
                .filter(apple -> 100 < apple.getWeight())
                .findAny();
        any.ifPresent(d-> System.out.println(d.toString()));
    }

findFirst

获取第一个流元素

Optional<T> findFirst();

示例:

    private static void test() {
        List<Apple> apples = getApples();
        Optional<Apple> any = apples.stream()
                .filter(apple -> 100 < apple.getWeight())
                .findFirst();
        any.ifPresent(d-> System.out.println(d.toString()));
    }

何时使用findFirst和findAny?
你可能会想,为什么会同时有findFirst和findAny呢?答案是并行。找到第一个元素
在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流
时限制较少

归约

概念

把一个流中的元素组合起来,使用reduce操作来表达更复杂的查
询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为归约操作
(将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠( fold),因为你可以将这个操
作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。

元素求和

  1. 有初始化值

如下:初始化值0,reduce()有两个参数a,b ,求numbers元素之和

int sum = numbers.stream().reduce(0, (a, b) -> a + b);

在Java 8中, Integer类现在有了一个静态的sum
方法来对两个数求和

int sum = numbers.stream().reduce(0, Integer::sum);
  1. 无初始化值
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));

numbers可能为空,所以Optional也可能是空

最大值和最小值

计算方式与求和类似

Optional<Integer> max = numbers.stream().reduce(Integer::max);

Optional<Integer> min = numbers.stream().reduce(Integer::min);

构建流

由值创建流

使用静态方法Stream.of,通过显示值创建一个流。它可以接受任意数量的参数

示例:

Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);

结果:
JAVA 8 
LAMBDAS 
IN 
ACTION

创建一个空流

 Stream<Object> empty = Stream.empty();

由数组创建流

使用静态方法Arrays.stream从数组创建一个流。Arrays.stream()根据数组元素类型调用不同的方法。

    public static <T> Stream<T> stream(T[] array) {
        return stream(array, 0, array.length);
    }

示例:

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();

结果:
41

此时调用的是
    public static IntStream stream(int[] array) {
        return stream(array, 0, array.length);
    }

由文件生成流

Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。
java.nio.file.Files中的很多静态方法都会返回一个流

示例如下:

一个很有用的方法是
Files.lines,它会返回一个由指定文件中的各行构成的字符串流。使用你迄今所学的内容,
你可以用这个方法看看一个文件中有多少各不相同的词
在这里插入图片描述

由函数生成流

Stream API提供了两个静态方法来从函数生成流: Stream.iterate和Stream.generate。
这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。但是使用处理无限流必须用limit限制。

示例:

Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);

收集器

Collectors类提供的工厂方法创建收集器

主要提供了三大功能:

  • 将流元素归约和汇总为一个值
  • 元素分组
  • 元素分区

归约和汇总

I.获取集合的元素数量:Collectors.counting()以及方法count(),其方法与集合和size()方法功能相同。

示例:

        Long collect = getApples().stream()
                .filter(apple -> StringUtils.equals("red", apple.getColor()))
                .count();

        Long collect1 = getApples().stream()
                .collect(Collectors.counting());
                
        int size = getApples().size();

II.获取集合中最大和最小的元素,返回的结果是Optional也就是可能为空:Collectors.maxBy() 和 Collectors.minBy 分别对应有方法max() 和min()。

示例:

        Optional<Apple> collect = getApples().stream()
                .collect(Collectors.maxBy(Comparator.comparing(Apple::getWeight)));

        Optional<Apple> collect1 = getApples().stream()
                .max(Comparator.comparing(Apple::getWeight));
        System.out.println(collect1);

        Optional<Apple> min = getApples().stream()
                .min(Comparator.comparing(Apple::getWeight));
        System.out.println(min);

III.获取所有元素的总和:Collectors.summingInt()

示例:

        Integer collect1 = getApples().stream()
                .collect(Collectors.summingInt(Apple::getWeight));
        System.out.println(collect1);

        int sum = getApples().stream()
                .mapToInt(Apple::getWeight)
                .sum();
        System.out.println(sum);

通过方法summingInt就能查到此类方法还会有除了int类型以外的,Collectors.summingLong和Collectors.summingDouble,这些方法是计算和的,还有计算平均数的Collectors.averagingInt、Collectors.averagingLong、Collectors.averagingDouble。

IntSummaryStatistics 收集器返回的了
数据长度、总和、最小值、最大值、平均数,并且通过get方法
可以获取到这些值

示例:

        IntSummaryStatistics collect = getApples().stream()
                .collect(Collectors.summarizingInt(Apple::getWeight));

IV.拼接字符串:Collectors.joining(char)与StringBuild或者StringBuffy的append()方法功能相同。

示例:

        String collect = getApples().stream()
                .map(Apple::getType)
                .collect(Collectors.toList())
                .stream()
                .distinct()
                .collect(Collectors.joining(","));
        System.out.println(collect);
结果:富士,青苹果,蛇果

分组

分组的方式其实在前面示例就用到了:Collectors.groupingBy()

示例:

        //首先对苹果集合通过类型进行分组,获取key是苹果类型,value是对应苹果集合
        //然后对value通过重量比较获取最大的苹果对象
        //最后转换对象类型为苹果
        Map<String, Apple> collect = getApples().stream()
                .collect(Collectors.groupingBy(Apple::getType,
                        Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(Apple::getWeight)),
                                Optional::get)));

        System.out.println(collect.toString());
        
                Map<String, Apple> collect1 = getApples().stream()
                .collect(Collectors.toMap(Apple::getType,                     //类型分组
                        Function.identity(),                                  
                        BinaryOperator.maxBy(
                                Comparator.comparing(Apple::getWeight))));    //比较重量获取最大值的苹果

        System.out.println(collect1.toString());

分区

分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函
数。分区函数返回一个布尔值,这意味着得到的分组Map的键类型是Boolean,于是它最多可以
分为两组——true是一组, false是一组。Collectors.partitioningBy()

示例:

        Map<Boolean, List<Apple>> red = getApples().stream()
                .collect(Collectors.partitioningBy(apple -> StringUtils.equals("red", apple.getColor())));
        System.out.println(red);

Collectors静态方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

并行流

并行流:收集源通过调用parallelStream方法来吧集合转换为并行流。并行流就是一个吧内容分为多个数据块,并用不同的线程分别处理每个数据块的流。这样一来,你就可以自动把给定操作的工作负荷分配给多核处理的所有内核,让它们都忙起来。

顺序流和并行流转换

顺序流-> 并行流 parallel()

并行流-> 顺序流 sequntial()

注意:影响整个流水线的是最后一次调用的是哪个方法

示例:

stream.parallel()
.filter(...)
.sequential()
.map(...)
.parallel()
.reduce();

这样多次调用方法转换流类型,但是最后调用的是parallel(),所以此流水线并行流。

并行流内部使用了默认的ForkJoinPool,它默认的线程数量就是你的处理器数量,这个值是由Runtime.getRuntime().avaliableProcessors()得到的。

但 是 你 可 以 通 过 系 统 属 性 java.util.concurrent.ForkJoinPool.common.
parallelism来改变线程池大小,如下所示:
System.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”,“12”);
这是一个全局设置,因此它将影响代码中所有的并行流。反过来说,目前还无法专为某个
并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值,
除非你有很好的理由,否则我们强烈建议你不要修改它。

正确使用并行流

并不是只要使用并行流性能就好,效率就高。

正确使用并行流:

  • 通过测试,使用顺序流和并行流相比较,测试那种方式更适合;
  • 避免装箱和拆箱;
  • 依赖有序元素的操作,比如limit和findFirst;
  • 数量较少的情况;
  • 考虑数据结构,比如:ArrayList的拆分效率比LinkedList
    高得多,因为前者用不着遍历就可以平均拆分,而后者则必须遍历;

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值