• 【Java8新特性】Stream API有哪些中间操作?看完你也可以吊打面试官!!


    写在前面

    在上一篇《【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?》中,一名读者去面试被面试官暴虐!归根结底,那哥儿们还是对Java8的新特性不是很了解呀!那么,我们继续讲述Java8的新特性,旨在最终可以让每位读者在跳槽面试的过程中吊打面试官!!

    Stream的中间操作

    多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值” 。 Stream的中间操作是不会有任何结果数据输出的。

    Stream的中间操作在整体上可以分为:筛选与切片、映射、排序。接下来,我们就分别对这些中间操作进行简要的说明。

    筛选与切片

    这里,我将与筛选和切片有关的操作整理成如下表格。

    方法 描述
    filter(Predicate p) 接收Lambda表达式,从流中排除某些元素
    distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去 除重复元素
    limit(long maxSize) 截断流,使其元素不超过给定数量
    skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素 不足 n 个,则返回一个空流。与 limit(n) 互补

    接下来,我们列举几个简单的示例,以便加深理解。

    为了更好的测试程序,我先构造了一个对象数组,如下所示。

    protected List<Employee> list = Arrays.asList(
        new Employee("张三", 18, 9999.99),
        new Employee("李四", 38, 5555.55),
        new Employee("王五", 60, 6666.66),
        new Employee("赵六", 8, 7777.77),
        new Employee("田七", 58, 3333.33)
    );
    

    其中,Employee类的定义如下所示。

    @Data
    @Builder
    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    public class Employee implements Serializable {
        private static final long serialVersionUID = -9079722457749166858L;
        private String name;
        private Integer age;
        private Double salary;
    }
    

    Employee类的定义比较简单,这里,我就不赘述了。之后的示例中,我们都是使用的Employee对象的集合进行操作。好了,我们开始具体的操作案例。

    1.filter()方法

    filter()方法主要是用于接收Lambda表达式,从流中排除某些元素,其在Stream接口中的源码如下所示。

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

    可以看到,在filter()方法中,需要传递Predicate接口的对象,Predicate接口又是个什么鬼呢?点进去看下源码。

    @FunctionalInterface
    public interface Predicate<T> {
    
        boolean test(T t);
    
        default Predicate<T> and(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
        }
    
        default Predicate<T> negate() {
            return (t) -> !test(t);
        }
        
        default Predicate<T> or(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) || other.test(t);
        }
        
        static <T> Predicate<T> isEqual(Object targetRef) {
            return (null == targetRef)
                    ? Objects::isNull
                    : object -> targetRef.equals(object);
        }
    }
    

    可以看到,Predicate是一个函数式接口,其中接口中定义的主要方法为test()方法,test()方法会接收一个泛型对象t,返回一个boolean类型的数据。

    看到这里,相信大家明白了:filter()方法是根据Predicate接口的test()方法的返回结果来过滤数据的,如果test()方法的返回结果为true,符合规则;如果test()方法的返回结果为false,则不符合规则。

    这里,我们可以使用下面的示例来简单的说明filter()方法的使用方式。

    //内部迭代:在此过程中没有进行过迭代,由Stream api进行迭代
    //中间操作:不会执行任何操作
    Stream<Person> stream = list.stream().filter((e) -> {
        System.out.println("Stream API 中间操作");
        return e.getAge() > 30;
    });
    

    我们,在执行终止语句之后,一边迭代,一边打印,而我们并没有去迭代上面集合,其实这是内部迭代,由Stream API 完成。

    下面我们来看看外部迭代,也就是我们人为得迭代。

    //外部迭代
    Iterator<Person> it = list.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
    

    2.limit()方法

    主要作用为:截断流,使其元素不超过给定数量。

    先来看limit方法的定义,如下所示。

    Stream<T> limit(long maxSize);
    

    limit()方法在Stream接口中的定义比较简单,只需要传入一个long类型的数字即可。

    我们可以按照如下所示的代码来使用limit()方法。

    //过滤之后取2个值
    list.stream().filter((e) -> e.getAge() >30 ).limit(2).forEach(System.out :: println);
    

    在这里,我们可以配合其他得中间操作,并截断流,使我们可以取得相应个数得元素。而且在上面计算中,只要发现有2条符合条件得元素,则不会继续往下迭代数据,可以提高效率。

    3.skip()方法

    跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素 不足 n 个,则返回一个空流。与 limit(n) 互补。

    源码定义如下所示。

    Stream<T> skip(long n);
    

    源码定义比较简单,同样只需要传入一个long类型的数字即可。其含义是跳过n个元素。

    简单示例如下所示。

    //跳过前2个值
    list.stream().skip(2).forEach(System.out :: println);
    

    4.distinct()方法

    筛选,通过流所生成元素的 hashCode() 和 equals() 去 除重复元素。

    源码定义如下所示。

    Stream<T> distinct();
    

    旨在对流中的元素进行去重。

    我们可以如下面的方式来使用disinct()方法。

    list.stream().distinct().forEach(System.out :: println);
    

    这里有一个需要注意的地方:distinct 需要实体中重写hashCode()和 equals()方法才可以使用。

    映射

    关于映射相关的方法如下表所示。

    方法 描述
    map(Function f) 接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。
    mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream。
    mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream。
    mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream
    flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流

    1.map()方法

    接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。

    先来看Java8中Stream接口对于map()方法的声明,如下所示。

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

    我们可以按照如下方式使用map()方法。

    //将流中每一个元素都映射到map的函数中,每个元素执行这个函数,再返回
    List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
    list.stream().map((e) -> e.toUpperCase()).forEach(System.out::printf);
    
    //获取Person中的每一个人得名字name,再返回一个集合
    List<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toList());
    

    2.flatMap()

    接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流。

    先来看Java8中Stream接口对于flatMap()方法的声明,如下所示。

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

    我们可以使用如下方式使用flatMap()方法,为了便于大家理解,这里,我就贴出了测试flatMap()方法的所有代码。

    /**
         * flatMap —— 接收一个函数作为参数,将流中的每个值都换成一个流,然后把所有流连接成一个流
         */
        @Test
        public void testFlatMap () {
            StreamAPI_Test s = new StreamAPI_Test();
            List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
            list.stream().flatMap((e) -> s.filterCharacter(e)).forEach(System.out::println);
    
            //如果使用map则需要这样写
            list.stream().map((e) -> s.filterCharacter(e)).forEach((e) -> {
                e.forEach(System.out::println);
            });
        }
    
        /**
         * 将一个字符串转换为流
         */
        public Stream<Character> filterCharacter(String str){
            List<Character> list = new ArrayList<>();
            for (Character ch : str.toCharArray()) {
                list.add(ch);
            }
            return list.stream();
        }
    

    其实map方法就相当于Collaction的add方法,如果add的是个集合得话就会变成二维数组,而flatMap 的话就相当于Collaction的addAll方法,参数如果是集合得话,只是将2个集合合并,而不是变成二维数组。

    排序

    关于排序相关的方法如下表所示。

    方法 描述
    sorted() 产生一个新流,其中按自然顺序排序
    sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序

    从上述表格可以看出:sorted有两种方法,一种是不传任何参数,叫自然排序,还有一种需要传Comparator 接口参数,叫做定制排序。

    先来看Java8中Stream接口对于sorted()方法的声明,如下所示。

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

    sorted()方法的定义比较简单,我就不再赘述了。

    我们也可以按照如下方式来使用Stream的sorted()方法。

    // 自然排序
    List<Employee> persons = list.stream().sorted().collect(Collectors.toList());
    
    //定制排序
    List<Employee> persons1 = list.stream().sorted((e1, e2) -> {
        if (e1.getAge() == e2.getAge()) {
            return 0;
        } else if (e1.getAge() > e2.getAge()) {
            return 1;
        } else {
            return -1;
        }
    }).collect(Collectors.toList());
    

    写在最后

    如果觉得文章对你有点帮助,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习Java8新特性。

    最后,附上Java8新特性核心知识图,祝大家在学习Java8新特性时少走弯路。

    img

  • 相关阅读:
    vue组件间传值
    Kth MIN-MAX 反演
    BZOJ4671 异或图(容斥+线性基)
    hihoCoder #1646 : Rikka with String II(容斥原理)
    字符串小结
    LOJ# 572. 「LibreOJ Round #11」Misaka Network 与求和(min25筛,杜教筛,莫比乌斯反演)
    SPOJ divcntk(min25筛)
    LA3490 Generator(KMP + 高斯消元)
    ExKMP(Z Algorithm) 讲解
    BZOJ 2728: [HNOI2012]与非(位运算)
  • 原文地址:https://www.cnblogs.com/binghe001/p/12969825.html
Copyright © 2020-2023  润新知