函数式编程&Stream流-java8新特性(二)

一、Stream流的介绍

1、介绍:

        Stream流是在jdk8当中提供的一个新特性,它可以去操作数组或者集合,并且是把里面的数据像流一样的形式来进行操作。

   如果流的一系列的操作要成功触发并生效,那必须需要有终结操作,一个流只能做一次终结操作,如果没有终结操作,中间操作根本不会被执行,所有的中间操作返回的都是一个Stream对象,所以可以一直链式编程。

二、Stream流的创建方式

                对于java来说,集合分为两大类:

    一个是单列集合,它的接口是Connection, 另外一个是列集合它的接口是map。

1、单列集合: 
  • 集合对象.stream() 

List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();


List<String> list = Arrays.asList("a", "b", "c", "d");
Stream<String> stream = list.stream();

  • 数组: Arrays.stream(数组)  或者使用  Stream.of(数组) 来创建

Integer[] arr = {1,2,3,4,5} ;

Stream<Integer> stream1 = Arrays.stream(arr);

Stream<Integer> stream2 = Stream.of(arr);

2、双列集合:

       转换成单列集合后再创建 

  •  ps: 关于Map的四种遍历方式、entrySet()、Stream流中的Map举例可以看我这篇文章:

                                              http://t.csdnimg.cn/Lvo0A

@Test
    public void Test08(){
        Map<String, Integer> map = new HashMap<>();

        map.put("蜡笔小新",5);

        map.put("小黑子",22);

        map.put("日向翔阳",16);

        Set<Map.Entry<String,Integer>> entries = map.entrySet();

        Stream<Map.Entry<String,Integer>> stream1 = entries.stream();

        Stream<Map.Entry<String,Integer>> stream2 = map.entrySet().stream();
    }

        我们创建完流后,还要经过一系列中间操作,最后还要加上终结操作,这样的话这个流才能真正的使用起来。

        

三、Stream流"中间操作"常用的API

   如果流的一系列的操作要成功触发并生效,那必须需要有终结操作,一个流只能做一次终结操作,如果没有终结操作,中间操作根本不会被执行,所有的中间操作返回的都是一个Stream对象,所以可以一直链式编程

Stream 有很多的 API 供我们使用,如下:

根据操作分类:

0、案例准备:

        实体类Author:


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode  // 去重
public class Author {
    private Long id;
    private String name;
    private Integer age;
    private String intro;
    private List<Book> books;
}

         实体类Book


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Book {
    private Long id;
    private String name;
    private String category;
    private Integer score;
    private String intro;
}


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class StreamDemo {

    private static List<Author> getAuthors() {
      Author author = new Author(1L, "蒙多", 17, "一个祖安人", null);
        Author author2 = new Author(2L, "亚拉索", 18, "艾欧尼亚", null);
        Author author3 = new Author(3L, "易大师", 19, "黑色玫瑰", null);
        Author author4 = new Author(3L, "易大师", 19, "黑色玫瑰", null);

        List<Book> book1 = new ArrayList<>();
        List<Book> book2 = new ArrayList<>();
        List<Book> book3 = new ArrayList<>();
        List<Book> book4 = new ArrayList<>();

        book1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情", 84, "*"));
        book1.add(new Book(2L,"一个人不能死在同一把刀下","爱情,个人成长", 80, "**"));

        book2.add(new Book(3L,"那风吹不到的地方","爱情,传记", 75, "***"));
        book2.add(new Book(3L,"那风吹不到的地方","爱情,传记", 75, "****"));
        book2.add(new Book(4L,"吹或不吹","哲学", 76, "*****"));

        book3.add(new Book(5L,"你的剑就是我的剑","个人成长", 78, "******"));
        book3.add(new Book(6L,"风与剑","传记", 82, "*******"));
        book3.add(new Book(6L,"风与剑","传记", 82, "********"));

        book4.add(new Book(5L,"你的剑就是我的剑","个人成长", 78, "******"));
        book4.add(new Book(6L,"风与剑","传记", 82, "*******"));
        book4.add(new Book(6L,"风与剑","传记", 82, "*******"));



        author.setBooks(book1);
        author2.setBooks(book2);
        author3.setBooks(book3);
        author4.setBooks(book4);

        List<Author> authors = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
        return authors;
    }
}

