Java基础之Lamdba表达式03之Stream

Java8由于引入了Lamdba表达式这种非常方便的表示方式,使用Lambda可以简化整个集合类的遍历操作,所以也就为Stream提供了基础,Stream非常类似MongoDB中的集合处理,通过管道的形式来完成数据的遍历和聚合,读这一篇文章的前提条件是了解Java8的Lamdba表达式。

Stream预览

先看一个简单的例子。

public class BaseStream {
    public static void main(String[] args) {
        List<Student> stuList = Arrays.asList(
                new Student("张三","001",19),
                new Student("李四","005",22),
                new Student("王五","010",14),
                new Student("赵六","004",18),
                new Student("何琦","006",12)
                );
        
        stuList.stream()
            .filter(s->s.getAge()>16)
            .forEach(s->System.out.println(s.getName()));
    }
}

首先创建了一个list的列表,通过stream()方法可以将列表转换为一个Stream对象,Stream就具备了管道的功能,代码中首先执行了filter,首先看filter的源码

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

该方法传入了Predicate这个Function接口,这个函数接口可以接受一个对象,返回一个boolean的值,所以我们返回了age大于16的人,此时就等于管道中只有年龄大于16岁的所有student,之后将这些数据向后提交,之后执行了forEach管道,forEach上一讲已经介绍过了,可以传入一个对象,没有返回值,在代码中我们通过输出的方式输出了这个对象。通过这个简单的例子我们可以快速了解java8的Stream,相信Stream会在未来成为java的一种重要的集合处理手段,下面我们将主要介绍Stream中常用的几个方法。

Stream的创建方式

只要是集合类都可以创建Stream,其中数组也可以支持,下面的代码显示了基于List的集合类和基于map的集合类操作,所有的Collection都有stream()方法直接将集合类转换为Stream

public class FirstStream {
    public static void main(String[] args) {
        List<String> strs = Arrays.asList("a1","a2","a3","a4","a5");
        strs.stream().forEach(System.out::println);
        Map<String,String> maps = new HashMap<String,String>();
        maps.put("001","str1");
        maps.put("002","str2");
        maps.put("003","str3");
        maps.put("004","str4");
        maps.entrySet().stream().forEach(System.out::println);
        maps.keySet().stream().forEach(System.out::println);
        maps.values().stream().forEach(System.out::println);
    }
}

处理List和Map之外,还可以直接将字符串,字符数组等类型直接转换为Stream,主要使用Stream.of方法

public class SecondStream {
    public static void main(String[] args) {
        //转换为字符的stream,转换后的值是int类型,stream的类型是IntStream
        "abcdefg0".chars().forEach(System.out::println);
        //基于字符的方式输出
        "abcdefg0".chars().forEach(c-> System.out.println((char)c));
        //转换为数组
        int[] nums = {1,2,3,4,5,6};
        Stream.of(nums).forEach(System.out::println);
        //将数组转换为stream,使用Stream.of
        Stream.of("hello,world,good,how,are,you".split(","))
                .forEach(System.out::println);
        //直接使用of的参数创建Stream
        Stream.of("a","b","c").forEach(System.out::println);
        
    }
}

还可以直接使用Lamdba来创建Stream,使用Stream的generate方法,该方法会生成一个无限的Stream流,参数是一个Supplier的函数表达式,Supplier函数接口中的方法是获取一个对象,此时只要配合limit即可创建一些满足要求的随机数流。

Stream.generate(()->(int)(Math.random()*100))
                .limit(20).forEach(System.out::println);

通过iterate可以生成某种序列的流,iterate的第一个参数是种子,种子等于第一个数,之后按照第二个参数类推出去,例子如下,生成2的倍数的流

Stream.iterate(1,i->i*2)
                .limit(10).forEach(System.out::println);

Stream可以通过limit、distinct、sorted、filter等方法对流进行处理,这些处理完成之后都返回了流对象

List<String> s = Arrays.asList("1","4","2","1","5","4","2","8","5");
        s.stream().distinct().sorted().limit(4)
                .forEach(System.out::println);

distinct表示取唯一值,sorted表示排序,limit表示取几个值。

Map、filter和toArray方法

首先看filter的源代码

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

里面的参数是Predicate,这个函数接口中的函数是test,传入一个参数返回一个boolean类型的值,filter此处就是用来进行条件过滤,然后看一下map函数的代码

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

参数是Function,Function中的apply方法传入一个参数,返回另一个参数,这个函数用来重组对象

public class FilterAndMap {
    public static void main(String[] args) {
        List<Student> list = Arrays.asList(
                new Student("001","zhangsan",20),
                new Student("002","lisi",30),
                new Student("003","wangwu",40),
                new Student("004","jake",23),
                new Student("005","Leon",21)
        );
        list.stream().filter((t)->t.getName().startsWith("z")).forEach(System.out::println);
        list.stream().filter(t->t.getAge()>25)
                .map((t)->t.getName()+"-"+t.getAge()).forEach(System.out::println);
    }
}

