欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java 8 新特性功能和用法介绍---Java Stream API

程序员文章站 2022-11-15 15:16:38
Java Stream APIStream 是java 8最重要的亮点,它跟java.io包中inputStream和outStream是不同的概念。 它是对集合对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很...

Java Stream API

Stream 是java 8最重要的亮点,它跟java.io包中inputStream和outStream是不同的概念。 它是对集合对象功能的增强,
它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。
同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。
通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。
所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。
下面从以下几个进行讨论:

  • 函数式接口
  • Stream流操作
  • 聚合操作
  • Optional使用介绍

函数式接口

函数式接口是java 8对一类特殊类型接口的称呼。该类接口定义了唯一抽象方法的接口,并使用注解@FunctionalInterface注释该接口。
jdk8 最直接的支持就是java.util.function包,并且定义四个最基础的函数接口:

接口 参数 返回值 类别
Consumer T void 消费性接口
Supplier None T 供给型接口
Function T R 函数型接口
Predicate T boolean 断言型接口

消费型接口

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
    ......
    ......
}

该接口表示一个接受单个输入参数并且没有返回值的操作。不像其它函数式接口,Consumer接口期望执行带有副作用的操作(Consumer的操作可能会更改输入参数的内部状态)。

具体使用示例

    public void testConsumer(){
        consumer.accept("今天天气真好");
    }
    
    Consumer<String> consumer = message -> System.out.println(message);

供给型接口

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

这个接口是一个提供者的意思,只有一个get的抽象类,没有默认的方法以及静态的方法,传入一个泛型T的,get方法,返回一个泛型T
具体使用示例

public void testSupplier(){
        Supplier<Cat> catSupplier = Cat::new;
        Cat cat = catSupplier.get();
        cat.setName("test");
        cat.setColour("black");
        System.out.println(cat.getName());
    }

函数型接口

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
    ......
    }

数据转换器,接收一个 T 类型的对象,返回一个 R类型的对象; 单参数单返回值的行为接口;提供了 apply, compose, andThen, identity 方法;

  public void testFunction() {
        List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
                , new Cat("lily", "black"));
        System.out.println(countBlackCat.apply(cats));
    }

    Function<List<Cat>, Long> countBlackCat = cats -> cats.stream().filter(cat -> "black".equals(cat.getColour())).count();

断言型接口

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
    ......
}

接收一个 T 类型的对象,返回布尔值,通常用于传递条件函数; 单参数布尔值的条件性接口。提供了 test (条件测试)

public void testPredicate(){
        List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
                , new Cat("lily", "black"));
        cats.stream().filter(filterCatName).forEach(cat-> System.out.println(cat.getName()));
    }
    
    Predicate<Cat> filterCatName = cat -> cat.getName().length() == 3;

Stream 流操作

java 8 stream流是对集合操作的抽象,可以执行复杂查询、过滤、映射、排序和聚合等操作,其特点是
不改变原有数据结构,不保存数据以及是Lazy操作,只在最终执行终止操作时,将中间记录的过程,进行求值操作。
其主要分为四类接口

  • 中间转换操作 filter、map、flatMap、peek、limit、sorted、skip等
  • 终止操作 toArray、reduce、collect、min、max、count、anyMatch、allMatch等
  • 直接遍历 也是终止操作一种 forEach、forEachOrdered
  • 创建流 empty、of、generate等

创建流

创建流有两种1.通过Stream接口的静态方法构造 2.通过Collection接口的默认方法stream()进行转换

public void testCreateStream() {
        int sum = Stream.of(1, 3, 4).mapToInt(Integer::intValue).sum();
        int max = Arrays.asList(1, 3, 4).stream().mapToInt(Integer::intValue).max().orElse(0);
        System.out.println("和为" + sum + ", 最大值为" + max);
    }

中间转换

转换流实际上将原始流通过某些行为操作转换另一个新流,下面介绍几个非常常用的转换流

filter过滤

对流中包含的元素使用给定的过滤函数进行过滤操作,那么新生成的流只包含符合条件的元素

public void testPredicate(){
        List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
                , new Cat("lily", "black"));
        cats.stream().filter(filterCatName).forEach(cat-> System.out.println(cat.getName()));
    }
    
    Predicate<Cat> filterCatName = cat -> cat.getName().length() == 3;

上面通过filter过滤出猫的名称长度等于3,满足该条件的进行输出

map转换

对于流中包含的元素使用给定的转换函数进行转换操作,新生成的流只包含转换后的元素

 public void testMapStream() {
        List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
                , new Cat("lily", "black"));
        cats.stream().map(Cat::getColour).forEach(System.out::println);
    }

上面通过map操作将cat猫的实体转换成猫的名称,并输出

flatMap 扁平化转换

与map类似,不同的是元素是流,扁平化为正常元素,再进行元素转换

 public void testFlatMapStream(){
        List<People> peopleList = Arrays.asList(new People("张三", 17, Arrays.asList(new Cat("tom", "black")
                , new Cat("jack", "white"))),new People("李四", 19, Arrays.asList(new Cat("a", "black")
                , new Cat("b", "white"))));
        //统计养黑猫的个数
        long count = peopleList.stream().flatMap(people -> people.getCats().stream()).filter(cat -> "black".equals(cat.getColour())).count();
        System.out.println(count);
    }

代码中flatMap 里面是个猫的流,通过flatMap转换成猫的元素,再通过filter过滤出黑猫,最后count统计个数

peek

