一.命令式编程与函数式编程的区别
1.命令式编程
命令式编程:需要告诉程序所有的编程细节
public class demo{
public static void main(String[] args) {
int[] nums = {33,44,55,-666,90};
int min = Integer.MAX_VALUE;
for(int i: nums) {//单线程式的循环,如果想使用多线程的方式的话需要拆分数组再分配给不同的线程去执行,比较麻烦
if(i<min){
min = i;
}
}
System.out.println(min);
}
}
2.函数式编程
函数式编程:通过函数思想的方式实现
//实现过程
int min2 = IntStream.of(nums).min().getAsInt();
//如果想使用多线程的方式只需要使用parallel()实现并行的方式
int min2 = IntStream.of(nums).parallel().min().getAsInt();
3.函数式编程的优势
-
代码简洁,如上Demo中如果想实现优化循环(多线程)我们需要拆分数组并分配给不同的线程进行计算,而使用函数式编程只需要添加 调用parallel()方法即可
-
代码便于理解
//如 创建一个线程输出ok的基本写法 new Tread(new Runnable(){ @Override public void run() { System.out.println("ok"); } }).start(); //jdk8 lambda new Thread(()->System.out.println("ok")).start(); //只需要知道实现一个方法,这个方法没有输入和输出,方法内部执行输出操作即可
二.lambda表达式
1.提取lambda表达式内容
- 首先将Runnable接口实现内容提取出来,转变结果如下
Runnable target = new Runnable() {
@Override
public void run() {
System.out.println("ok");
}
}
new Thread(target).start();
Runnable target2 = ()->System.out.println("ok");
new Thread(target2).start();
- 如果将Runnable()接口实现返回内容使用Object接收,new Runnable()的方式实现的接口需要在传入到Thread中时进行转变;lambda接口实现返回需要在等号右侧强转成Runnable类型,即如下所示
Object target = new Runnable() {
@Override
public void run() {
System.out.println("ok");
}
}
new Thread((Runnable)target).start();
Object target2 = (Runnable)()->System.out.println("ok");
new Thread(target2).start();
- 得出结论:lambda表达式实际上返回的是一个实现了指定接口的对象实例
2.有输出参数和返回值的写法
interface Interface1{
int doubleNum(int i);
}
public class demo {
public static void main(String[] args) {
Interface1 i1 = (i) -> i*2;
//一个参数可以省略括号如: Interface1 i2 = i -> i*2; 默认一行是return内容
//还可以定义类型如: Interface1 i3 = (int i) -> i*2;
//如果返回内容有多行的话,需要使用括号: Interface1 i4 = (int i) -> {
// System.out.println("Ok")
// return i*2;
//}
}
}
三.JDK8的接口新特性
1.新特性1:函数式接口(lambda函数对接口的限制)
- 接口只能有一个需要被实现的方法
- 故JDK8引入的概念:函数式接口,用来函数式编程的接口。可以使用
@FunctionalInterface
注解。此注解表示此接口是函数接口,只能添加一个需要被实现的方法。从而尽量使得一个接口的作用变小,实现单一责任制原则。
2.新特性2:默认方法【最重要的特性】
-
JDK8允许接口中定义默认方法
@FunctionalInterface interface Interface1 { //需要被实现的方法 int doubleNum(int i); //默认方法:已经有默认实现的方法 default int add(int x,int y) { //可以在默认方法中使用this关键字 //int test = this.doubleNum(1) return x+y; } }
-
如1.8版本中List接口使用default默认方式设计的replaceAll()方法
3.新特性3:接口多重继承下默认方法覆盖的问题
-
如果一个接口继承两个有同名默认方法的接口,则需要指明覆盖的默认方法内容,如下
@FunctionalInterface interface Interface1 { //需要被实现的方法 int doubleNum(int i); //默认方法:已经有默认实现的方法 default int add(int x,int y) { return x+y; } } @FunctionalInterface interface Interface2 { //需要被实现的方法 int doubleNum(int i); //默认方法:已经有默认实现的方法 default int add(int x,int y) { return x+y; } } @FunctionalInterface interface Interface3 extends Interface1, Interface2 { @Override default int add(int x,int y) { return Interface1.super.add(x,y); } }
四.JDK8中重要的函数接口
1.使用函数接口Function<T,T>
- 直接定义接口并使用lambda调用
interface IMoneyFormat {
String format(int i);
}
class MyMoney {
private final int money;
public MyMoney(int money) {
this.money = money;
}
public void printMoney(IMoneyFormat moneyFormat) {
System.out.println("我的存款:"+moneyFormat.format(this.money));
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney me = new MyMoney(9999);
me.printMoney(i -> new DecimalFormat("#,###").format(i));
}
}
-
使用JDK8的函数接口
Function<输入类型,输出类型>
,使用apply()方法执行对应的函数接口【适用于只有输入和输出的内容】使用函数接口可以帮助我们省略很多接口的定义
class MyMoney {
private final int money;
public MyMoney(int money) {
this.money = money;
}
// Function函数式接口是JDK8的,定义输入是Integer类型和输出是String类型
public void printMoney(Function<Integer,String> moneyFormat) {
System.out.println("我的存款:"+moneyFormat.apply(this.money));//apply方法即通过给定的输出获得对应的输出结果
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney me = new MyMoney(9999);
me.printMoney(i -> new DecimalFormat("#,###").format(i));
}
}
- 函数接口支持链式操作
class MyMoney {
private final int money;
public MyMoney(int money) {
this.money = money;
}
// Function函数式接口是JDK8的,定义输入是Integer类型和输出是String类型
public void printMoney(Function<Integer,String> moneyFormat) {
System.out.println("我的存款:"+moneyFormat.apply(this.money));//apply方法即通过给定的输出获得对应的输出结果
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney me = new MyMoney(9999);
Function<Integer,String> moneyFormat = i -> new DecimalFormat("#,###").format(i);
//使用链式操作andThen方法输入是字符串返回也是字符串
me.printMoney(moneyFormat.andThen(s->"人民币"+s));
}
}
2.其他函数接口
-
断言函数接口(用于判断是否正确)
Predicate<Integer> predicate = i-> i>0 System.out.println(predicate.test(-9)); // 输出结果是false
-
消费函数接口(没有返回值用于对输入内容进行处理消费)
Cusumer<String> consumer = s->System.out.println(s); consumer.accept("输入的数据"); // 输出结果是 输入的数据
-
基本数据类型JDK提供了对应的函数接口,比如
Predicate<Integer>
可以使用IntPredicate
等等带了类型的函数接口,这样就可以不用写泛型了
五.方法引用
1.方法引用初试
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("接受的数据");
//可以用如下方法引用的方式替换
Consumer<String> consumer = System.out::println;
consumer.accept("接受的数据");
2.方法引用的形式
-
静态方法的方法引用
class Dog { private String name = "哮天犬"; /** * 狗叫 静态方法 */ public static void bark(Dog dog) { System.out.println(dog+"叫了"); } @Override public String toString() { return this.name; } } public class MethodReferenceDemo { public static void main(String[] args) { //静态方法的引用: 类名::静态方法名 Consumer<Dog> consumerDog = Dog::bark; Dog dog = new Dog(); consumerDog.accept(dog); } }
-
非静态方法,使用对象实例的方法引用
class Dog { private String name = "哮天犬"; private int food = 10;//默认食物10斤 /** * 狗叫 静态方法 */ public static void bark(Dog dog) { System.out.println(dog+"叫了"); } /** * 吃狗粮 * num 吃了多少 * return 剩下多少 */ public static void eat(int num) { System.out.println("吃了"+num+"斤狗粮"); this.food -= num; return this.food; } @Override public String toString() { return this.name; } } public class MethodReferenceDemo { public static void main(String[] args) { Dog dog = new Dog(); //非静态方法的引用,使用对象实例的方法引用: 实例::方法名 Function<Integer,Integer> function = dog::eat; System.out.println("还剩下"+function.apply(2)+"斤"); //输出 //吃了2斤狗粮 //还剩下8斤 // 由于输入和输出的类型一样,可以使用一元函数接口UnaryOperator<T> UnaryOperator<Integer> function = dog::eat; System.out.println("还剩下"+function.apply(2)+"斤"); // 由于类型是基本类型,可以使用IntUnaryOperator IntUnaryOperator function = dog::eat; System.out.println("还剩下"+function.applyAsInt(2)+"斤"); // 非静态方法的引用还可以使用类名的方式,因为费静态方法中的this实际上是方法默认隐含的第一个参数即Dog this BiFunction<Dog, Integer,Integer> eatFunction = Dog::eat; System.out.println("还剩下"+eatFunction.apply(dog,2)+"斤"); } }
-
构造函数的方法引用
public class MethodReferenceDemo { public static void main(String[] args) { Dog dog = new Dog(); // 不带参数的构造函数的方法引用 Supplier<Dog> supplier = Dog::new; //因为不带参数的构造函数方法没有输入只有输出,故使用Supplier System.out.println("创建了新对象: " + supplier.get());//输出: 创建了新对象:哮天犬 //带参数的构造方法引用,如public Dog(String name) {this.name = name;} Function<String,Dog> function2 = Dog::new; System.out.println("创建了新对象: "+function2.apply("旺财")); //输出: 创建了新对象:旺财 } }
六.类型推断
-
lambda表达式需要知道返回值类型
-
如果lambda表达式作为方法参数传值则会自动进行类型推断,如下所示
interface IMath { int add(int x, int y); } public class TypeDemo { public void test(IMath imath) { // 执行某些操作 } public static void main(String[] args) { // 变量类型定义 IMath lambda = (x,y) -> x+y; // 数组里 IMath[] lambdas = {(x,y) -> x+y}; // 强转 Object lambda2 = (IMath)(x,y) -> x+y; // 如果参数是指定接口类型,则不需要强转,自动识别成对应类型 TypeDemo typeDemo = new TypeDemo(); typeDemo.test((x,y) -> x+y); } }
七.级联表达式和柯里化
-
什么是级联表达式:多个箭头的lambda表达式
- 如
Function<Integer,Function<Integer,Integer>>fun = x->y->x+y;
,测试:System.out.println(fun.apply(2).apply(3))
,结果为5,实际上实现的是x+y的效果
- 如
-
什么是柯里化:把多个参数的函数转换为只有一个参数的函数
-
柯里化的目的:函数标准化
-
如有三个参数,想实现函数的柯里化,做法如下
Function<Integer,Function<Integer,Function<Integer,Integer>>> fun2 = x->y->z->x+y+z; System.out.println(fun2.apply(2).apply(3).apply(4));//输出结果:9
-
使用数组循环的方式实现柯里化
int[] nums = {2,3,4}; Function f = fun2; for(int i=0;i<nums.length;i++) { if(f instanceof Function) { Object obj = f.apply(nums[i]); if(obj instanceof Function) { f = (Function) obj; }else { System.out.println("调用结果:" + obj); //输出:调用结果:9 } } }
-
-
高阶函数:即返回函数的函数