常用的中间操作API如下:


1、filter:

                对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中

                         

        

  •  案例:需要打印所有年龄小于18的作家的名字,并且注意去重:
  private static void test01() {
        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .filter(new Predicate<Author>() {
                    @Override
                    public boolean test(Author author) {
                        return author.getAge()< 18;
                    }
                })
                .forEach(new Consumer<Author>(){
                    public void accept(Author author){
                        System.out.println(author.getName());
                    }
                });
    }
  • Lambda表达式优化匿名内部类:
  private static void test01() {
        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .filter(author -> author.getAge()<18)
                .forEach(author -> System.out.println(author.getName()));
    }
2、distinct:

       可以去除流中的重复元素,distinct方法是依赖Object的equals方法来判断是否是相同对象的,所以需要注意重写equals方法,即加注解 @EqualsAndHashCode

  • 案例:对作家进行去重:

List<Author> authors = getAuthors();

authors.stream().distinct()
3、map:

        对Stream流中的元素进行计算或者对Stream流中的元素进行数据类型的转换

                

  • 3.1、案例一:打印所有作家的姓名

        map方法,它的参数是function ,它有两个泛型,第一个泛型Author就是流元素类型,第一个泛型不能修改,改了报错,第一个泛型必须和流当中的泛型是一样的

        但是第二个是非常重要的,它代表了你期望通过map把Author转换成什么类型,如果说你还是转换成Author,那你就写Author:

        但是我期望是把它转换成一个字符串。我希望到时候流当中的每个元素都是一个字符串儿,就是这个作家的姓名,就写String

        经过这一步map操作之后,其实流里面都是一个一个的字符串了:

  •  Lambda表达式优化匿名内部类:

  • 3.2、也可以对流中的元素进行计算,案例二:
 private static void test02() {
        List<Author> authors = getAuthors();
        authors.stream()
                .map(author -> author.getAge())
                .map(age -> age + 10)
                .distinct()
                .forEach(age -> System.out.println(age));
    }
4、sorted:

        可以对流中的元素进行排序 

  • 我们在调用sorted时会发现它有两种传参形式:

  • 4.1、空参的sorted()方法

案例:对流中的元素按照年龄进行降序排序 

    如果调用空参的sorted()方法,需要流中的元素实现了Comparable接口,否则会抛出

                        java.lang.ClassCastException  类型转换异常

        实际上这里在进行排序的时候,他会把流当中的Author类型的元素,转换成Comparable接口类型,再去进行相应方法的调用。

        但是现在的Author 和 Comparable接口是没有关系的,它并没有实现Comparable这个接口,所以肯定会出现类型转换异常:

        如果一个类想让它具有比较的能力,你可以选择去实现这个Comparable接口,在compareTo()方法当中去定义如何去比较两个元素/两个对象:

        我们这里要求是按照年龄进行排序,所以我们就可以拿this当前作家和你所传入进来的Author o对象去进行一个年龄的比较去进行一个相减的操作,比较的结果是int类型,其实只要去返回他们的比较结果就可以了:

                                以this为准,this在左是升序,this在右是降序

                                o1-o2是升序,o2-o1是降序,o1是当前项

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode  // 去重
public class Author implements Comparable<Author>{
    private Long id;
    private String name;
    private Integer age;
    private String intro;
    private List<Book> books;

    @Override
    public int compareTo(Author o) {
        return  o.getAge()- this.getAge();
    }
}

 结果输出:

  • 4.2、传入对 Comparator 接口的实现:

案例:对流中的元素按照年龄进行降序排序 

                                 以this为准,this在左是升序,this在右是降序

                                 o1-o2是升序,o2-o1是降序,o1是当前项

    private static void test02(){
        List<Author> authors = getAuthors();
        authors.stream()
                .sorted(new Comparator<Author>(){
                    public int compare(Author o1 , Author o2){
                        return o2.getAge()-o1.getAge();
                    }
                })
                .forEach(author -> System.out.println(author.getAge()));
    }
  •  Lambda表达式优化匿名内部类:
    private static void test02(){
        List<Author> authors = getAuthors();
        authors.stream()
                .sorted((o1,o2) -> o2.getAge()-o1.getAge())
                .forEach(author -> System.out.println(author.getAge()));
    }
