函数式编程的核心特点是,函数作为一段功能代码,可以像变量一样进行引用和传递,以便在有需要的时候进行调用。
大家都知道JAVA中复制一个对象的属性,必须要先new一个目标对象
new Obja new Objb BeanUtil.copy(obja,objb);
FarUser farUser = token.getFarUser(); FarUserVo farUserVo = BeanUtil.copyProperties(farUser, FarUserVo::new);
是不是感觉不一样?
package com.far.camel.infterf.func; @FunctionalInterface public interface NewCallble<T> { T newCall(); }
package com.far.camel.utils; import com.far.camel.infterf.func.NewCallble; import org.springframework.beans.BeanUtils; /* * @description: TODO * @author mike/Fang.J/方东信 * @date 2022/4/19 1:49 */ public class BeanUtil { public static <T> T copyProperties(Object src, NewCallble<T> newCallble) { T module = newCallble.newCall(); BeanUtils.copyProperties(src, module); return module; } }
@FunctionalInterface interface IntAdder { int add(int x, int y); } IntAdder adder = (x, y) -> x + y;
IntAdder 就可以看成是一个“函数类型”。
Lambda表达式和方法引用的介绍见后文。
概念有点绕,但大致意思如此。需要注意的有几点:
为什么必须是只声明一个方法的接口?
显然这个方法就是用来代表“函数类型”所能执行的功能,一个函数一旦定义好,它能执行的功能是确定的,就是调用和不调用的区别。接口中声明的方法就是和函数体定义一一对应的。事实上,@FunctionalInterface下只能声明一个方法,多一个、少一个都不能编译通过 。
覆写Object中toString/equals的方法不受此个数限制。 比如Comparator接口就声明了2个方法:
// Comparator.java @FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); //... }
严格地说,@FunctionalInterface下只能声明一个未实现的方法,default方法和static方法因为带有实现体,所有不受此限制。
@FunctionalInterface public interface IAdd<T, R> { R add(T t1, T t2); default R test1(T t1, T t2) {//可以额外定义default方法 return null; } static <T,R> R test2(T t1, T t2) {//可以额外定义static方法 return null; } }
关于interface中声明default/static方法有疑虑的话,可以查阅博主另一篇文章:java接口里面可以有成员变量么?
@FunctionalInterface注解不是必须的,不加这个注解的接口(前提是只包含一个方法)一样可以作为函数类型。不过,显而易见的是,加了这个注解表意更明确、更直观。
要定义清楚一个函数类型,必须明确规定函数的参数个数和类型、返回值类型,这些信息都是包含于接口中声明的方法。
2. JDK提供的“函数类型”
java.util.function包下预定义了常用的函数类型,包括:
@FunctionalInterface public interface Consumer<T> { void accept(T t); //接收一个类型为T(泛型)的参数,无返回值;所以叫消费者 } @FunctionalInterface public interface BiConsumer<T, U> { void accept(T t, U u);//接收2个参数,无返回值 } @FunctionalInterface public interface Supplier<T> { T get();//无参数,有返回值(所以叫提供者) } //注意没有BiSupplier,因为返回值只能有1个,不会有2个 @FunctionalInterface public interface Function<T, R> { R apply(T t);//一个输入(参数),一个输出(返回值) } @FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u);//两个输入,一个输出 } @FunctionalInterface public interface UnaryOperator<T> extends Function<T, T> { static <T> UnaryOperator<T> identity() {//一元操作,输出原样返回输入 return t -> t; } } @FunctionalInterface public interface BinaryOperator<T> extends BiFunction<T,T,T> {//二元操作,输入输出类型相同 public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;//传入比较器,返回较小者 } public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;//传入比较器,返回较大者 } }
这些个定义,都是在参数个数(0,1,2)和有无返回值上做文章。另外还有一些将泛型类型具体化的衍生接口,比如Predicate、LongSupplier等等。
@FunctionalInterface public interface Predicate<T> { boolean test(T t);//输入1个参数,返回boolean,就好像是预言家,预言你这个输入是真还是假 } @FunctionalInterface public interface LongSupplier { long getAsLong();//没有输入,输出long类型(long类型的提供者) }
Lambda表达式用来定义函数实现体。有很多种写法(都是为了简化书写),但核心是通过->连接参数和实现代码:
(入参)->{实现代码}
//无返回值的时候 (int x)->{System.out.println(x);} (x)->{System.out.println(x);}//参数类型自动推断 x->{System.out.println(x);}//只有一个参数的时候,可以省略小括号 x->System.out.println(x);//实现体只有一个表达式可以省略大括号,System.out.println本身无返回值 //有返回值的情况 (int x)->{return x*x;} (x)->{return x*x;} //x->return x*x; //错误,不能这么写!! x->x*x;
说了这么多,来实操一把:
IntConsumer ic = x->System.out.println(x); IntFunction<Integer> ifi1 = x->{return x*x;}; IntFunction<Integer> ifi2 = x->x*x; ic.accept(100);//100 System.out.println(ifi1.apply(5));//25 System.out.println(ifi2.apply(5));//25
好了,函数类型–>Lambda表达式说明白了,再来看看方法引用是怎么回事。
对类构造方法的引用,如Test::new。
对类静态方法的引用,如Test::staticMethodName
对对象实例方法的引用,如:new Test()::instanceMethod
是2和3的结合,如Test::instanceMethod2,但要求函数类型声明和函数调用的时候,其第一个参数必须是Test类(的实例)。
第4种比较难以说清楚,看看下面的例子吧:
public class Test { private String name = ""; public Test() { System.out.println("构造方法:无参数"); } public Test(String name) { this.name = name; System.out.println("构造方法:参数="+name); } public static void staticMethod(String str) { System.out.println("static method: input=" + str); } public void instanceMethod(String str) { System.out.println("instance method: input=" + str); } public static void main(String[] args) { Supplier<Test> s1 = Test::new;//对无参构造器的引用 s1.get();//构造方法:无参数 Function<String, Test> f1 = Test::new;//引用有一个String参数的构造器 f1.apply("Test");//构造方法:参数=Test Consumer<String> c1 = Test::staticMethod;//对静态方法引用 c1.accept("1");//static method: input=1 Consumer<String> c2 = new Test()::instanceMethod;//对实例方法的引用 c2.accept("2");//instance method: input=2 //第4种 BiConsumer<Test, String> bc1 = Test::instanceMethod; bc1.accept(new Test(), "3");//instance method: input=3 } }
第4中方法引用,本质上是对实例方法的引用,只不过是在调用的时候才传入那个实例对象。
//java.util.concurrent.Callable @FunctionalInterface public interface Callable<V> { V call() throws Exception; } //java.lang.Runnable @FunctionalInterface public interface Runnable { public abstract void run(); }
这两者也都是JDK预定义的函数接口,两者都不接收参数,主要用于多线程编程。
Runnable无返回值,一般用于new一个新线程的时候,在新线程中执行代码。
Callable一样一般用于在新线程中执行,只不过执行成功后有返回值,如果执行失败还会抛异常。
最后,一起分析:
Callable<Integer> c1 = ()->1; Callable<Integer> c2 = ()->c1.call();
c1引用了一个Lambda表达式;
c2引用了一个新的Lambda表达式,表示式的实现代码中调用了c1提供的call()方法,并将call()方法的返回值返回。
Callable<Integer> c1 = ()->1; Callable<Integer> c2 = ()->c1.call(); Callable<Integer> c3 = ()->{ System.out.println("c3 call c1"); return c1.call(); }; try { System.out.println(c1.call());//1 System.out.println(c2.call());//1 System.out.println(c3.call());//c3 call c1 //1 } catch (Exception e) { e.printStackTrace(); }