0202年了,还没有用上Java函数式编程!!!——Lambda表达式

0202年了,还没有用上Java函数式编程!!!——Lambda表达式

    • 函数式编程是什么
        • 命令式编程(Imperative)
        • 声明式编程(Declarative)
        • 函数式编程(Functional)
        • 总结
    • 函数式编程的好处
    • Lambda表达式
      • 函数式接口
      • 五种形式
        • 1.无参数
        • 2.有单个参数
        • 3.有多个参数
        • 4.实现有多行
        • 5.带类型
      • 方法引用
      • 常用内置函数式接口
        • Supplier< T >
        • Consumer< T >
        • Function< T ,R >
        • Predicate< T >
    • Stream流
    • 总结
    • 最后
    • 参考

0202年了,还没有用上Java函数式编程!!!——Lambda表达式_第1张图片

函数式编程是什么

这里不得不提的一个点,叫做编程范式,即一类典型的编程风格,那么有哪些比较通用的范式呢

命令式编程(Imperative)

这个其实很好理解,计算机硬件运行指令的方式,就是命令式的,而高级语言中,常用的循环语句,条件语句,就属于这类,即告诉计算机硬件一步一步要去怎样做

声明式编程(Declarative)

声明式编程是以数据结构的形式来表达程序执行的逻辑,即告诉计算机应该要做什么,常见的形式:SQL(结构化查询语言)

函数式编程(Functional)

函数式编程跟声明式有同样的指导思想,即关注要做什么,但函数式提现函数为第一公民的原则,值得注意的事这里的函数术语指的是一种映射关系,也就是数学中的函数而非计算机中的函数。这里我们可以用一种更简单的方式去对比理解,面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象

总结

函数式编程关心数据的映射,命令式编程关心解决问题的步骤

函数式编程的好处

  • 代码简洁,开发快速
  • 接近自然语言,易于理解

举例说明之: 有三个"字符串"都说自己比较长,那么到底谁最长?

0202年了,还没有用上Java函数式编程!!!——Lambda表达式_第2张图片

Stream.of("我长","我才长","你两都没有我长").mapToInt(String::length).summaryStatistics().getMax()

我们来拆分一下(注意方法名的含义)

1. Stream.of(“我长”,“我才长”,“你两都没有我长”):构建了这三个字符组成的流。
2. mapToInt(String::length): 转化每个字符的长度(int类型)。
3. summaryStatistics(): 统计前面的长度数据。
4. getMax(): 获取最大的长度。

简单的一行代码就完成了这个需求,同时轻松易读,何乐而不为之!!!

Lambda表达式

首先我们得明白一点,函数式编程,是可以借助lambda表达式去实现的,所以理解lambda表达式就是一个重点了,而lambda表达式的规则是函数式接口了。

函数式接口

函数式接口:只包含了一个抽象方法接口
举个栗子,我们的“精神领袖”——“”窃.格瓦拉“”,就是不打工(对不起!!!)
0202年了,还没有用上Java函数式编程!!!——Lambda表达式_第3张图片

/**
 *  “精神领袖”——“窃.格瓦拉”,用于展示函数式接口,不打工是我们唯一的抽象方法 
* @author 妖帝艾雷诺
* @version 1.0
* @createDate 2020/04/20 16:42
*/
@FunctionalInterface public interface CheGuevara { /** * 不打工 */ void notWork(); }

@FunctionalInterface 注解仅用于标识该接口为函数接口,让编译器能够检查是否符合一个抽象方法。

五种形式

定义了函数式接口后,就要使用了,语法格式如:

(parameters) -> expression 或 (parameters) -> { expression }

1.无参数

实现:

    CheGuevara cheGuevara = ()-> System.out.println("这辈子都不可能打工的!!!");

简单理解的话,就是一种语法糖,对比平时我们用的匿名内部类:

    CheGuevara cheGuevara = new CheGuevara() {
            @Override
            public void notWork() {
                System.out.println("这辈子都不可能打工的!!!");
            }
        };

2.有单个参数

接口:

    /**
     * 不打工 (单参数)
     * @param electromobile 电瓶车
     */
    void notWork(String electromobile);

