函数式编程-stream流

1.概述

1.1 为什么学

  • 看懂公司代码
  • 大数量下处理集合效率高
  • 代码可读性高
  • 消灭嵌套地狱

1.2 函数式编程思想

1.2.1 概念

面向对象思想需要关注用什么对象完成什么事情,而函数式编程思想就类似于我们数学中的函数。它主要关注的是对数据进行了什么操作。

1.2.2 优点
  • 代码简介,开发快速
  • 接近自然语言,易于理解
  • 易于“并发编程”

2.Lambda 表达式

2.1 概述

Lambda是JDK8中一个语法糖。对一些匿名内部类的写法进行简化,让我们更加关注对数据的处理

2.2 核心原则

可推导可省略

2.3 基本格式

(参数列表)->{代码}
例一
//匿名内部类
new Thread(new Runnable() {
public void run() {
System.out.println("Hello World!");
}
}).start();
//Lambda表达式
new Thread(()-> System.out.println("Hello World!"));
例二
//匿名内部类
int res = calculateNum(new IntBinaryOperator() {
    @Override
    public int applyAsInt(int left, int right) {
    	return left + right;
    }
});

private static int calculateNum(IntBinaryOperator operator){
    int a = 10;
    int b = 20;
    return operator.applyAsInt(a,b);
}
//Lambda
int res = calculateNum(((left, right) -> left+right));

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

2.4 省略规则

  • 参数类型可以省略
  • 方法体只有一句代码时大括号、return和唯一一句代码的分号可以省略
  • 方法只有一个参数时小括号可以省略

3.Stream流

3.1 概述

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

3.2 准备

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //去重的使用,自动重写equals方法
public class Author {
    private Long id;
    private String name;
    private Integer age;
    //简介
    private String intro;
    private List<Book> books;
}


@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //去重的使用,自动重写equals方法
public class Book {
    private Long id;
    private String name;
    private String category;
    private Integer score;
    private String intro;
}

public List<Author> authors(){
    Author a1 = new Author(1L,"a1",18,"a1",null);
    Author a2 = new Author(2L,"a2",19,"a2",null);
    Author a3 = new Author(3L,"a3",20,"a3",null);
    Author a4 = new Author(4L,"a4",21,"a4",null);
    List<Book> books1 = new ArrayList<>();
    List<Book> books2 = new ArrayList<>();
    List<Book> books3 = new ArrayList<>();
    List<Book> books4 = new ArrayList<>();
    Book b1 = new Book(1L,"b1","哲学,爱情",1,null);
    Book b2 = new Book(2L,"b2","个人成长,爱情",2,null);
    Book b3 = new Book(3L,"b3","爱情",3,null);
    Book b4 = new Book(4L,"b4","个人传记",4,null);
    Book b5 = new Book(5L,"b5","爱情,个人传记",1,null);
    Book b6 = new Book(6L,"b6","哲学",1,null);
    books1.add(b1);
    books1.add(b2);
    books1.add(b3);

    books2.add(b3);
    books2.add(b4);
    books2.add(b5);

    books3.add(b6);
    books3.add(b3);
    books3.add(b1);

    books4.add(b2);
    books4.add(b5);
    books4.add(b6);

    List<Author> authors = new ArrayList<>();
    authors.add(a1);
    authors.add(a2);
    authors.add(a3);
    authors.add(a4);
    return authors;
}

3.3 快速入门

3.3.1 需求

打印年龄小于20的作家名字,并去重

3.3.2 实现
List<Author> authors = authors();
authors.stream()//集合转换成流
        .distinct()//去重
        .filter((author)->author.getAge()<20)
        .forEach((author)-> System.out.println(author.getName()));

3.4 常用操作

3.4.1 创建流

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

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

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

3.4.2 中间操作
  • filter

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

  • map

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

    List<Author> authors = authors();
    authors.stream()
        .map(author -> author.getAge() + 10)
        .forEach(age -> System.out.println(age));
    
    List<Author> authors = authors();
    authors.stream()
            .map(author -> author.getName())//将流中的数据用author.name替换
            .forEach(name -> System.out.println(name));
    
  • distinct

    可以去除流中的重复元素,依赖于equals方法,所以需要重写equals方法

  • sorted

    可以对流中的元素进行排序,如果使用默认sorted,则实现类需要实现Comparable接口并实现方法

例如

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

