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)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。
它属于”结构化编程”的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。举例来说,现在有这样一个数学表达式:
(1+2)∗3−4
(1 + 2) * 3 - 4
(1+2)∗3−4
传统的过程式编程,可能这样写:
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相关的内容
函数接口:
- 为什么需要使用函数接口?
我们看如下代码:
// 定义一个用于格式化字符串的接口
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的顶层接口,而现在函数接口直接能实现这种简单接口的定义,我们在使用时只需要关注入参是什么,返回值是什么。只要明确这两点后,往往我们就能找到合适的函数接口
- 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);
- 如何使用上面的这些函数式接口?
- 明确返回值,如果没有返回值,一定是消费型的函数接口。再确认入参个数,单个的话,为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的值是不允许的:
可以看到,编译报错,我们使用在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);
这两个特性使用的不多,所以能看懂其含义就行,这里就不多介绍了。这篇文章就到这里,下篇文章我们开始进入流式编程的学习,希望在国庆期间能把这一系列的文章写文。
希望能交到一些兴趣相同的朋友哦
码字不易,喜欢的话,点个赞吧!!!
SpringCloudGateWay学习 之 从函数式编程到lambda的更多相关文章
- Java 函数式编程(Lambda表达式)与Stream API
1 函数式编程 函数式编程(Functional Programming)是编程范式的一种.最常见的编程范式是命令式编程(Impera Programming),比如面向过程.面向对象编程都属于命令式 ...
- [学习] 从 函数式编程 到 lambda演算 到 函数的本质 到 组合子逻辑
函数式编程 阮一峰 <函数式编程初探>,阮一峰是<黑客与画家>的译者. wiki <函数编程语言> 一本好书,<计算机程序的构造与解释>有讲到schem ...
- Java函数式编程和lambda表达式
为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于 ...
- Java8函数式编程以及Lambda表达式
第一章 认识Java8以及函数式编程 尽管距离Java8发布已经过去7.8年的时间,但时至今日仍然有许多公司.项目停留在Java7甚至更早的版本.即使已经开始使用Java8的项目,大多数程序员也仍然采 ...
- Python函数式编程:Lambda表达式
首先我们要明白在编程语言中,表达式和语句的区别. 表达式是一个由变量.常量.有返回值的函数加运算符组成的一个式子,该式子是有返回值的 ,如 a + 1 就是个表达式, 单独的一个常量.变量 或函数调 ...
- C# 函数式编程 —— 使用 Lambda 表达式编写递归函数
最近看了赵姐夫的这篇博客http://blog.zhaojie.me/2009/08/recursive-lambda-expressions.html,主要讲的是如何使用 Lambda 编写递归函数 ...
- Java8函数式编程和lambda表达式
文章目录函数式编程JDK8接口新特性函数接口方法引用函数式编程函数式编程更多时候是一种编程的思维方式,是一种方法论.函数式与命令式编程区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉 ...
- Java 函数式编程和Lambda表达式
1.Java 8最重要的新特性 Lambda表达式.接口改进(默认方法)和批数据处理. 2.函数式编程 本质上来说,编程关注两个维度:数据和数据上的操作. 面向对象的编程泛型强调让操作围绕数据,这样可 ...
- 【java8新特性】01:函数式编程及Lambda入门
我们首先需要先了解什么是函数式编程.函数式编程是一种结构化编程范式.类似于数学函数.它关注的重点在于数据操作.或者说它所提倡的思想是做什么,而不是如何去做. 自Jdk8中开始.它也支持函数式编程.函数 ...
随机推荐
- push和appendChild的区别
概述:绑定事件(push和appendChild用法相似:但是一个是控制数组,一个是控制元素节点)用法:1.数组1的更改后的长度 = 数组1.push();//用来控制数组,在数组最后面插入项,返回数 ...
- Linux/UNIX 下终端复用利器 tmux
简介 tmux 是一个终端复用器类自由软件,功能类似 GNU Screen,但使用 BSD 许可发布.用户可以通过 tmux 在一个终端内管理多个分离的会话,窗口及面板,对于同时使用多个命令行,或多个 ...
- ios 中使用 animation-play-state: paused 属性失效的问题
前言 因为要做一个播放器的播放图片旋转动画,像这样子 当音乐播放就转动,停止就暂停. 开始于是很自然地想到了使用Css3的 animation 动画属性CSS3 animation(动画) 属性 an ...
- 使用pandas读取csv文件和写入文件
这是我的CSV文件 读取其中得tempo这一列 import pandas as pd #导入pandas包 data = pd.read_csv("E:\\毕设\\情感识别\\Music- ...
- 5个有趣的Python小知识,结果令人意外
1 字符串驻留 如果上面例子返回True,但是下面例子为什么是False: 这与Cpython 编译优化相关,行为称为字符串驻留,但驻留的字符串中只包含字母,数字或下划线. 2 相同值的不可变对象 这 ...
- Jmeter工具 组件简单认识
JMETER 所有的组件(元素)都是基于测试计划的,先有测试计划然后才有 JMETER 组件 JMETER 核心组件1.JMETER中的 Threads 类似与线程数,每一个线程数代表一个虚拟用户:测 ...
- [linux] 小问题:管道符,换行问题等;[nginx]启动,重启,关闭命令;以及升级nginx切换命令
Lniux换行问题 后面回车不会马上执行本条命令而是换行继续. : 是运行完前面就继续后面的, && 同样是前面正确就运行后面, || 是前面运行不正确就运行后面. | 管道符“|”将 ...
- Springboot:静态资源加载(七)
WebMvc自动配置: 搜索WebMvcAutoConfiguration自动装配类: 第一种方式通过webjars加载静态资源: https://www.webjars.org(通过maven加载依 ...
- 重装anaconda的记录,包含设置jupyter kernel
anaconda安装记录 官网下载最新版 linux:sh xx.sh 注意不要敲太多回车,容易错过配置bash的部分,还要手动添加 (vim ~/.bashrc 手动添加新bash,卸载时也要删掉此 ...
- 随笔之——伪类选择器:nth-child(n) 与 nth-of-type(n)的区别!!!
话不多说!直接正题!!! 一.E:nth-child(n)///选中父元素中第(n)个元素.若第n个元素为E则选中:若第n个不为E则不选中.n可以为2n(偶数).2n+1(奇数).等... 二.E:n ...