5、flatMap:

        map只能把一个对象转换成另一个对象来作为流中的元素,而flatMap可以把一个对象转换成多个对象作为流中的元素

它的参数是传进来Author作家,也就是说每个元素都是作家类型的,然后你要注意它返回的类型:要求我们返回的是Stream流对象:

                                                         

5.1、案例一:

 要求打印所有书籍的名字, 书籍当中如果有重复的话,我们要进行去重

  • 很多同学没有学过flatMap,很有可能做的一件事情是这样的:
private static void tesst04(){
        List<Author> authors = getAuthors();
        authors.stream()
                .map(author -> author.getBooks())
                .forEach(new Consumer<List<Book>>() {
                    @Override
                    public void accept(List<Book> books) {
      // 这样写的话,流当中的每一个元素都是List<Book>,不是Book
      // 整体是 List<List<Book>>
      // 我们还要在这里面再进行forEach遍历
                    }
                });
    }
  •    这种方式其实就不太符合我们的期望了,我们其实就是想把List<List<Book>>集合里面的每个List<Book>元素都转换成一个Book对象:即让流中的每一个元素都变成Book对象;
  •      所以应该使用到flatMap:首先我们先调用author.getBooks()方法, 拿到的是一个List<Book>集合,然后把这个集合转换成对应的Stream流对象 , 并且现在流当中的元素类型就是我们的Book类型,他会自动把这些流进行一个拼接。

           相对于是把List<List<Book>>集合当中的每一个元素转化为多个元素返回:

  •   Lambda表达式优化匿名内部类:
  private static void tesst04(){
        List<Author> authors = getAuthors();
        authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .forEach(book -> System.out.println(book.getName()));
    }
 5.2、案例二:

打印现有书籍的所有分类。要求对分类进行去重、不能出现这种格式:哲学,爱情:

        如果说这本书是属于多个分类”哲学、爱情“,那我们应该在多个分类之间用逗号进行分割,把它转换成两个分类:一个是哲学,一个是爱情,相当于就是进行一个字符串的分割:

                        

        调用String.split()方法,将一个字符串分割为子字符串,然后将结果作为字符串数组返回:返回的是一个字符型数组,如果说他只有一种分类的这种情况,它其实也会转换成一个字符串数组,只不过数组里只有一个元素。

        flatMap() 方法 它要求返回的是Stream流对象,所以你要把这个数组转换成流对象,数组怎么转换成流对象,前面讲过:

        

                .flatMap(book -> Stream.of(book.getCategory().split(",")))

                .flatMap(book-> Arrays.stream(book.getCategory().split(",")))

  //打印现有书籍的所有分类。要求对分类进行去重
    //1、先获得所有的书籍 Stream<Book>
    //2、再获得所有的书籍的分类
    private static void test05(){
        List<Author> authors = getAuthors();
        authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct() //这里的去重是对流当中的书籍去重
                .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
                .distinct()//这里的去重是对流当中的类别去重
                .forEach(name -> System.out.println(name));
    }
 6、limit:

        可以设置流的最大长度,超出的部分将被抛弃掉,相当于进行了一个截取

  •  案例:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名:

        可以先进行排序再去重,也可以先去重再排序,实际上先去重效果会更好一点,因为你去重之后,它参与排序的元素就不用那么多了:

    private static void test06(){
        //对流中的元素按照年龄进行降序排序,
        // 并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名
        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .sorted((o1,o2)->o2.getAge()-o1.getAge())
                .limit(2)
                .forEach(author -> System.out.println(author.getName()));
    }
 7、sikp:

        跳过流中的前n个元素,返回剩下的元素

  • 案例:打印除了年龄最大的作家以外的其他作家,要求不能有重复元素,并且按照年龄降序排列:
 private static void test07(){
        //打印除了年龄最大的作家以外的其他作家,
        // 要求不能有重复元素,并且按照年龄降序排列
        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .sorted(new Comparator<Author>() {
                    @Override
                    public int compare(Author o1, Author o2) {
                        return o2.getAge()-o1.getAge();
                    }
                })
                .skip(1)
                .forEach(author -> System.out.println(author.getName()));
    }