filter设置了条件过滤,而map把student转换为了字符串(name+age)的方式。看一下运行的结果

Student{no='001', name='zhangsan', age=20}
//map的结果
lisi-30
wangwu-40

Stream中提供了toArray方法来将Stream转换为数组,转换的数组类型是Object[],接下来看看两个操作

Object[] objs = list.stream().toArray();
//对单个对象进行转换
Stream.of(objs).forEach((obj)-> System.out.println(((Student)obj).getName()));
//直接转换为学生数组
Student[] stus = list.stream().toArray(Student[]::new);
Stream.of(stus).forEach(System.out::println);

收集和分组操作

Stream提供了分组操作,类似于数据库的groupby等操作,在介绍这些知识之前,我们首先需要给大家介绍一下Collectors,这是收集器,可以将stream中的东西收集到一个List、Set或者Map中。

Map<String,Integer> maps = list.stream()
                    .collect(Collectors.toMap(Student::getName,Student::getAge));
System.out.println(maps);
List<Student> list1 = list.stream().filter(t->t.getAge()>22)
        .sorted((t1,t2)->(t1.getAge()-t2.getAge()))
        .collect(Collectors.toList());
System.out.println(list1);

大家看一下结果

{jake=23, lisi=30, zhangsan=20, wangwu=40, Leon=21}
[Student{no='004', name='jake', age=23}, Student{no='002', name='lisi', age=30}, Student{no='003', name='wangwu', age=40}]

Collectors可以完成数据的收集工作,这是配合groupby的基础,接下来看一个groupby的实例,我们建一个Person的对象,看一下分组的实例

System.out.println(persons.stream().collect(Collectors.groupingBy(p->p.getCountry())));
//另外一种写法
System.out.println(persons.stream().collect(
    Collectors.groupingBy(Person::getCountry,
                        Collectors.counting())));

Collectors中还有一个partitioningBy,这个方法的参数是一个Precidate的函数接口,它用来统计是否满足要求的数据,返回的map中有一个是boolean,另一个是List或者Array,也可以是Collectors的集合数据(counting,max)等

 System.out.println(persons.stream()
      .collect(Collectors.partitioningBy(t->t.getCountry().equals("USA"))));
 
System.out.println(persons.stream()
      .collect(Collectors.partitioningBy(t->t.getCountry().equals("USA"),Collectors.counting())));

看看这四个的结果

{USA=[org.konghao.stream.Person@15aeb7ab, org.konghao.stream.Person@7b23ec81], China=[org.konghao.stream.Person@6acbcfc0, org.konghao.stream.Person@5f184fc6], Germany=[org.konghao.stream.Person@3feba861]}

{USA=2, China=2, Germany=1}

{false=[org.konghao.stream.Person@6acbcfc0, org.konghao.stream.Person@5f184fc6, org.konghao.stream.Person@3feba861], true=[org.konghao.stream.Person@15aeb7ab, org.konghao.stream.Person@7b23ec81]}

{false=3, true=2}

已上就是Stream的统计查询,这个功能非常的实用,虽然使用原来的方式可以实现,但工作量要大得多。

match操作

Stream提供了几种匹配操作,AllMatch表示要全部满足要求才返回true,noneMatch要所有不满足才返回真,anyMatch表示要有一个满足要求就返回true,看如下代码:

public class TestAllMatch {
    static int i = 0;
    public static void main(String[] args) {
        List<Student> list = Arrays.asList(
                new Student("001","zhangsan",20),
                new Student("002","lisi",30),
                new Student("003","wangwu",40),
                new Student("004","jake",23),
                new Student("005","Leon",21)
        );

        //只要有一个不满足要求就马上退出函数,并且返回false,AllMatch需要所有满足才为true
        boolean b1 = list.stream().allMatch(p->{
            boolean flag = p.getAge()>20;
            System.out.println("#"+(i++));
            return flag;
        });

        System.out.println(b1);
        //所有人的年龄都大于等于20,所以返回true
        boolean b2 = list.stream().allMatch(p->p.getAge()>=20);
        System.out.println(b2);

        //有一个满足就返回true
        System.out.println(list.stream().anyMatch(p->p.getName().startsWith("z")));
        //所有不满足才是true
        System.out.println(list.stream().noneMatch(p->p.getName().startsWith("k")));

    }
}

看看结果

#0
false
true
true
true

第一个才执行了一次就退出函数了,就表示只要满足条件就不会再做任何的判断了。

Lamdba的系列已经讲解完了,里面应该已经把入门的所有知识都讲解了,特别是Stream,目前可能很多人还不习惯这种操作方式,但是相信将来一定是一种主流的操作方式,因为它的确提供了非常便利的操作。希望这三部分的内容能够对大家有所帮助。

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

推荐阅读更多精彩内容