2

JAVA流式计算

流的简单介绍

Java 8 中,引入了流(Stream)的概念,利用提供的Stream API,我们可以方便的操作集合数据,这种方式很类似于使用SQL对数据库的操作。

如何生成流

利用Stream API,首先我们需要生成流,以下是生成流的常用方式(这里我们只介绍顺序流):

1、所有继承自Collection的接口都可以直接转化为流:

List<Integer> l1 = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = l1.stream();

Map<String,Student> s1 = new HashMap<>();
Stream<Map.Entry<String, Student>> stream1 = s1.entrySet().stream();

2、利用Arrays类中的stream()方法:

Integer[] i1 = new Integer[]{1,2,3,4,5};  
Stream<Integer> stream = Arrays.stream(i1);

3、使用Stream类中的静态方法:

Stream<Integer> stream = Stream.of(1,2,3,4,5);

4、利用BufferedReader读取文本中的内容转化为流:

BufferedReader reader = new BufferedReader(new FileReader("D:\\stream.txt"));  
Stream<String> stream = reader.lines();

我们经常使用的还是方式1,将集合转化为流。

如何操作流

流的主要操作有筛选/切片/查找/匹配/映射/归约

操作流的方法简介

操作流的方法分为两类,一类是惰性求值,一类是及早求值;

惰性求值并不是立刻执行的,而是将求值的过程记录下,在进行及早求值的时候,才会按顺序执行前面的惰性求值,一般的执行过程如下:

Stream.惰性求值.惰性求值. ... .惰性求值.及早求值

区分是惰性求值还是及早求值,可以通过方法的返回值来判断,返回值时Stream类型的就是惰性求值

在介绍如何通过Stream API对流进行操作前,我们首先了解下函数式接口的相关知识(后面介绍的Stream API中会使用到这些函数式接口),这里只作简单介绍。

函数式接口

所谓函数式接口,是指只含有一个抽象方法的接口,一般加@FunctionalInterface注解加以标注,函数式接口可以被隐式地转化为Lamada表达式;

这里我们主要介绍了java.util.function包下的四个常用的函数式接口:

接口 方法 简介
Consumer void accept(T t) 消费接口,接收一个T类型的对象
Supplier T get() 供给接口,返回一个T类型的对象
Function R apply(T t) 映射接口,接收一个T类类型的对象转换为R类型的对象
Predicate boolean test(T t) 判断接口,判断一个T类型的对象是否满足某个条件,返回一个boolean类型
使用Stream API操作流

首先我们将java.util.stream.stream类中的API进行分类:

方法类型 方法名
惰性求值 无状态 filter();map();mapToInt();mapToLong();mapToDouble;flatMap()...
有状态 distinct();sorted();skip();limit()...
及早求值 非短路操作 reduce();forEach();collect();min();max();count()...
短路操作 anyMatch();allMatch()...
无状态:元素的处理是独立的,不受前面元素的影响;  
​  
有状态:元素的处理不是独立的,受到前面元素的影响;  
​  
非短路:必须处理完所有元素才能得到结果;  
​  
短路:不需要处理完所有元素就能得到结果;

下面我们介绍下常用的Stream方法

1、filter()

方法定义:

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

方法介绍:


用于对数据进行过滤,筛选出符合条件的数据;  
接收一个Predicate的函数接口,用于进行条件的过滤;返回符合条件的数据组成的一个新的流;

代码示例:

List<Integer> l1 = Arrays.asList(1,2,3,4,5);  
//过滤出大于2的元素  
Stream<Integer> integerStream = l1.stream().filter(s -> s > 2);

2、map()

方法定义:

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

方法介绍:

对数据进行处理,将T类型的对象转化为R类型的对象,简单来说就是对流中的数据进行同一项操作;  
接收一个Function的函数接口,用于对数据处理;返回R类型的数据组成的一个新的流;

代码示例:

List<String> l2 = Arrays.asList("A", "B", "C");  
//将每个元素全部转化为小写  
Stream<String> stringStream = l2.stream().map(s -> s.toLowerCase());

注:mapToInt(),mapToLong(),mapToDouble(),这是java针对基本数据类型封装出对应的流,这里就不在介绍;

3、flatMap()

方法定义:

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

方法介绍:

将流中的每一个元素转化为一个流,然后将转化的一个个流组合成一个新的流;  
接收一个Function的函数接口,用于对数据处理;返回R类型的数据组成的一个新的流;

代码示例:

List<List<Integer>> l3 = Arrays.asList(Arrays.asList(1,2,5,4,7),Arrays.asList(4,8,6,9,4));  
//将l3中的每个元素(List),转化为一个个流,然后组合成一个新的流  
Stream<Integer> integerStream1 = l3.stream().flatMap(s -> s.stream());

4、sorted()

方法定义:

Stream<T> sorted();  
Stream<T> sorted(Comparator<? super T> comparator);

