SpringCloudGateWay学习 之 从函数式编程到lambda

简介: SpringCloudGateWay学习 之 从函数式编程到lambda

前言:


这一系列的文章主要是为了学习SpringCloudGateWay,如官网所说,SpringCloudGateWay是基于 Spring Boot 2.x, Spring WebFlux, and Project Reactor的。并且Spring WebFlux中也用到了很多Project Reactor的知识,Reactor与Spring是兄弟项目,侧重于Server端的响应式编程,主要 artifact 是 reactor-core,这是一个基于 Java 8 的实现了响应式流规范 (Reactive Streams specification)的响应式库。所以在学习之前,我们首先需要对java8的lambda表达式以及流式编程有一定了解,这篇文章我们主要对lambda做一个总结学习


在学习lambda之前,我们先了解一个概念---------函数式编程


函数式编程:


参考链接:https://blog.csdn.net/u012611878/article/details/78495165


什么是函数式编程:


函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程典范,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。


它属于”结构化编程”的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。举例来说,现在有这样一个数学表达式:

image.png


传统的过程式编程,可能这样写:

var a = 1 + 2;
var b = a * 3;
var c = b - 4;

函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:

var result = subtract(multiply(add(1,2), 3), 4);

这就是函数式编程。


函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。


比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。


函数式编程的特点


函数式编程近些年异军突起,又重新回到了人们的视线,并得到蓬勃发展。总结起来,无外乎如下好处:


1.减少了可变量(Immutable Variable)的声明,程序更为安全。

2.相比命令式编程,少了非常多的状态变量的声明与维护,天然适合高并发多现成并行计算等任务,这也是函数是编程近年又大热的重要原因。

3.代码更为简洁,可读性更强,对强迫症的同学来说是个重大福音。


lambda表达式:


核心:


对于lambda表达式我们首先明确一个概念,lambda其实就一个特定接口的实现实例。


我们通过一段代码来理解下这句话:

public class Demo01 {
    public static void main(String[] args) {
        // 可以看到在这一行,我们直接将一个lambda表达式赋值给了一个变量,这个lambda表达式其实就是一个实      // 现了Runnable接口的对象实例
        Runnable lambdaRunnable = () -> System.out.println("lambda表达式执行了");
        Runnable interfaceRunnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("接口实现方法执行了");
            }
        };
        lambdaRunnable.run();
        interfaceRunnable.run();
    }
}

现在我们再来看看jdk8中的一些跟lambda相关的内容


函数接口:


1.为什么需要使用函数接口?

我们看如下代码:

// 定义一个用于格式化字符串的接口
interface MyFormat {
    String format(String str);
}
public class Demo02 {
    public static void main(String[] args) {
        MyFormat format = new MyFormat() {
            @Override
            public String format(String str) {
                System.out.println("对字符串进行格式化,统一加后缀 ',hello'");
                return str + ",hello";
            }
        };
        MyFormat lambdaFormat = str -> {
            System.out.println("采用lambda对字符串进行格式化,统一加后缀 ',hello,my name is lambda'");
            return str + ",hello,my name is lambda";
        };
        String zhangsan = format.format("zhangsan");
        System.out.println(zhangsan);
    }
}

我们再采用jdk8中提供的函数接口来改造上面的代码:

public static void main(String[] args) {
    Function<String, String> function = str -> {
        System.out.println("采用lambda对字符串进行格式化,统一加后缀 ',hello,my name is function inteface'");
        return str + ",hello,my name is function inteface";
    };
    function.apply("zhangsan");
}

对比以上几种写法,我们发现函数接口简化了的接口定义,原本我们需要专门定义一个format的顶层接口,而现在函数接口直接能实现这种简单接口的定义,我们在使用时只需要关注入参是什么,返回值是什么。只要明确这两点后,往往我们就能找到合适的函数接口


2.jdk8中提供了哪些函数接口?

消费型接口:

Conusmer<T>  void accept(T t);
BiConusmer<T,U> void accept(T t,U u); //增加一种入参类型

供给型接口:

Supplier<T>  void get();

函数型接口:

Function<T ,R> R apply(T t);
UnaryOperator<T> T apply(T t);      //入参与返回值类型一致
BiFunction <T ,U,R> R apply(T t,U u); //增加一个参数类型
BinaryOperator<T> T apply(T t1,T t2); //两个相同类型入参与同类型返回值
ToIntFunction<T>              //限定返回int
ToLongFunction<T>           //限定返回long
ToDoubleFunction<T>           //限定返回double
IntFunction<R>              //限定入参int,返回泛型R
LongFunction<R>             //限定入参long,返回泛型R
DoubleFunction<R>           //限定入参double,返回泛型R

