玩转Java8Stream(一、从零认识Stream)

相信Java8的Stream 大家都已听说过了,但是可能大家不会用或者用的不熟,笔者将在《玩转Java8Stream》系列文章中带大家从零开始使用,循序渐进,带你走向Stream的巅峰。

操作符

什么是操作符呢?操作符就是对数据进行的一种处理工作,一道加工程序;就好像工厂的工人对流水线上的产品进行一道加工程序一样。

image-20190124191452884

Stream的操作符大体上分为两种:中间操作符终止操作符

中间操作符

对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。

中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作):

  1. map(mapToInt,mapToLong,mapToDouble) 转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。
  2. flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。
  3. limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用。
  4. distint 去重操作,对重复元素去重,底层使用了equals方法。
  5. filter 过滤操作,把不想要的数据过滤。
  6. peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。
  7. skip 跳过操作,跳过某些元素。
  8. sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。

终止操作符

数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。

  1. collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。
  2. count 统计操作,统计最终的数据个数。
  3. findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。
  4. noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。
  5. min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。
  6. reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。
  7. forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。
  8. toArray 数组操作,将数据流的元素转换成数组。

这里只介绍了Stream,并没有涉及到IntStreamLongStreamDoubleStream,这三个流实现了一些特有的操作符,我将在后续文章中介绍到。

说了这么多,只介绍这些操作符还远远不够;俗话说,实践出真知。那么,Let‘s go。

代码演练

Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。

  • map,可以看到 map 操作符要求输入一个Function的函数是接口实例,功能是将T类型转换成R类型的。
image-20190124205024064

map操作将原来的单词 转换成了每个单的长度,利用了String自身的length()方法,该方法返回类型为int。这里我直接使用了lambda表达式,关于lambda表达式 还请读者们自行了解吧。

public class Main {

    public static void main(String[] args) {
        Stream.of("apple","banana","orange","waltermaleon","grape")
                .map(e->e.length()) //转成单词的长度 int
                .forEach(e->System.out.println(e)); //输出
    }
}

当然也可以这样,这里使用了成员函数引用,为了便于读者们理解,后续的例子中将使用lambda表达式而非函数引用。

public class Main {

    public static void main(String[] args) {
         Stream.of("apple","banana","orange","waltermaleon","grape")
                .map(String::length) //转成单词的长度 int
                .forEach(System.out::println);
    }
}

结果如图:

image-20190124205358513
  • mapToInt 将数据流中得元素转成Int,这限定了转换的类型Int,最终产生的流为IntStream,及结果只能转化成int。
image-20190124212519049
public class Main {

    public static void main(String[] args) {
         Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .mapToInt(e -> e.length()) //转成int
                .forEach(e -> System.out.println(e));
    }
}

mapToInt如图:

image-20190124213759260
  • mapToLong、mapToDouble 与mapToInt 类似
public class Main {

    public static void main(String[] args) {
         Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .mapToLong(e -> e.length()) //转成long ,本质上是int 但是存在类型自动转换
                .forEach(e -> System.out.println(e));
    }
}

mapToLong 如图:

image-20190124213629698
public class Main {

    public static void main(String[] args) {
         Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .mapToDouble(e -> e.length()) //转成Double ,自动类型转换成Double
                .forEach(e -> System.out.println(e));
    }
}

mapToDouble如图:

image-20190124213532625
  • flatmap 作用就是将元素拍平拍扁 ,将拍扁的元素重新组成Stream,并将这些Stream 串行合并成一条Stream
image-20190124214204150
public class Main {

    public static void main(String[] args) {
        Stream.of("a-b-c-d","e-f-i-g-h")
                .flatMap(e->Stream.of(e.split("-")))
                .forEach(e->System.out.println(e));

    }
}

flatmap 如图:

image-20190124215359833
  • flatmapToInt、flatmapToLong、flatmapToDouble 跟flatMap 都类似的,只是类型被限定了,这里就不在举例子了。

  • limit 限制元素的个数,只需传入 long 类型 表示限制的最大数

public class Main {

    public static void main(String[] args) {
        Stream.of(1,2,3,4,5,6)
                .limit(3) //限制三个
                .forEach(e->System.out.println(e)); //将输出 前三个 1,2,3
    }
}

limit如图:

image-20190124220311866
  • distinct 将根据equals 方法进行判断,如果要对自己自定义的bean 去重,则需要 重写equals方法,但是这不是唯一的方法,后面文章我将带大家实现自定义(bean 的某个字段去重)去重。
image-20190124220900422
public class Main {

    public static void main(String[] args) {

        Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
                .distinct() //去重
                .forEach(e->System.out.println(e));

    }
}

distinct 如图:

image-20190124220943803
  • filter 对某些元素进行过滤,不符合筛选条件的将无法进入流的下游
public class Main {

    public static void main(String[] args) {
        Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
                .filter(e->e>=5) //过滤小于5的
                .forEach(e->System.out.println(e));
    }
}

filter 如图:

image-20190124221423496
  • peek 挑选 ,将元素挑选出来,可以理解为提前消费
public class Main {

    public static void main(String[] args) {

        User w = new User("w",10);
        User x = new User("x",11);
        User y = new User("y",12);

        Stream.of(w,x,y)
                .peek(e->{e.setName(e.getAge()+e.getName());}) //重新设置名字 变成 年龄+名字
                .forEach(e->System.out.println(e.toString()));

    }

    static class User {

        private String name;

        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

}

peek 如图:

image-20190124222208780
  • skip 跳过 元素
public class Main {