实现:

    CheGuevara cheGuevara = (electromobile)-> System.out.println("这辈子都不可能打工的!!! 我只想拿【"+electromobile+"】");

3.有多个参数

接口:

    /**
     * 不打工(多参数)
     * @param electromobile 电瓶车
     * @param bicycle 自行车
     */
    void notWork(String electromobile, String bicycle);

实现:

    CheGuevara cheGuevara = (electromobile, bicycle)-> System.out.println("这辈子都不可能打工的!!! 我想拿【"+electromobile+"】和拿【"+bicycle+"】");

4.实现有多行

接口:

    /**
     * 不打工(多参数)
     * @param electromobile 电瓶车
     * @param bicycle 自行车
     */
    void notWork(String electromobile, String bicycle);

实现:

    CheGuevara cheGuevara = (electromobile, bicycle)-> {
            System.out.println("这辈子都不可能打工的!!!");
            System.out.println("我想拿【"+electromobile+"】");
            System.out.println("还要拿【"+bicycle+"】");
        };

5.带类型

通过前面的例子,应该不难发现,参数是没有带有类型的,因为编译器可以通过原先定义的方法判断参数类型,也就是类型推断,有的时候,你想知道具体类型的话,也可以加上,如:

    CheGuevara cheGuevara = (String electromobile, String bicycle)-> {
            System.out.println("这辈子都不可能打工的!!!");
            System.out.println("我想拿【"+electromobile+"】");
            System.out.println("还要拿【"+bicycle+"】");
        };

方法引用

Java8为了使lambda表达式更加精简,提供了方法引用,前提是,lambda表达式只有一行,其语法格式:

类型 示例 对应lambda表达式
引用静态方法 某类名::静态方法 (a,b,…)-> 某类名.静态方法(a,b,…)
引用某个对象的实例方法 某对象::实例方法 (a,b,…)-> 某对象.实例方法(a,b,…)
引用当前参数对象的实例方法(第一个参数) a::实例方法 (a,b,…)-> a.实例方法(b,…)
引用构造方法 某类名::new (a,b,…)-> new 某类名(a,b,…)

首先方法引用是惰性的,包括前面定义的各种形式,都是惰性的,因为它们仅仅是定义了,并没有真正开始执行,那么怎样才算调用执行,拿上个例子说明:

    cheGuevara.notWork("某某牌电动车", "某某牌自行车");

接着,让我们看看方法引用怎么用,还是用多参数的例子,
接口:

     /**
     * 不打工(多参数)
     * @param electromobile 电瓶车
     * @param bicycle 自行车
     */
    void notWork(String electromobile, String bicycle);

     /**
     * 不打工(Java8接口静态方法,用于展示“方法引用”, 不一定非要在这个接口写实现方法,也可以用其他类)
     * @param electromobile 电瓶车
     */
    static void staticNotWork(String electromobile, String bicycle){
        System.out.println("这辈子都不可能打工的!!!我想拿【"+electromobile+"】和拿【"+bicycle+"】");
    }

实现:

    CheGuevara cheGuevara = CheGuevara::staticNotWork;

调用仍然是:

    cheGuevara.notWork("某某牌电动车", "某某牌自行车");

留下个问题,大家思考下,我上述例子对应的是方法引用的那种类型呢?
当然,像伟大的IntelliJ IDEA有智能的提示,如果发现你的lambda表达式里能够转化为更精简的方法引用的话。

常用内置函数式接口

我们上述例子,都是围绕自定义的“窃.格瓦拉”函数式接口,难道我们用lambda表达式,就非要自己定义么,当然不是,Java8内置了很多函数式接口,这里举主要的几个(其他的是这几种接口的变式,大同小异,详细可参考菜鸟教程的函数式接口),进行说明。(注意:每个函数式接口仅列举了一个方法,其他的方法使用,建议看源码或者Java8 API)

内置函数式接口 描述
Supplier<T> 无参数,返回一个T类型结果
Consumer<T> 代表了接受一个输入T类型参数并且无返回的操作
Function<T,R> 接受一个输入T类型参数,返回一个R类型结果
Predicate<T> 接受一个输入T类型参数,返回一个布尔值结果