peek是指将原有stream流的所有元素,经过peek消费,生成一个包含原有元素的新stream流

 public void testPeek(){
        int sum = Stream.of(1, 3, 4).peek(System.out::println).mapToInt(Integer::intValue).sum();
        System.out.println(sum);
        List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
                , new Cat("lily", "black"));
        long black = cats.stream().peek(cat -> cat.setColour("black")).filter(cat -> "black".equals(cat.getColour())).count();
        System.out.println(black);
    }

上面代码可以看出求和,peek是打印出原有元素,并没有改变。统计黑猫时,我们peek里面改变了猫对象的颜色
最终统计黑猫树是3.这是因为peek接受的是一个Consumer,而void Consumer 是没有返回值,所以他的修改是回不到流中的,所以流的元素还是
原来的元素,这样就可以解释,为什么猫对象中颜色被改变,其实还是原有对象,并没有生产新的对象,这个与ma操作有很大区别。

limit,skip

limit 是对stream进行截取操作,获取前N个元素,而skip是对stream跳过操作,跳过前M个元素

public void testLimitSkip() {
        int sum = Stream.of(1, 3, 4, 6, 7, 8, 9).skip(3).limit(3).peek(System.out::println).mapToInt(Integer::intValue).sum();
        System.out.println(sum);
    }

代码意义是跳过前3个元素,截取前3个元素,最终输出是6,7,8,和为21

流的聚合操作

聚合操作接受一个元素的输入,经过反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。

Collect可变聚合

collect聚合实际上将元素收集到一个结果容器中

 /* *@param <R> type of the result
     * @param supplier a function that creates a new result container. For a
     *                 parallel execution, this function may be called
     *                 multiple times and must return a fresh value each time.
     * @param accumulator an <a href="package-summary.html#Associativity">associative</a>,
     *                    <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                    <a href="package-summary.html#Statelessness">stateless</a>
     *                    function for incorporating an additional element into a result
     * @param combiner an <a href="package-summary.html#Associativity">associative</a>,
     *                    <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                    <a href="package-summary.html#Statelessness">stateless</a>
     *                    function for combining two values, which must be
     *                    compatible with the accumulator function
     * @return the result of the reduction
     */
    <R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

三个参数的含义:Supplier supplier是一个工厂函数,用来生成一个新的容器;BiConsumer accumulator也是一个函数,用来把Stream中的元素添加到结果容器中;BiConsumer combiner还是一个函数,用来把中间状态的多个结果容器合并成为一个(并发的时候会用到)

   /** @param <R> the type of the result
     * @param <A> the intermediate accumulation type of the {@code Collector}
     * @param collector the {@code Collector} describing the reduction
     * @return the result of the reduction
     * @see #collect(Supplier, BiConsumer, BiConsumer)
     * @see Collectors
     */
    <R, A> R collect(Collector<? super T, A, R> collector);

Java8还给我们提供了Collector的工具类,正常都使用该工具类

下面列举几个工具类常用的操作

 public void testCollect() {
        List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
                , new Cat("lily", "black"));
        //使用toList,获取所有猫名
        List<String> catNames = cats.stream().map(Cat::getName).collect(Collectors.toList());

        //使用groupingBy ,按照颜色分类
        Map<String, List<Cat>> colourGroup = cats.stream().collect(Collectors.groupingBy(Cat::getColour));

        //使用toMap转换成key,value键值对,假设猫名唯一
        Map<String, String> catMap = cats.stream().collect(Collectors.toMap(Cat::getName, Cat::getColour));

        //使用reducing将猫名合并按照逗号分隔
        String name = cats.stream().map(Cat::getName).collect(Collectors.reducing((name1, name2) -> name1 + "," + name2)).orElse("");

    }

其他汇聚操作

主要是reduce方法,该方法非常同意,比如stream里面count、sum、max、min都可以使用其实现

 public void testReduce(){
        List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
                , new Cat("lily", "black"));
        //统计猫的颜色种类
        long colourCount = cats.stream().map(Cat::getColour).distinct().count();
        //reduce将猫名合并按照逗号分隔
        String name = cats.stream().map(Cat::getName).reduce((name1, name2) -> name1 + "," + name2).orElse("");
    }

并行流

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

 public void testParallelStream(){
        List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
                , new Cat("lily", "black"));
        //统计猫的颜色种类
        long count = cats.parallelStream().map(Cat::getColour).distinct().count();
        
        //错误用法
        Set<String> colourSet = new HashSet<>();
        cats.parallelStream().map(Cat::getColour).forEach(col-> colourSet.add(col));
        long catCount = colourSet.size();
    }

parallelStream里直接去修改变量是非线程安全的,但是采用collect和reduce操作就是满足线程安全,
另外什么时候使用parallelStream,需要考虑任务大小,运行机器是否多核去决定,并不是说并行比串行就快,所以大家谨慎使用。

Optional

为什么单独把Optional 给单独拎出来说一说,因为java 8 设计它本身来替换掉null,因此java 8 引入了一个名为java.util.Optional的新的类。
本身我们在定义接口方法时,如果对方法返回明确有空异常,那么定义成optional,别人引用的时候,肯定需要
判断空异常,这样更加明确。

创建Optional对象

  • Optional.empty:声明一个空的Optional;
  • Optional.of:依据一个非空值创建Optional;
  • Optional.ofNullable:可接受null的Optional;
public Optional<Cat> testOptional() {
        List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
                , new Cat("lily", "black"));
        //查询一个名字叫lily的猫
        return cats.stream().filter(cat -> "lily".equals(cat.getName())).findFirst();
    }

本文地址:https://blog.csdn.net/weixin_43946605/article/details/109577044

相关标签: java