List<Author> authors = authors();
authors.stream()
    .distinct()
    .sorted((o1, o2) -> o2.getAge() - o1.getAge())
    .forEach(author -> System.out.println(author));
  • limit

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

例如

​ 对流中的元素按照年龄降序,无重复值,然后打印其中年龄最大的两个作家的名字

List<Author> authors = authors();
authors.stream()
    .distinct()
    .sorted((o1,o2)->o2.getAge()-o1.getAge())
    .limit(2)
    .forEach(author -> System.out.println(author));
  • skip

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

例如

​ 打印除了年龄最大的作家外的其它作家,无重复,并降序

List<Author> authors = authors();
authors.stream()
    .distinct()
    .sorted((o1,o2)->o2.getAge()-o1.getAge())
    .skip(1)//跳过几个
    .forEach(author -> System.out.println(author));
  • flatMap

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

例一

​ 打印所有书籍的名字。去除

        List<Author> authors = authors();

        authors.stream()
                .flatMap(new Function<Author, Stream<Book>>() {
                    @Override
                    public Stream<Book> apply(Author author) {
                       return author.getBooks().stream();
                    }
                })
                .distinct()
                .forEach(new Consumer<Book>() {
                    @Override
                    public void accept(Book book) {
                        System.out.println(book.getName());
                    }
                });

 List<Author> authors = authors();

        authors.stream()
                .flatMap((Function<Author, Stream<Book>>) author -> author.getBooks().stream())
                .distinct()
                .forEach(book -> System.out.println(book.getName()));

例二

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

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

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

例子

​ 输出所有作家的名字

        List<Author> authors = authors();
        authors.stream()
                .map(author -> author.getName())
                .distinct()
                .forEach(name-> System.out.println(name));
  • count

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

例子

​ 打印这些作家的所出书籍的数目,注意删除重复元素

        List<Author> authors = authors();
        long count = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .count();
        System.out.println(count);
  • max&min

    可以用来获取流中的最值

例子

​ 分别获取这些作家的所出书籍的最高分和最低分

        List<Author> authors = authors();
        Optional<Integer> max = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .max((s1, s2) -> s1 - s2);
        System.out.println(max.get());

        Optional<Integer> min = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .min((s1, s2) -> s1 - s2);
        System.out.println(min.get());
  • collect

    当前流转换成集合

例子

​ 获取一个存放所有作者名字的list集合

        List<String> collect = authors().stream()
                .map(author -> author.getName())
                .collect(Collectors.toList());
        System.out.println(collect);

​ 获取一个所有书名的set集合

        Set<String> collect = authors().stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getName())
                .distinct()
                .collect(Collectors.toSet());
        System.out.println(collect);

​ 获取一个map集合,map的key为作者名,value为List

        Map<String, List<Book>> collect = authors().stream()
                .distinct()
                .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
        System.out.println(collect);
查找与匹配
  • anyMatch

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

例子

​ 判断是否有年龄在18以上的作家

 boolean b = authors().stream()
     .anyMatch(author -> author.getAge() > 18);
System.out.println(b);
  • allMatch

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

例子

​ 判断是否所有的作家都是成年人

        boolean b = authors().stream()
                .allMatch(author -> author.getAge() >= 18);
        System.out.println(b);
  • noneMatch

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

例子

​ 判断作家是否都没有超过100岁的

        boolean b = authors().stream()
                .noneMatch(author -> author.getAge() < 100);
        System.out.println(b);
  • findAny

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

例子

​ 获取任意一个大于18的作家,如果存在就输出名字

        Optional<Author> any = authors().stream()
                .filter(author -> author.getAge() > 18)
                .findAny();
        any.ifPresent(author -> System.out.println(author.getName()));
  • finFirst

    获取流中第一个元素

例子

​ 获取一个年龄最小的作家,并输出他的姓名

        Optional<Author> first = authors().stream()
                .sorted((s1, s2) -> s1.getAge() - s2.getAge())
                .findFirst();
        first.ifPresent(author -> System.out.println(author.getName()));
  • reduce归并

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

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

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

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

例子

​ 使用reduce求所有作者年龄的和

        Integer reduce = authors().stream()
                .distinct()
                .map(author -> author.getAge())
                .reduce(0, (result, element) -> result + element);
        System.out.println(reduce);

​ 使用reduce求所有作者年龄的最大值

        Integer max = authors().stream()
                .map(author -> author.getAge())
                .distinct()
                .reduce(Integer.MIN_VALUE, (result, element) -> result = Math.max(result, element));
        System.out.println(max);