四、Stream流"终结操作"常用的API

        如果流的一系列的操作要成功触发并生效,那必须需要有终结操作,一个流只能做一次终结操作,如果没有终结操作,中间操作根本不会被执行,所有的中间操作返回的都是一个Stream对象,所以可以一直链式编程

1、forEach:

        对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体的操作

                 

  •  案例:输出所有作家的名字
//        输出所有作家的名字
        List<Author> authors = getAuthors();
        authors.stream()
                .map(author -> author.getName())
                .distinct()
                .forEach(name-> System.out.println(name));
 2、count:

        可以用来获取当前流中的元素

  •  案例:打印这些作家的所出书籍的数目,注意删除重复元素,不同的作家没有同名字的书籍
    private static void test08(){
        //打印这些作家的所出书籍的数目,注意删除重复元素
        List<Author> authors = getAuthors();
        Long counts = authors.stream()
                .distinct()
                .flatMap(author -> author.getBooks().stream())
                .count();
        System.out.println(counts);
    }
3、max、min

        可以用来获取流中的最值

  •  案例: 分别获取这些作家的所出书籍的最高分和最低分并打印。

最高分:

    private static void test09(){
        List<Author> authors = getAuthors();
        //分别获取这些作家的所出书籍的最高分和最低分并打印。
        Book book = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .max(new Comparator<Book>() {
                    @Override
                    public int compare(Book o1, Book o2) {
                        return o1.getScore() - o2.getScore();
                    }
                })
                .get();
        System.out.println(book.getScore());
    }
  private static void test10(){
        List<Author> authors = getAuthors();
        Integer max_score = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .map(book -> book.getScore())
                .max((o1,o2) -> o1-o2)
                .get();
        System.out.println(max_score);
    }

最低分: 

4、collect 

        把当前流转换成一个集合

         我们使用Stream流对数据进行了一系列的处理之后, 我最终的这些数据可能还要返回给别人去使用,例如返回成集合的一个形式,那这个时候就需要把流再转换成集合。

        我们知道集合其实主要有两种,一种是单列集合,单列集合其实主要就两种:List和Set;另一种双列集合里面,就是map集合。

        接下去我们就来看一下如何使用collect方法,把Stream流转分别转换成这三种集合:

          这里要注意collect方法,这个参数会比较复杂一点。实际上我们在用它的时候,是不需要直接写匿名内部类的,因为这个参数是非常复杂的,我们用的是它的工具类,它提供好的一个工具类Collectors,专门供我们这个collect方法去使用的:

         Collectors工具类,它里面提供了一些静态方法,因为工具里面基本上都是静态的成员,所以我们直接类名点就可以了:

4.1 转化成list集合——toList()方法:
  • 案例:获取一个存放所有作者名字的集合
 private static void test12(){
        //获取一个存放所有作者名字的集合
        List<Author> authors = getAuthors();
        List<String> names = authors.stream()
                .map(author -> author.getName())
                .distinct()
                .collect(Collectors.toList());
        System.out.println(names);//[蒙多, 亚拉索, 易大师]
    }
4.2 转化成set集合——toSet()方法:

           toSet()方法和toList()方法相比较而言,toSet()方法可以自动去重,

                因为Set集合本身是无序不可重复嘛

 private static void test12(){
        //获取一个存放所有作者名字的集合
        List<Author> authors = getAuthors();
        Set<String> names = authors.stream()
                .map(author -> author.getName())
                .collect(Collectors.toSet());
        System.out.println(names);//[亚拉索, 蒙多, 易大师]
    }
4.3 转化成Map集合——toMap()方法:
  • 案例:获取一个Map集合,map的key为作者名,value为List<Book>

转化成Map集合相对来说比较复杂一点,

我们要把流当中的元素转换成一个map,map是有key和value的,那你得告诉流当中的元素怎么转换成key,怎么转换成value,所以它要求你传入两个参数都是Function类型,实际上这个接口是可以进行转换的,我们前面的map方法,它的参数其实都是这个function接口类型的。

所以我们要传入两个Function,一个是告诉他怎么转换成key,另外一个是告诉他怎么转换成value:  你要去指定一下从哪个类型转换成哪个类型。

  • 20
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值