函数式编程-Stream流简单讲解

目录

1.0 概述

2.0 Lambda表达式练习

        2.2 省略规则 

3.0 Stream流

        3.1 概述

        3.2 初始化一个数据,用来对流操作的学习

数据初始化

        3.3 创建流

        3.4 中间件

4.0 Optional

        4.1 概述

        4.2 安全消费

        4.3 安全获取值

5.0 函数时接口

        5.1 概述

        5.2 常用方法

         5.21 and or negate(三个方法)

6.0 方法引用

        6.1 推荐使用

7.0 高级特性

        7.2 并行流


1.0 概述

        如果匿名内部类是一个接口,并且只有一个抽象方法,可以使用Lambda表达式。

2.0 Lambda表达式练习

        2.1 练习由简单到深入

    public static int calculateNum(IntBinaryOperator operator){
        int a = 10;
        int b = 20;
        return operator.applyAsInt(a, b);
    }

        匿名内部类形式

            calculateNum(new IntBinaryOperator() {
                @Override
                public int applyAsInt(int left, int right) {
                    return left + right;
                }
            });

        简化后

calculateNum((left, right) -> left + right);

        可以通过点击接口名然后Alt键+回车转化为Lambda表达式简化后的形式

        2.2 省略规则 

  • 参数类型可以省略

  • 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略

  • 方法只有一个参数时小括号可以省略

  • 以上这些规则都记不住也可以省略不记

3.0 Stream流

        3.1 概述

        Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们对集合或数组操作。

        3.2 初始化一个数据,用来对流操作的学习

        

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Book {
    //id
    private Long id;
    //书名
    private String name;

    //分类
    private String category;

    //评分
    private Integer score;

    //简介
    private String intro;

}
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Author {
    //id
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //简介
    private String intro;
    //作品
    private List books;
}