断言型接口:

Predicate<T> boolean test(T t);

3.使用上面的这些函数式接口?

  • 明确返回值,如果没有返回值,一定是消费型的函数接口。再确认入参个数,单个的话,为Conusmer,两个为BiConusmer
  • 如果有返回值,看入参,不需要入参,为供给型接口Supplier
  • 如果既有返回值又有入参,看入参个数,如果1个的话,为Function接口,两个的话为BiFunction
  • 其余的一些接口,都是从上面几个接口衍生出来的
  • 如果需要进行判断,需要使用Predicate接口进行断言

我们可以发现,上面的函数接口,参数个数最多为两个,但是当我们需要超过两个参数的情况该怎么办呢?这个时候我们需要自定义一些函数接口,如果定义一个函数接口呢?


假设我们现在需要定义一个三个参数的函数型接口,代码如下:

// 这个例子单纯为了说明这种情况,代码本身没有任何意义,大家不要纠结这个问题
@FunctionalInterface
interface MyFunctionInterface {
    PersonAndDog apply(Dog dog, String str, Person person);
}
public class Demo03 {
    public static void main(String[] args) {
        MyFunctionInterface functionInterface = (dog, str, person) -> new PersonAndDog();
    }
}
class Person {
    String name;
}
class Dog {
    String dogName;
}
class PersonAndDog {
    String someThing;
}

我们可以看到,我们主要是添加了一个@FunctionalInterface的注解。其实不加这个注解,上面的代码也是完全可以的编译以及运行的。那么为什么我们需要添加这个注解呢?主要是为了在编译期对代码进行检查。因为申明为函数接口的接口只能有一个待实现的方法。我们想想看,当你申明了一个函数式接口,并且在项目的很多地方使用了它,结果有一天你的同事突然在这个接口中加了一个抽象方法。那么你之前所有的代码都原地爆炸了。这个注解的主要作用就是告诉别人,这是一个函数式接口,不能在其中添加其它抽象方法。当然,我们可以通过default关键字添加默认实现的方法,大家可以自行百度default关键字吗,这里不多赘述


方法引用:


主要分为以下两种情况:


  • 静态方法引用
  • 非静态方法引用


      使用对象实例进行引

     不使用对象实例进行引用

示例代码:

class Cat {  
    int total = 10;
    static void miao(int num) {
        System.out.println("让猫叫" + num + "次");
        for (int i = 0; i < num; i++) {
            System.out.println("喵");
        }
    }
    int eat(int num) {
        System.out.println("猫吃了" + num + "斤猫粮");
        total = total - num;
        System.out.println("还剩" + total + "斤猫粮");
        return total;
    }
}
public class Demo04 {
    public static void main(String[] args) {
        // 静态方法引用
        Consumer<Integer> consumer = Cat::miao;
        consumer.accept(2);
        // 非静态方法引用
        // 1.通过对象实例引用
        Cat cat = new Cat();
        Function<Integer, Integer> eat = cat::eat;
        eat.apply(2);
        // 2.通过类名的方式调用
        BiFunction<Cat, Integer, Integer> biFunction = Cat::eat;
        biFunction.apply(cat, 2);
    }
}

**可以看到,对于非静态方法引用,必须要指明这个方法要作用于哪个对象。**当我们通过类名的方式的时候要在函数调用的时候传入这个对象,而当我们通过某一个对象调用的时候,只需要执行这个动作就可以了


类型推断:


这点我就不多说了,看如下代码:

interface Imath {
    int add(int x, int y);
}
public class Demo05 {
    public static void test(Imath imath) {
        System.out.println("test");
    }
    public static void main(String[] args) {
        // 1.通过变量类型定义
        Imath imath = (x, y) -> x + y;
        // 2.通过数组,集合
        Imath[] imaths = {(x, y) -> x + y};
        List<Imath> imaths1 = Collections.singletonList((x, y) -> x + y);
        // 3.强转
        Object o = (Imath) (x, y) -> x + y;
        // 4.通过方法返回值限定
        test((x, y) -> x + y);
    }
}

总结下来,没有什么特别特俗的地方。只要lambda符合接口定义的规范,你可以把它用到任何接口能用到的地方,并且能自动推导出类型


变量引用:


我们直接通过代码说明问题

public class Demo06 {
    public static void main(String[] args) {
        String s = "haha";
        int a = 10;
        Consumer<String> consumer = s1 -> {
            System.out.println(s1);
        };
    }
}

如果我们想在上述的代码中更改a的值是不允许的:

image.png

可以看到,编译报错,我们使用在lambda中的变量必须是final的或者是等同于final。什么叫等同于final呢?

我们更改上述代码:

public class Demo06 {
    public static void main(String[] args) {
        String s = "haha";
        int a = 10;
        Consumer<String> consumer = s1 -> {
            System.out.println(s1);
        };
        // 将赋值语句放到lambad之后,可以发现编译是通过的
        a =20;
    }
}

到这里我们可以总结了:

在lambda表达式中如果我们引用了外部的变量,不能改变这个变量。必须将其当作一个final类型的变量进行处理


级联表达式跟柯里化:


  • 级联表达式
Function<Integer, Function<Integer, Integer>> function = x -> y -> x + y;

上面就是一个级联表达式。

  • 柯里化

柯里化就是把多个参数的函数转换为只有一个参数的函数,其目的就是为了将函数标准化

我们对上面的函数进行调用:

Integer apply = function.apply(2).apply(3);
System.out.println(apply);

这两个特性使用的不多,所以能看懂其含义就行,这里就不多介绍了。这篇文章就到这里,下篇文章我们开始进入流式编程的学习,希望在国庆期间能把这一系列的文章写文。

相关文章
|
25天前
|
IDE 开发工具 开发者
Kotlin语法 - 函数与Lambda表达式
本教程详细讲解了Kotlin中的函数与Lambda表达式,包括函数的基本定义、默认返回值类型、匿名函数、Lambda表达式的定义及简化、Lambda与函数引用的结合使用,以及如何在Lambda中实现循环控制。适合希望深入了解Kotlin语法的开发者。
37 1
|
4月前
|
Scala 开发者
Scala中的模式匹配与高阶函数:探索强大的编程范式
【7月更文挑战第11天】Scala中的模式匹配和高阶函数是两种极其强大的特性,它们不仅提升了代码的表达力和可读性,还使得开发者能够编写出更加灵活和可重用的解决方案。通过
|
6月前
|
存储 并行计算 算法
Lambda表达式与函数式工具
【5月更文挑战第10天】探索Python的函数式编程:Lambda表达式用于快速定义匿名函数,如求平方;函数式工具如`map()`、`filter()`、`reduce()`简化代码。通过实例展示在数据处理、并行计算中的应用,如匿名函数与`map()`结合实现列表元素运算,`filter()`筛选条件,`reduce()`做累积计算。不可变性和纯函数提升代码可靠性,结合面向对象编程实现代码复用。利用`functools`、`itertools`等模块及第三方库如`toolz`增强函数式编程能力。函数式编程适用于数据处理、并行计算,优点在于清晰、高效和易于维护。
37 0
|
6月前
|
机器学习/深度学习 开发框架 .NET
C# 中的 Lambda 表达式:一种简洁而强大的编程工具
【1月更文挑战第6天】在现代编程中,Lambda 表达式已经成为一种非常流行的编程范式。C# 作为一种功能强大的、面向对象的编程语言,自然也不例外。Lambda 表达式在 C# 中提供了一种简洁、灵活的方式来表示匿名函数。这些函数可以被用作委托或表达式树类型,从而在各种不同的上下文中使用。
|
11月前
|
算法 编译器 C++
C++11 Lambda表达式的用法与原理
C++11 Lambda表达式的用法与原理
99 0
|
Java 编译器 API
4.3 Lambda表达式的性能与限制:在某些情况下避免使用Lambda表达
4.3 Lambda表达式的性能与限制:在某些情况下避免使用Lambda表达
1312 0
|
Serverless Kotlin
Kotlin | 高阶函数reduce()、fold()详解
在 `Kotlin` 中,`reduce()` 和 `fold()` 是函数式编程中常用的高阶函数。它们都是对集合中的元素进行聚合操作的函数,将一个集合中的元素缩减成一个单独的值。它们的使用方式非常相似,但是返回值略有不同
153 0
|
Java Android开发
Rxjava和lambda语法
Rxjava和lambda语法
Rxjava和lambda语法
Lambda函数式编程
Java8所有的新特性基本基于函数式编程的思想,函数式编程给Java带来了注入了新鲜的活力。 函数式编程其实并不是很难,小编在学习函数式编程时候刚开始一头雾水,最后仔细观察就发现了其中的小窍门,读了本篇文章如果还没有掌握,就算我输了
143 0
|
JavaScript 前端开发 Java
别翻了,Lambda 表达式入门,看这篇就够了(1)
别翻了,Lambda 表达式入门,看这篇就够了
156 0
别翻了,Lambda 表达式入门,看这篇就够了(1)