定义一个具体的“窃.格瓦拉”来说明,跟上面其他例子关联不大,仅仅为了延续上面的风格,方便理解。


/**
 *  一位看似忠诚的“窃.格瓦拉”的信仰者 
* * @author 妖帝艾雷诺
* @version 1.0
* @createDate 2020/04/20 23:12
*/
public class CheGuevaraImpl implements CheGuevara{ @Override public void notWork(String electromobile, String bicycle) { System.out.println("这辈子都不可能打工的,哪怕你连一百块都不给我!!!但我不拿【"+electromobile+"】也不拿【"+bicycle+"】"); } }

Supplier< T >

        //利用Supplier定义一个的"窃.格瓦拉",用方法引用简化new操作
        Supplier<CheGuevaraImpl> cheGuevaraSupplier = CheGuevaraImpl::new;

Consumer< T >

消费:


        //定义一个消费行为,怎么消费的(惰性,还未执行)
        Consumer<CheGuevaraImpl> cheGuevaraImplConsumer = cheGuevaraImpl1 -> {
            System.out.println("花了一百块钱,买醉。没钱了!!!");
        };

        //接收Supplier提供的一个具体的"窃.格瓦拉",然后作为消费者,进行真实消费 (真正执行)
        cheGuevaraImplConsumer.accept(cheGuevaraSupplier.get());

消费结果

花了一百块钱,买醉。没钱了!!!

Function< T ,R >

结合上面Consumer<T>的例子,如下:

        Function<CheGuevaraImpl, String> cheGuevaraStringFunction = cheGuevaraImpl2->{
            //输入消费(利用上面定义的消费行为,对当前输入的"窃.格瓦拉"进行接收)
            cheGuevaraImplConsumer.accept(cheGuevaraImpl2);
            //输出
            return "吐了";
        };

        //接收Supplier提供的一个具体的"窃.格瓦拉",然后作为消费者,进行真实消费,并输出
        String output = cheGuevaraStringFunction.apply(cheGuevaraSupplier.get());
        System.out.println(output);

结果:

花了一百块钱,买醉。没钱了!!!
吐了

Predicate< T >

Predicat常用于判断的场景,比如说,判断当前输入数字是否大于100。(对不起,"窃.格瓦拉"的例子,在这实在编不出来了)
0202年了,还没有用上Java函数式编程!!!——Lambda表达式_第4张图片

		Predicate<Integer> integerPredicate = x-> x > 100;
        System.out.println("50 > 100 = "+integerPredicate.test(50));

结果:

50 > 100 = false

Stream流

单独写的,请点击链接:
0202年了,还没有用上Java函数式编程!!!——Lambda表达式_第5张图片
0202年了,还没有用上Java函数式编程!!!——Stream流

总结

  1. 编程范式:函数式编程是一种编程范式,常见的还有命令式编程,声明式编程,常见的面向对象编程是一种命令式编程。
  2. 面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象,现实世界中,数据和行为并存,程序也是如此,因此这两种编程方式我们都得学。
  3. 函数式编程的核心思想:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值( 函数式编程的一个特点就是可以把函数作为参数传递给另一个函数,也就是所谓的高阶函数)

最后

初次写博客,水平有限,有问题,还望不吝赐教!!!欢迎大家留言讨论,同时欢迎转载,但请注明出处和作者。
所有示例,已经上传至我的github https://github.com/zhiwen-li/Exemplar-learning

参考

  • [1] [英] Richard Warburton著,王群锋译.Java8 函数式编程[M].人民邮电出版社:北京,2015.4.
  • [2] 李刚.疯狂Java讲义(第4版)[M].电子工业出版社:北京,2018.1.
  • [3] backslash112.编程范式:命令式编程(Imperative)、声明式编程(Declarative)和函数式编程(Functional)[EB/OL].https://www.cnblogs.com/sirkevin/p/8283110.html,2018-01-15.

0202年了,还没有用上Java函数式编程!!!——Lambda表达式_第6张图片

你可能感兴趣的:(基础)