​ 使用reduce求所有作者中年龄的最小值

        Integer min = authors().stream()
                .map(author -> author.getAge())
                .distinct()
                .reduce(Integer.MAX_VALUE, (result, element) -> result = Math.min(result, element));
        System.out.println(min);

reduce一个参数的重载形式内部的计算

boolean foundAny = false;
T result = null;
//将第一个元素作为初始化值
for(T element : this stream){
	if(!foundAny){
		foundAny = true;
		result = element;
	}else
		result = accumlator.apply(result,element);		
}
return foundary?Optional.of(result) : Optional.empty();

如果用一个参数的重载方法求最小值代码如下

        Optional<Integer> min = authors().stream()
                .map(author -> author.getAge())
                .distinct()
                .reduce((result, element) -> result = Math.min(result, element));
        min.ifPresent(age -> System.out.println(age));

3.5 注意事项

  • 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
  • 流是一次性的
  • 不会影响原有数据

4.Optional

4.1 概述

优雅的避免空指针

4.2 使用

4.2.1 创建对象

无论传入的参数是否为null都不会出现异常

static Author getAuthor(){
	return new Author(1L,"灰二",18,"HuiEr",null);
//        return null;
}

Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);
authorOptional.ifPresent(data -> System.out.println(data.getName()));
4.2.2 完全消费值
Optional<Author> authorOptional = Optional.ofNullable(author);
authorOptional.ifPresent(data -> System.out.println(data.getName()));
4.2.3 获取值

如果我们想获取Optaional中封装到的值可以使用get方法,但是不推荐,因为当封装的值为null时会有异常

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

            Optional<Author> authorOptional = Optional.ofNullable(null);
            Author author = authorOptional.orElseGet(() -> new Author());
            System.out.println(author);
    
  • orElseThrow : 获取数据时,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出

            Optional<Author> authorOptional = Optional.ofNullable(null);
            try {
                Author author = authorOptional.orElseThrow(() -> new RuntimeException("空指针"));
                System.out.println(author);
            }catch (Exception e){
                e.printStackTrace();
            }
    
4.2.5 过滤
        Author a1 = new Author();
        a1.setAge(18);
        Optional<Author> a11 = Optional.ofNullable(a1);
        a11.filter(author -> author.getAge() > 17)
                .ifPresent(author -> System.out.println(author.getAge()));
4.2.6 判断

isPresent : 判断内部封装的数据是否为空,不为空则返回true,为空则返回false

更加推荐使用ifPresent

4.2.7 数据转换

map 和 flatMap

4.3 总结

一般使用Optional的创建、获取操作

5.函数式接口

5.1 概述

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

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

5.2 常用函数式接口

  • Consumer消费接口

    根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费

    void accept(T t);
    
  • Function计算转换接口

    根据其中抽象方法的参数列表和返回值类型知道,我们可以再方法中对传入的参数计算或转移,把结果返回

    R apply(T t);
    
  • Predicate判断接口

    根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回 结果

    boolean test(T t);
    
  • Supplier生产型接口

    根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回

    T get();
    

5.3 常用默认方法

Predicate

  • and &&
  • or ||
  • negate 取反

6.方法引用

我们在使用lambda时,只有一行代码,且只调用一个方法,则可以更进一步简化代码

6.1 推荐用法

6.2 基本格式

类名或者对象名::方法名

6.3 语法详解

6.3.1 引用静态方法

格式

类名::方法名

前提

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

例如:

​ 如下代码就可以简化

class StaticMethod {
    static void out() {
        System.out.println("静态方法的引用");
    }
}

new Thread(()->StaticMethod.out()).start();

new Thread(StaticMethod::out).start();

6.3.2 引用对象的实例方法

格式

对象名::方法名

前提

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

例如:

​ 如下代码就可以简化

class MemberMethod{
    void out(){
        System.out.println("成员方法的引用");
    }
}

MemberMethod memberMethod = new MemberMethod();
new Thread(memberMethod::out).start();

6.3.3 引用类的实例方法

6.3.4 构造器引用

如果方法体中的一行代码是构造器的话就可以使用构造器引用

格式

类名::new

7.高级用法

基本数据类型优化

  • mapToInt
  • mapToLong
  • mapToDouble
  • flatMapToInt
  • flatMapToDouble

并行流

stream.parallel()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值