方法介绍:

将流中的数据进行排序,然后排序后的数据组合成一个新的流;  
无参的方法,默认按照升序升序进行排列;  
有参的方法,需要传入Comparator接口的一个实现类,按照该实现进行排序操作;

代码示例:

List<Integer> l1 = Arrays.asList(2,1,4,5);  
//升序排列  
Stream<Integer> sorted = l1.stream().sorted();  
//降序排列  
Stream<Integer> sorted1 = l1.stream().sorted((t1, t2) -> { return t1 < t2 ? 1 : t1 == t2 ? 0 : -1; });

5、reduce()

方法定义:

Optional<T> reduce(BinaryOperator<T> accumulator);  
T reduce(T identity, BinaryOperator<T> accumulator);  
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);

方法介绍:

将流中的数据进行规约,返回规约后的数据;  
Optional<T> reduce(BinaryOperator<T> accumulator):第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。  
​  
T reduce(T identity, BinaryOperator<T> accumulator):流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。  
​  
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner):在串行流(stream)中,第三个参数combiner不会起作用,该方法跟第二个方法一样。在并行流(parallelStream)中,道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是对每个线程的执行结果进行规约,最终返回结果。

代码示例:

List<Integer> l1 = Arrays.asList(2,1,4,5);
//求和,返回的是Optional对象
Optional<Integer> reduce = l1.stream().reduce((t1, t2) -> t1 + t2);
//求和返回的是Integer对象
Integer reduce1 = l1.stream().reduce(0, (t1, t2) -> t1 + t2);
//每个单独的并行流进行求和操作,并行流输出的结果进行相乘操作输出最终结果
Integer reduce2 = l1.parallelStream().reduce(0, (t1, t2) -> t1 + t2, (s1, s2) -> s1 * s2);

6、collect()

方法定义:

<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);

方法介绍:

将流中的数据转化为一个新的数据结构;  
<R, A> R collect(Collector<? super T, A, R> collector):传入一个想要输出的集合类型,一般通过Collectors创建集合类型  
<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner):第一个参数supplier为结果存放容器,第二个参数accumulator为结果如何添加到容器的操作,第三个参数combiner则为多个容器的聚合策略

代码示例:

//将流转化为List数据结构
List<Integer> collect1 = Stream.of(1, 2, 3).collect(Collectors.toList());

//将流转化为Map结构
Student student1 = new Student("xiaoni", "male", 15);
Student student2 = new Student("xiaohua", "female", 20);
List<Student> l1 = new ArrayList<>();
l1.add(student1);
l1.add(student2);
HashMap<String, Student> collect = l1.stream()
    .collect(() -> new HashMap<String, Student>(), 
             (h, v) -> {h.put(v.getName(),v);},
             HashMap::putAll);

7、综合训练

public static void main(String[] args) throws FileNotFoundException {

        Student s1 = new Student("xiaoni", "male", 18);
        Student s2 = new Student("xiaohua", "female", 20);
        Student s3 = new Student("xiaodong", "male", 19);
        Student s4 = new Student("xiaoben", "male", 24);
        Student s5 = new Student("xiaoyun", "female", 23);
        Student s6 = new Student("xiaojing", "female", 20);

        List<Student> l1 = new ArrayList<>();
        l1.add(s1);
        l1.add(s2);
        l1.add(s3);
        l1.add(s4);
        l1.add(s5);

        //统计男生的数量
        long count = l1.stream().filter(s -> "male".equals(s.getSex())).count();
        System.out.println("男生数量:"+count);
        //按年龄从大到小进行排序
        List<Student> collect = l1.stream()
                .sorted((k1,k2) ->
                {
                    int a1 = k1.getAge();
                    int a2 = k2.getAge();
                    return a1 < a2 ? 1 : a1 == a2 ? 0 : -1;
                })
                .collect(Collectors.toList());
        System.out.println("按年龄排序:" + collect);
        //求所有学生的年龄之和
        long count1 = l1.stream().mapToInt(s -> s.getAge()).sum();
        System.out.println("年龄之和:" + count1);
        //将数据存放到map中,学生姓名作为key值
        Map<String, Student> collect1 = l1.stream().collect(Collectors.toMap(Student::getName, s -> s));
        System.out.println("map中的学生:"+collect1);

        List<Integer> l2 = Arrays.asList(1,5,4,7,9,2,4,5,1);
        //输出最小的三个数字,不包含重复的数字
        List<Integer> l3 = l2.stream().distinct().sorted(Integer::compareTo).limit(3).collect(Collectors.toList());
        System.out.println("最小的三个数:"+l3);
        //输出第二小的数字
        List<Integer> l4 = l2.stream().distinct().sorted(Integer::compareTo).skip(1).limit(1).collect(Collectors.toList());
        System.out.println("第二小的数字:"+l4);

    }

sean
7 声望0 粉丝

我在学JAVA