    public static void main(String[] args) {
        Stream.of(1,2,3,4,5,6,7,8,9)
                .skip(4) //跳过前四个
                .forEach(e->System.out.println(e)); //输出的结果应该只有5,6,7,8,9
    }
}

skip 如图:

image-20190124222454233
  • sorted 排序 底层依赖Comparable 实现,也可以提供自定义比较器

这里Integer 实现了比较器

public class Main {

    public static void main(String[] args) {
        Stream.of(2,1,3,6,4,9,6,8,0)
                .sorted()
                .forEach(e->System.out.println(e));
    }
}

sorted 默认比较器如图:

image-20190124222837320

这里使用自定义比较,当然User 可以实现Comparable 接口

public class Main {

    public static void main(String[] args) {

        User x = new User("x",11);
        User y = new User("y",12);
        User w = new User("w",10);

        Stream.of(w,x,y)
                .sorted((e1,e2)->e1.age>e2.age?1:e1.age==e2.age?0:-1)
                .forEach(e->System.out.println(e.toString()));

    }

    static class User {

        private String name;

        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

}

如图:

image-20190124223311909
  • collect 收集,使用系统提供的收集器可以将最终的数据流收集到List,Set,Map等容器中。

这里我使用collect 将元素收集到一个set中

public class Main {

    public static void main(String[] args) {
        Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .collect(Collectors.toSet()) //set 容器
                .forEach(e -> System.out.println(e));
    }
}

咦?,不是说终止操作符只能使用一次吗,为什么这里调用了forEach 呢?forEach不仅仅是是Stream 中得操作符还是各种集合中得一个语法糖,不信咋们试试。

public class Main {

    public static void main(String[] args) {
        
        Set<String> stringSet = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .collect(Collectors.toSet()); //收集的结果就是set
        stringSet.forEach(e->System.out.println(e)); set的语法糖forEach
}

结果如图:

image-20190125111833196
  • count 统计数据流中的元素个数,返回的是long 类型
public class Main {

    public static void main(String[] args) {

        long count = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .count();

        System.out.println(count);
    }
}

count 如图:

image-20190125112309044
  • findFirst 获取流中的第一个元素

这里找到第一个元素 apple

public class FindFirst {

    public static void main(String[] args) {
        Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .findFirst();
        stringOptional.ifPresent(e->System.out.println(e));
    }
}

findFirst 结果如图:

image-20190125114151413
  • findAny 获取流中任意一个元素
public class FindAny {

    public static void main(String[] args) {
        Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .parallel()
                .findAny(); //在并行流下每次返回的结果可能一样也可能不一样
        stringOptional.ifPresent(e->System.out.println(e));
    }
}

findAny 在并行流下 使用结果:

输出了orange

image-20190125114818948

输出了banana

image-20190125114910071
  • noneMatch 数据流中得没有一个元素与条件匹配的

这里 的作用是是判断数据流中 一个都没有与aa 相等元素 ,但是流中存在 aa ,所以最终结果应该是false

public class NoneMatch {

    public static void main(String[] args) {
        boolean result = Stream.of("aa","bb","cc","aa")
                .noneMatch(e->e.equals("aa"));
        System.out.println(result);
    }
}

noneMatch 如图:

image-20190125115629012
  • allMatch和anyMatch 一个是全匹配,一个是任意匹配 和noneMatch 类似,这里就不在举例了。
  • min 最小的一个,传入比较器,也可能没有(如果数据流为空)
public class Main {

    public static void main(String[] args) {

        Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
                .min((e1,e2)->e1.compareTo(e2));

        integerOptional.ifPresent(e->System.out.println(e));

    }

min如图:

image-20190125120404758
  • max 元素中最大的,需要传入比较器,也可能没有(流为Empty时)
public class Main {

    public static void main(String[] args) {

        Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
                .max((e1,e2)->e1.compareTo(e2));

        integerOptional.ifPresent(e->System.out.println(e));

    }
}

max 如图:

image-20190125120612451
  • reduce 是一个规约操作,所有的元素归约成一个,比如对所有元素求和,乘啊等。

这里实现了一个加法,指定了初始化的值

public class Main {
    public static void main(String[] args) {

        int sum = Stream.of(0,9,8,4,5,6,-1)
              .reduce(0,(e1,e2)->e1+e2);
        System.out.println(sum);
    }
}

reduce 如图:

image-20190125121043425
  • forEach

forEach 其实前就已经见过了,对每个数据遍历迭代

  • forEachOrdered 适用用于并行流的情况下进行迭代,能保证迭代的有序性

这里通过并行的方式输出数字

public class ForEachOrdered {
    public static void main(String[] args) {
        Stream.of(0,2,6,5,4,9,8,-1)
                .parallel()
                .forEachOrdered(e->{
                    System.out.println(Thread.currentThread().getName()+": "+e);});
    }
}

forEachOrdered 如图:

image-20190125133704357
  • toArray 转成数组,可以提供自定义数组生成器
public class ToArray {
    public static void main(String[] args) {
        Object[] objects=Stream.of(0,2,6,5,4,9,8,-1)
                .toArray();

        for (int i = 0; i < objects.length; i++) {
            System.out.println(objects[i]);
        }
    }
}

toArray 如图:

image-20190125134425560

总结

Java8Stream 第一篇就带大家认识到这里,如果你能跟着我的文章把每一个例子都敲一遍,相信都能掌握这些操作符的初步用法;后续文章我会带大家一步步深入Stream。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容