Java函数式编程(上)
函数
函数就是一种规则,给定输入就会有特定的输出,只要输入相同,无论多少次调用输入都是相同的。
如果函数引用了外部可变的数据那么这个函数就是不合格的函数(当外部引用数据改变时,在相同输入的情况下,输出可能会变化)。
有形的函数
将函数变为对象,函数的位置就是可变化的,哪里要用到这个函数的功能,就把它传递过去。
public static Sample{
static int add(int a, int b){
return a+b;
}
interface Lambda{
int calculate(int a,int b);
}
Lambda add = (a,b)->a+b;
public static void main(String[] args){
Sample.add(1,2);
add.calculate(1,2);
}
}
函数对象的好处
行为参数化
// 这个函数对象的写法还是相当太保守了,其实非常的简单,甚至可以没有lambda接口的显示表示
// 判断逻辑就是一个行为,可以直接作为参数传递进方法中
System.out.println(fileter(students,student -> student.getAge()>19));
System.out.println(fileter(students,student -> student.getName().startsWith("洪")));
interface Lambda{
boolean test(Student student);
}
/**
* 通用的过滤方法
* @param students
* @param lambda 传递进来的函数对象
* @return
*/
static List<Student> fileter(List<Student> students,Lambda lambda){
List<Student> res = new ArrayList<>();
for (Student student : students) {
// 传入的Lambda其实是一个已经实现了Lambda接口的类的对象,
// 因此可以直接调用其中的方法,具体里面的方法的实现还是从main方法中传递进来的对象决定的
if(lambda.test(student))
res.add(student);
}
return res;
}
延迟执行
//不管是否满足debug执行的条件,等到执行到这条语句时会首先执行expensive()方法,但如果不符合条件就是非常的浪费
logger.debug("{}",expensive());
//传入一个函数对象,会先执行debug,通过其内部的具体实现判断是否要调用expensive()方法,更节约资源
logger.debug("{}",()->expensive());
函数编程语法
函数对象表现形式
Lambda表达式
(int a,int b)-> a+b;//只有一行语句不需要大括号也不需要return语句
(a,b)->a+b;//只有当通过上下文能够推断出参数类型的时候才能省略参数
(a,b)->{int c=a+b;return c;}
interface Lambda{
int op(int a,int b);
}
Lambda lambda = (a,b)->a+b;//能够通过上下文知道参数的类型
a->a;//只有一个参数就可以省略小括号
方法引用
Math::max (int a,int b)->Math.max(a,b);
System.out::println (Object obj)->System.out.println(obj);
Student::getName (Student stu)->stu.getName();
Student::new ()->new Student();
函数对象类型
函数归类:参数个数和类型相同+返回类型相同------>同一个对象就能用一个函数式接口定义函数对象的类型
//函数对象
Type1 lambda1 = (int a)-> (a&1)==0;
Type1 lambda2 = (int a)-> BigInteger.valueOf(a).isProbablePrime(100);
//在编译时检查是否满足函数式接口的条件
@FunctionalInterface
interface Type1{
boolean test(int a);
}
使用泛型简化接口的编写:
@FunctionalInterface
interface Type3<T,I>{
T op(I input);
}
Type3<Student,String > lambda5 = str ->new Student(str);
Type3<List<Student>,String > lambda6 = str -> List.of(new Student(str));
System.out.println(lambda5.op("your name"));
System.out.println(lambda6.op("your name"));
JDK自带的函数式接口:
Runnable: ()->void;//多线程编程中的任务对象
Callable: ()->T;//同上
Comparator: (T,T)->int;
Consumer,BiConsumer,IntConsumer(参数类型是int),LongConsumer,DoubleConsumer: (T)->void;
Function,BiFunction,IntFunction(参数类型是int)...... : (T)->R;
Predicate,BiPredicate(两个参数),IntPredicate(参数类型是int)......: (T)->boolean;
Supplier,IntSupplier(返回值是Int)...:()->T;
UnaryOperator(一个参数),BinaryOperator(两个参数),IntOperator(参数类型是int),...: (T)->T
方法引用
将现有方法的调用转化为函数对象
-
静态方法:
(String s)->Integer.parseInt(s)=====Integer::parseInt
-
类名::静态方法
-
-
非静态方法:
(Student stu)-> stu.getName()===Student::getName
-
需要多传入一个参数作为对象
-
-
构造方法:
()-> new Student()===Student::new
-
对象::非静态方法
(可以在类外使用)-
this::非静态方法
(只能在一个类里面使用) -
super::非静态方法
-
-
特例:对于无需返回值的函数接口(consumer、Runnable)它们可以配合有返回值的函数对象使用
Consumer<Object> x = Sample2::print1;
Function<Object,Integer> y = Sample2::print2;
// 有返回值的函数对象能够赋值给没有返回值的函数对象,只是没有办法接受到返回值了
Consumer<Object> x1 = Sample2::print2;//会把结果忽略
static void print1(Object obj){
System.out.println(obj);
}
static int print2(Object obj){
System.out.println(obj);
return 1;
}
闭包和柯里化
闭包Closure
函数对象与外界变量(静态变量、成员变量、方法参数变量)绑定到一起形成了一个整体,即一个闭包。
变量必须是final/effective final(没有被修改过的)
但是变量的内部状态是可以改变的(一个对象的内部变量发生变化)
int x = 10;//不能被修改
highOrder((int y)->x+y);//函数对象能够访问到这个x,x就和这个函数对象绑定到一起了
x++;//上面的会报错
Student stu = new Student(19);
highOrder(y->y+stu.d);
//stu不能修改,但是其中的d属性是可以被修改的
stu.d = 20;
highOrder(y->y+stu.d);
违背了函数的不变性原则(多次使用同一个输入,输出结果必须相同)
闭包的作用:给函数执行提供数据的手段
//建立10个任务对象,并给每个任务对象一个任务编号
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 9; i++) {
int k = i+1;//k的值在每次训循环中都不会改变,可以与任务对象组成闭包
Runnable runnable = ()-> System.out.println("执行任务"+k);
list.add(runnable);
}
list.forEach(Runnable::run);
柯里化Currying
将接受多个参数的函数转换成一系列接受一个参数的函数
(a,b)->a+b;==========>(a)->返回另一个函数对象==========>(b)->a+b;
F2 f2 = (a,b)->a+b;
// 柯里化:将两个参数的函数对象变成一个参数的函数对象
F1a fa = (a) -> (b) -> a+b;//定义最外层的函数对象
F1b fb = fa.op(10);//fa返回一个函数对象,携带了a数据,则fb与a形成了闭包
int r = fb.op(20);//fb最终返回计算结果,携带了b数据
System.out.println(r);
柯里化的作用:让函数分步执行,所需要的数据不能一次性完成,需要分步收集
高阶函数
-
指的是它是其他函数对象的使用者
-
作用:
-
将通用、复杂的逻辑隐含在高阶函数内
-
将易变、未定的逻辑放在外部的函数对象中
-
-
练习:高阶函数的内部循环、遍历二叉树、简单的Stream流
自定义Stream:
public class SimpleStream<T> {
public static void main(String[] args) {
List<Integer> list = List.of(1,2,3,4,5);
SimpleStream.of(list)
.filter(x->(x&1)==1)
.map(x->x*x)
.forEach(System.out::println);
}
private final Collection<T> collection;
public SimpleStream(Collection<T> collection){
this.collection = collection;
}
// 每一次返回的都是一个新的SimpleStream对象,为了防止对原始数据的修改
public SimpleStream<T> filter(Predicate<T> predicate){
List<T> result = new ArrayList<>();
for (T t : collection) {
if(predicate.test(t)){
result.add(t);
}
}
return new SimpleStream<>(result);
}
public <U> SimpleStream<U> map(Function<T,U> function){
List<U> result = new ArrayList<>();
for (T t : collection) {
U u = function.apply(t);
result.add(u);
}
return new SimpleStream<>(result);
}
public void forEach(Consumer<T> consumer){
for (T t : collection) {
consumer.accept(t);
}
}
// 相当于是一个工厂方法
// 静态泛型的定义需要在方法名之前再次声明一下
public static <T> SimpleStream<T> of(Collection<T> collection){
return new SimpleStream<>(collection);
}
// 按照某种规则将两两元素合并为一个
public T reduce(T o, BinaryOperator<T> operator){
T p = o;
for (T t : collection) {
p = operator.apply(p,t);
}
return p;
}
}
原文地址:https://blog.csdn.net/weixin_74118846/article/details/144330969
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!