数据初始化

 private static List getAuthors() {
        //数据初始化
        Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
        Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);
        Author author3 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
        Author author4 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);

        //书籍列表
        List books1 = new ArrayList<>();
        List books2 = new ArrayList<>();
        List books3 = new ArrayList<>();

        books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
        books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));

        books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
        books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
        books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"一个哲学家的恋爱观注定很难把他所在的时代理解"));

        books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"无法想象一个武者能对他的伴侣这么的宽容"));
        books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
        books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));

        author.setBooks(books1);
        author2.setBooks(books2);
        author3.setBooks(books3);
        author4.setBooks(books3);

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

        3.3 创建流

         3.3.1 单列集合: `集合对象.stream()

        List authors = getAuthors();
		Stream stream = authors.stream();

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

        Integer[] arr = {1,2,3,4,5};
        Stream stream = Arrays.stream(arr);
        Stream stream2 = Stream.of(arr);

        3.3.3 双列集合:转换成单列集合后再创建

        Map map = new HashMap<>();
        map.put("蜡笔小新",19);
        map.put("黑子",17);
        map.put("日向翔阳",16);

        Stream> stream = map.entrySet().stream();

        在以上流的基础上,做一些中间操作如filter过滤、distinct()去重、sorted 排序、limit限制流的长度、 map转化,这里的map与集合的map有所不同等,下面进行详解

        3.4 中间件

        3.4.1 filter 

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

        例如:打印所有姓名长度大于1的作家的姓名

        List authors = getAuthors();
        authors.stream()
                .filter(author -> author.getName().length()>1)
                .forEach(author -> System.out.println(author.getName()));

        3.4.2 map

        可以把对流中的元素进行计算转换

        List authors = getAuthors();
        authors
                .stream()
                // 这里转换,本来集合中流元素是Author也就是泛型当中的类型
                // Function 第一个参数不能改边,第二个参数可以改成自己需要的类型
                // 这里是将流中Author类型转化为字符串类型
                .map(new Function() {
                    @Override
                    public String apply(Author author) {
                        return author.getName();
                    }
                });

        使用lambda表达式

        List authors = getAuthors();
        authors
                .stream()
                // 这里转换,本来集合中流元素是Author也就是泛型当中的类型
                // Function 第一个参数不能改边,第二个参数可以改成自己需要的类型
                // 这里是将流中Author类型转化为字符串类型
                .map(author -> author.getName());

        当map为计算的时候 

        List authors = getAuthors();
        authors.stream()
                //这里是将流中Author转化为Integer类型
                .map(author -> author.getAge())
                // 对流进行计算年龄+10操作后打印
                .map(age->age+10)
                .forEach(age-> System.out.println(age));

        3.4.3 distinct

        可以去除流中的重复元素。

        例如: 打印所有作家的姓名,并且要求其中不能有重复元素。

        List authors = getAuthors();
        authors.stream()
                .distinct()
                .forEach(author -> System.out.println(author.getName()));

注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法。

        3.4.4 sorted

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

        例如:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。

        List authors = getAuthors();
//        对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
        authors.stream()
                .distinct()
                .sorted((o1, o2) -> o2.getAge()-o1.getAge())
                .forEach(author -> System.out.println(author.getAge()));

        3.4.5 limit

        可以设置流的最大长度,超出的部分将被抛弃。

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

 List authors = getAuthors();
        authors.stream()
                .distinct()
                .sorted()
                .limit(2)
                .forEach(author -> System.out.println(author.getName()));

        3.4.6 skip

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

        例如:打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。

//        打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
        List authors = getAuthors();
        authors.stream()
                .distinct()
                .sorted()
                .skip(1)
                .forEach(author -> System.out.println(author.getName()));

        3.4.7 flatMap

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

        例如: 打印所有书籍的名字。要求对重复的元素进行去重。

//        打印所有书籍的名字。要求对重复的元素进行去重。
        List authors = getAuthors();

        authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .forEach(book -> System.out.println(book.getName()));

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

//        打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情     爱情
        List authors = getAuthors();
        authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
                .distinct()
                .forEach(category-> System.out.println(category));

        3.4.8 终结操作 -- forEach

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

        例如 :输出所有作家的名字

//        输出所有作家的名字
        List authors = getAuthors();

        authors.stream()
                .map(author -> author.getName())
                .distinct()
                .forEach(name-> System.out.println(name));

        3.4.9 终结操作 -- count

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

//        打印这些作家的所出书籍的数目,注意删除重复元素。
        List authors = getAuthors();

        long count = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .count();
        System.out.println(count);

        3.4.10 终结操作 -- max&min

                 可以用来或者流中的最值。

//        分别获取这些作家的所出书籍的最高分和最低分并打印。
        //Stream  -> Stream ->Stream  ->求值

        List authors = getAuthors();
        Optional max = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .max((score1, score2) -> score1 - score2);

        Optional min = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .min((score1, score2) -> score1 - score2);
        System.out.println(max.get());
        System.out.println(min.get());

        3.4.11 终结操作 -- collect

        把当前流转换成一个集合。

//        获取一个存放所有作者名字的List集合。
        List authors = getAuthors();
        List nameList = authors.stream()
                .map(author -> author.getName())
                .collect(Collectors.toList());
        System.out.println(nameList);
//        获取一个所有书名的Set集合。
        List authors = getAuthors();
        Set books = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .collect(Collectors.toSet());

        System.out.println(books);
//        获取一个Map集合,map的key为作者名,value为List
        List authors = getAuthors();

        Map> map = authors.stream()
                .distinct()
                .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));

        System.out.println(map);

        3.4.12 查找与匹配 -- anyMatch

                可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型。

//        判断是否有年龄在29以上的作家
        List authors = getAuthors();
        boolean flag = authors.stream()
                .anyMatch(author -> author.getAge() > 29);
        System.out.println(flag);

         3.4.13 查找与匹配 -- allMatch

       可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则结果为false。

//        判断是否所有的作家都是成年人
        List authors = getAuthors();
        boolean flag = authors.stream()
                .allMatch(author -> author.getAge() >= 18);
        System.out.println(flag);

         3.4.14 查找与匹配 -- noneMatch

        可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false

//        判断作家是否都没有超过100岁的。
        List authors = getAuthors();

        boolean b = authors.stream()
                .noneMatch(author -> author.getAge() > 100);

        System.out.println(b);

         3.4.15 查找与匹配 -- findAny

        获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。

//        获取任意一个年龄大于18的作家,如果存在就输出他的名字
        List authors = getAuthors();
        Optional optionalAuthor = authors.stream()
                .filter(author -> author.getAge()>18)
                .findAny();

        optionalAuthor.ifPresent(author -> System.out.println(author.getName()));

        3.4.16 查找与匹配 -- findFirst

        获取流中的第一个元素。

//        获取一个年龄最小的作家,并输出他的姓名。
        List authors = getAuthors();
        Optional first = authors.stream()
                .sorted((o1, o2) -> o1.getAge() - o2.getAge())
                .findFirst();

        first.ifPresent(author -> System.out.println(author.getName()));

         3.4.16 reduce归并

        对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)

        reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算。

         reduce两个参数的重载形式内部的计算方式如下:

T result = identity;
for (T element : this stream)
	result = accumulator.apply(result, element)
return result;

        其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。

        例子:

//        使用reduce求所有作者年龄的和
        List authors = getAuthors();
        Integer sum = authors.stream()
                .distinct()
                .map(author -> author.getAge())
                .reduce(0, (result, element) -> result + element);
        System.out.println(sum);
//        使用reduce求所有作者中年龄的最大值
        List authors = getAuthors();
        Integer max = authors.stream()
                .map(author -> author.getAge())
                .reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);

        System.out.println(max);

       注意事项

  • 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)

  • 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)

  • 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)

4.0 Optional

        4.1 概述

        我们在编写代码的时候出现最多的就是空指针异常。所以在很多情况下我们需要做各种非空的判断。

        Optional就好像是包装类,可以把我们的具体数据封装Optional对象内部。然后我们去使用Optional中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。

        我们一般使用Optional静态方法ofNullable来把数据封装成一个Optional对象。无论传入的参数是否为null都不会出现问题。

        Author author = getAuthor();
        Optional authorOptional = Optional.ofNullable(author);

        你可能会觉得还要加一行代码来封装数据比较麻烦。但是如果改造下getAuthor方法,让其的返回值就是封装好的Optional的话,我们在使用时就会方便很多。

        而且在实际开发中我们的数据很多是从数据库获取的。Mybatis从3.5版本可以也已经支持Optional了。我们可以直接把dao方法的返回值类型定义成Optional类型,MyBastis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。

        4.2 安全消费

        我们获取到一个Optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其ifPresent方法对来消费其中的值。

        这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码。这样使用起来就更加安全了。

例如,以下写法就优雅的避免了空指针异常。

        Optional authorOptional = Optional.ofNullable(getAuthor());

        authorOptional.ifPresent(author -> System.out.println(author.getName()));

        4.3 安全获取值

        它提供了一个get方法,但是如果为空是会报异常

        orElseGet

        获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建对象作为默认值返回。

        Optional authorOptional = Optional.ofNullable(getAuthor());
        Author author1 = authorOptional.orElseGet(() -> new Author());

        orElseThrow

        Optional authorOptional = Optional.ofNullable(getAuthor());
        try {
            Author author = authorOptional.orElseThrow((Supplier) () -> new RuntimeException("author为空"));
            System.out.println(author.getName());
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        4.4 过滤和判断

         我们可以使用filter方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。

        Optional authorOptional = Optional.ofNullable(getAuthor());
        authorOptional.filter(author -> author.getAge()>100).ifPresent(author -> System.out.println(author.getName()));

         我们可以使用isPresent方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为true。但是这种方式并不能体现Optional的好处,更推荐使用ifPresent方法

        Optional authorOptional = Optional.ofNullable(getAuthor());

        if (authorOptional.isPresent()) {
            System.out.println(authorOptional.get().getName());
        }

5.0 函数时接口

        5.1 概述

        只有一个抽象方法的接口我们称之为函数接口。

        JDK的函数式接口都加上了@FunctionalInterface 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。

        5.2 常用方法

         5.21 and or negate(三个方法)

        and

        我们在使用Predicate接口时候可能需要进行判断条件的拼接。而and方法相当于是使用&&来拼接两个判断条件

        例如:打印作家中年龄大于17并且姓名的长度大于1的作家。

        List authors = getAuthors();
        Stream authorStream = authors.stream();
        authorStream.filter(new Predicate() {
            @Override
            public boolean test(Author author) {
                return author.getAge()>17;
            }
        }.and(new Predicate() {
            @Override
            public boolean test(Author author) {
                return author.getName().length()>1;
            }
        })).forEach(author -> System.out.println(author));

        or

        我们在使用Predicate接口时候可能需要进行判断条件的拼接。而or方法相当于是使用||来拼接两个判断条件。

        例如:打印作家中年龄大于17或者姓名的长度小于2的作家。

//        打印作家中年龄大于17或者姓名的长度小于2的作家。
        List authors = getAuthors();
        authors.stream()
                .filter(new Predicate() {
                    @Override
                    public boolean test(Author author) {
                        return author.getAge()>17;
                    }
                }.or(new Predicate() {
                    @Override
                    public boolean test(Author author) {
                        return author.getName().length()<2;
                    }
                })).forEach(author -> System.out.println(author.getName()));

negate

Predicate接口中的方法。negate方法相当于是在判断添加前面加了个! 表示取反

例如:打印作家中年龄不大于17的作家。

//        打印作家中年龄不大于17的作家。
        List authors = getAuthors();
        authors.stream()
                .filter(new Predicate() {
                    @Override
                    public boolean test(Author author) {
                        return author.getAge()>17;
                    }
                }.negate()).forEach(author -> System.out.println(author.getAge()));

6.0 方法引用

        我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。  

        6.1 推荐使用

        我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可

当我们方法引用使用的多了慢慢的也可以直接写出方法引用。

        格式

类名::方法名

        6.2.1 使用前提

        如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。

        List authors = getAuthors();

        Stream authorStream = authors.stream();
        
        authorStream.map(author -> author.getAge())
                .map(age->String.valueOf(age));

        优化后

        List authors = getAuthors();

        Stream authorStream = authors.stream();

        authorStream.map(author -> author.getAge())
                .map(String::valueOf);

        6.2.2 使用前提

        如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法

        List authors = getAuthors();

        Stream authorStream = authors.stream();
        StringBuilder sb = new StringBuilder();
        authorStream.map(author -> author.getName())
                .forEach(name->sb.append(name));

        优化后:

        List authors = getAuthors();

        Stream authorStream = authors.stream();
        StringBuilder sb = new StringBuilder();
        authorStream.map(author -> author.getName())
                .forEach(sb::append);

        6.2.3 使用前提

         如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。

    interface UseString{
        String use(String str,int start,int length);
    }

    public static String subAuthorName(String str, UseString useString){
        int start = 0;
        int length = 1;
        return useString.use(str,start,length);
    }
    public static void main(String[] args) {

        subAuthorName("小周课堂", new UseString() {
            @Override
            public String use(String str, int start, int length) {
                return str.substring(start,length);
            }
        });

	}

        优化后

    public static void main(String[] args) {

        subAuthorName("小周课堂", String::substring);

    }

7.0 高级特性

        我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。

        即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。

        所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。

        例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等。

    private static void test27() {

        List authors = getAuthors();
        authors.stream()
                .map(author -> author.getAge())
                .map(age -> age + 10)
                .filter(age->age>18)
                .map(age->age+2)
                .forEach(System.out::println);

        authors.stream()
                .mapToInt(author -> author.getAge())
                .map(age -> age + 10)
                .filter(age->age>18)
                .map(age->age+2)
                .forEach(System.out::println);
    }

        7.2 并行流

        当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。

        parallel方法可以把串行流转换成并行流。

 private static void test28() {
        Stream stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Integer sum = stream.parallel()
                .peek(new Consumer() {
                    @Override
                    public void accept(Integer num) {
                        System.out.println(num+Thread.currentThread().getName());
                    }
                })
                .filter(num -> num > 5)
                .reduce((result, ele) -> result + ele)
                .get();
        System.out.println(sum);
    }

也可以通过parallelStream直接获取并行流对象。

        List authors = getAuthors();
        authors.parallelStream()
                .map(author -> author.getAge())
                .map(age -> age + 10)
                .filter(age->age>18)
                .map(age->age+2)
                .forEach(System.out::println);

        以上是我通过B站博主三更草堂视频笔记学习获得。

你可能感兴趣的:(数据结构,开发语言)