这里不得不提的一个点,叫做编程范式,即一类典型的编程风格,那么有哪些比较通用的范式呢
这个其实很好理解,计算机硬件运行指令的方式,就是命令式的,而高级语言中,常用的循环语句,条件语句,就属于这类,即告诉计算机硬件一步一步要去怎样做。
声明式编程是以数据结构的形式来表达程序执行的逻辑,即告诉计算机应该要做什么,常见的形式:SQL(结构化查询语言)
函数式编程跟声明式有同样的指导思想,即关注要做什么,但函数式提现函数为第一公民的原则,值得注意的事这里的函数术语指的是一种映射关系,也就是数学中的函数而非计算机中的函数。这里我们可以用一种更简单的方式去对比理解,面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。
函数式编程关心数据的映射,命令式编程关心解决问题的步骤。
举例说明之: 有三个"字符串"都说自己比较长,那么到底谁最长?
Stream.of("我长","我才长","你两都没有我长").mapToInt(String::length).summaryStatistics().getMax()
我们来拆分一下(注意方法名的含义)
1. Stream.of(“我长”,“我才长”,“你两都没有我长”):构建了这三个字符组成的流。
2. mapToInt(String::length): 转化每个字符的长度(int类型)。
3. summaryStatistics(): 统计前面的长度数据。
4. getMax(): 获取最大的长度。
简单的一行代码就完成了这个需求,同时轻松易读,何乐而不为之!!!
首先我们得明白一点,函数式编程,是可以借助lambda表达式去实现的,所以理解lambda表达式就是一个重点了,而lambda表达式的规则是函数式接口了。
函数式接口:只包含了一个抽象方法的接口。
举个栗子,我们的“精神领袖”——“”窃.格瓦拉“”,就是不打工(对不起!!!)
/**
* “精神领袖”——“窃.格瓦拉”,用于展示函数式接口,不打工是我们唯一的抽象方法
* @author 妖帝艾雷诺
* @version 1.0
* @createDate 2020/04/20 16:42
*/
@FunctionalInterface
public interface CheGuevara {
/**
* 不打工
*/
void notWork();
}
@FunctionalInterface 注解仅用于标识该接口为函数接口,让编译器能够检查是否符合一个抽象方法。
定义了函数式接口后,就要使用了,语法格式如:
(parameters) -> expression 或 (parameters) -> { expression }
实现:
CheGuevara cheGuevara = ()-> System.out.println("这辈子都不可能打工的!!!");
简单理解的话,就是一种语法糖,对比平时我们用的匿名内部类:
CheGuevara cheGuevara = new CheGuevara() {
@Override
public void notWork() {
System.out.println("这辈子都不可能打工的!!!");
}
};
接口:
/**
* 不打工 (单参数)
* @param electromobile 电瓶车
*/
void notWork(String electromobile);
实现:
CheGuevara cheGuevara = (electromobile)-> System.out.println("这辈子都不可能打工的!!! 我只想拿【"+electromobile+"】");
接口:
/**
* 不打工(多参数)
* @param electromobile 电瓶车
* @param bicycle 自行车
*/
void notWork(String electromobile, String bicycle);
实现:
CheGuevara cheGuevara = (electromobile, bicycle)-> System.out.println("这辈子都不可能打工的!!! 我想拿【"+electromobile+"】和拿【"+bicycle+"】");
接口:
/**
* 不打工(多参数)
* @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+"】");
};
通过前面的例子,应该不难发现,参数是没有带有类型的,因为编译器可以通过原先定义的方法判断参数类型,也就是类型推断,有的时候,你想知道具体类型的话,也可以加上,如:
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定义一个的"窃.格瓦拉",用方法引用简化new操作
Supplier<CheGuevaraImpl> cheGuevaraSupplier = CheGuevaraImpl::new;
消费:
//定义一个消费行为,怎么消费的(惰性,还未执行)
Consumer<CheGuevaraImpl> cheGuevaraImplConsumer = cheGuevaraImpl1 -> {
System.out.println("花了一百块钱,买醉。没钱了!!!");
};
//接收Supplier提供的一个具体的"窃.格瓦拉",然后作为消费者,进行真实消费 (真正执行)
cheGuevaraImplConsumer.accept(cheGuevaraSupplier.get());
消费结果
花了一百块钱,买醉。没钱了!!!
结合上面Consumer<T>的例子,如下:
Function<CheGuevaraImpl, String> cheGuevaraStringFunction = cheGuevaraImpl2->{
//输入消费(利用上面定义的消费行为,对当前输入的"窃.格瓦拉"进行接收)
cheGuevaraImplConsumer.accept(cheGuevaraImpl2);
//输出
return "吐了";
};
//接收Supplier提供的一个具体的"窃.格瓦拉",然后作为消费者,进行真实消费,并输出
String output = cheGuevaraStringFunction.apply(cheGuevaraSupplier.get());
System.out.println(output);
结果:
花了一百块钱,买醉。没钱了!!!
吐了
Predicat常用于判断的场景,比如说,判断当前输入数字是否大于100。(对不起,"窃.格瓦拉"的例子,在这实在编不出来了)
Predicate<Integer> integerPredicate = x-> x > 100;
System.out.println("50 > 100 = "+integerPredicate.test(50));
结果:
50 > 100 = false
单独写的,请点击链接:
0202年了,还没有用上Java函数式编程!!!——Stream流
初次写博客,水平有限,有问题,还望不吝赐教!!!欢迎大家留言讨论,同时欢迎转载,但请注明出处和作者。
所有示例,已经上传至我的github https://github.com/zhiwen-li/Exemplar-learning