1 函数式接口
函数式接口在Java中是指:有且仅有一个抽象方法的接口
函数式接口, 即适用于函数式编程场景的接口; 而Java中函数式编程体现就是Lambda, 所以函数式接口就是可以适用于Lambda使用的接口; 只有确保接口中有且仅有一个抽象方法, Java中的Lambda才能顺利地进行推导
备注 : “语法糖"是指使用更加方便, 但是远离不变的代码语法; 例如在遍历集合时使用的for-earch语法, 其实底层实现的仍然是迭代器, 这便是"语法糖”, 从应用层面来讲, Java中的Lambda可以被当做匿名内部类的"语法糖", 但是二者在原理上是不同的
格式 : 只要确保接口中有且仅有一个抽象方法即可
修饰符 interface 接口名称{public abstract 返回值类型 方法名称(可选参数信息);// 其他非抽象方法内容}
由于接口中抽象发的public abstract
是可以省略的, 所以定义一个函数式接口很简单
public interface MyFunctionInterface{void myMethod();}
@FunctionalInterface注解
与@Override
注解的作用类似, Java8中专门为函数式接口引入了一个新的注解 :@FunctionalInterface
, 该注解可用于一个接口的定义上
函数式接口的使用
/* 函数式接口的使用 : 一般作为方法的参数与返回值类型 */public class Demo{// 定义一个方法, 参数使用函数式接口MyFunctionalInterfacepublic static void show(MyFunctionalInterface myInter){myInter.method()}public static void main(String[] args){// 调用show方法, 方法的参数是一个接口,所以可以传递接口的实现类对象show(new MyFunctionInterfaceImpl);// 调用show方法, 方法的参数是一个接口, 所以我们可以传递接口的匿名内部类show(new MyFunctionInterface{@Overridepublic void method(){System.out.println("使用匿名内部类重写接口中的抽象方法")}});}// 调用方法, 方法的参数是一个函数式接口, 所以可以使用Lambda表达式show(() -> {System.out.println("使用Lambda表达式重写接口中的抽象方法")});// Lambda简化show( -> System.out.println("使用Lambda表达式重写接口中的抽象方法"));}
2 函数式编程
Lambda的延迟执行
有些场景的代码执行后, 结果不一定会被使用, 从而造成性能浪费, 而Lambda表达式是延迟执行的, 这正好可以作为解决方案, 提升性能
性能浪费的日志案例
日志可以帮助我们快速的定位问题, 记录程序运行过程中的情况, 以便项目的监控和优化
一种典型的场景就是对参数进行有条件使用, 例如对日志消息进行拼接后 ,在满足条件的情况下进行打印输出
public class Demo01Logger{private static void log(int level,String msg){// 根据日志级别,显示日志信息的方法if(level == 1){System.out.println(msg);}}public static void main(String[] args){String msgA = "Hello";String msgB = "World";String msgC = "Java";log(1,msgA+msgB+msgC);}}
以上的代码问题在于, 当传入的level 不为1时, 还是海鲜拼接字符,造成性能上的浪费
我们要做的是 当第一个参数不满足的shih,不执行后面的操作; 避免性能的浪费
// 接口类@FunctionalInterfacepublic interface MessageBuilder{// 定义一个拼接消息的抽象方法, 返回被拼接的信息public abstract String builderMessaeg();}public class Demo02Lambda{// 定义一个现实日志的方法, 方法的参数传递日志的等级和MessageBuilder接口public static vid showLog(int level,MessageBuilder mb){// 对日志的等级记性判断,如果是1级,则调用接口中的方法if(level==1){System.out.println(mb.builderMessage());}}public static void main(String[] args){String msgA = "Hello";String msgB = "World";String msgC = "Java";// 调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以使用Lambda表达式shoLog(1, () ->{return msgA+msgB+msgC;});}// lambda表达式作为参数传递, 仅仅是把参数传递到showLog方法中, 当level=1,才会调用接口中的方法如果条件不满足,则接口中的方法不会执行, 不存在性能浪费}
使用Lambda作为参数和返回值
在使用比较器接口的时候, 可以使用Lambda表达式, 避免重写compare
方法
3 常用函数式接口
Supplier接口
java.util.function.Supplier<T>
接口仅包含一个无参的方法 : T get(); 用来获取一个泛型指定类型的对象数据; 由于这是一个函数式接口, 这也意味着对应的Lambda需要对外提供一个符合泛型类型的对象数据
Supplier<T> 接口被称之为生产型接口, 指定接口的泛型是什么类型, 那么接口中的get方法就会返回什么类型
import java.function.Supplierpublic class Demo01Supplier{private Static getString(Supplier<String> sup){return sup.get();}public static void main(String[] args){String msgA = "Hello";String msgB = "World";System.out.println(getString(() -> msgA+msgB)));}}
练习 : 求数组中元素最大值
使用supplier
接口作为方法参数类型, 通过Lambda表达式求出int数组中的最大值, 提示 : 接口的泛型请使用java.lang.Integer
类
public class Demo02Test{public static int getMax(Supplier<Integer> sup){return sup.get();}public static vid main(String[] args){int arr[] = {2,56,48,3,9,64};// 调用getMax方法,参数传递Lambdaint manNum = getMax(() -> {// 计算数组中的最大值int max = arr[0];// 遍历数组for(int i:arr){if(i>max){max = i;}}return max;});System.out.println("数组中最大的元素"+max);}}
Consumer 接口
java.util.function.Consumer<T>
接口则正好与Supplier相反, 它不是生产一个数据, 而是消费一个数据, 其数据类型由类型决定
抽象方法 :accept
Consumer
接口中包含抽象方法void accept(T t)
, 意为消费一个指定泛型的数据,基本使用
import javautil.function.Consumerpublic class Demo01Consumer {private static void consumeString(String name,Consumer<String> con){con.accept(name);}public static int method(Supplier<Integer> sup){consumeString(s -> System.out.println(s));}}
更好地方法是使用 方法引用
默认方法 : andThen()
如果一个方法的参数和返回值都是Consumer
类型., 那么就可以实现效果 : 消费数据的时候, 实现先做一个操作, 然后在做一个操作, 实现组合; 而这个方法就是接口中的默认方法 andThen, 源码是
default Consumer<T> andThen(Consumer<? super T> after){Objects.requireNonNull(after);return (T t) -> {accept(t);after.accept(t);};}
备注 :
java.util.Objects
的requireNonNull
静态方法将会在参数为null时 主动抛出NullPointerException
异常, 这省去了重复编写if语句和抛出空指针异常的麻烦
要想实现组合, 需要两个或多个Lambda表达式即可, 而andThen
的语义正式"一步接一步"操作, 例如两个步骤组合的情况:
public class Demo02AndThen{public static void method(String s,Consumer<String> con1,Consumer<String> con2){// 连接两个consumer,再消费con1.andThen(con2).accept(s);}public static void main(String[] args){meth("hello",(t) ->{System.out.println(t.toUpperCase());}, (t) ->{System.out.println(t.toLowerCase());})}}
格式化打印信息
public class Demo03Test{public static void printInfo(String[] arr,Consumer<String> con1,Consumer<String> con2){// 遍历字符串数组for(String message: arr){con1.andThen(con2).accept(messge);}}public static void main(String[] args){// 定义一个字符串类型的数组String[] arr = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男"};printInfo(arr,(message)->{String name = meaasge.split(",")[0];System.out.print("姓名: "+name);},(meaasge)->{String age = messaeg.aplit(",")[1];System.out.println("年龄 :"+age);})}}
Predicate接口
有时我们需要对某种类型数据进行判断, 从而得到一个boolean值结果, 这时就可以使用jav.util.function.Predicate<T>
接口
抽象方法 : test
Predicate
接口中包含一个抽象方法 :boolean test\<T t>
, 用于条件判断场景
public class DemoPredicateTest{public static void method(Predicate<String> preficate){boolean veryLong = predicate.test("HelloWorld");System.out.println("字符串很长吗 :"+veryLong);}public static void main(String[] args){method(s -> s.length() >5);}}
条件判断的标准是传入的Lambda表达式逻辑, 只要字符串长度大于5 则认为很长
默认方法 : and
既然是条件判断, 就会存在与, 或, 非三种常见的逻辑关系; 其中将两个predicate
条件使用"与"逻辑连接起来实现"并且"的效果时, 就可以使用default方法and
,JDK源码为
default Predicate<T> and(Predicate<? super T> other){Objects.requireNonNull(other);return (t) ->test(t) && other.test(t);}
如果要判断一个字符既要包含大写"H", 又要包含大写"W", 那么:
public class DemoPredicateAnd{public static void method(Predicate<String> one,Predicate<String> two){boolean isValid = one.and(two).test("HelloWorld");System.out.println("字符串很长吗 :"+isVaild);public static void main(String[] args){method(s -> s.contains("H"),s -> s.contains("W"));}}}
默认方法 : or
与 and 的与类似 , 默认方法 or 实现逻辑关系中的 或 , JDK源码为
default Predicate<T> or(Predicate<? super T> other){Objects.requireNonNull(other);return (t) ->test(t) || other.test(t);}
如果实现逻辑字符串包含大写 H 或者大写 W , 那么大代码中西药将 and 修改为 or即可, 其他不变
public class DemoPredicateOr{public static void method(Predicate<String> one,Predicate<String> two){boolean isValid = one.or(two).test("HelloWorld");System.out.println("字符串很长吗 :"+isVaild);public static void main(String[] args){method(s -> s.contains("H"),s -> s.contains("W"));}}
默认方法 : negate
与 或已经了解了, 剩下的 非(取反)也非常简单, 默认发 negate的 JDK源码为
default Predicate<T> negate(){return (t) -> !test(t);}
从实现中很容易看出, 它执行了test方法之后, 对结果boolean值进行 ! 取反而已; 一定要在 test 方法调用之前调用 negate 方法, 正如and 和 or方法一样
public class DemoPredicateNegate{public static boolean checkString(String s,Predicate<String> pre){return !pre.test(s);}public static void main(String[] args){String = "abc"boolean b = checkString(s,(String str)-> {return str.length > 5});}}
练习 : 集合信息筛选
数组当中有多条 “姓名+性别” 的信息如下, 请通过Pridicate 接口的拼接将符合要求的字符串筛选到集合ArrayList 中, 需要同时满足下面两个条件:
必须为女生姓名为4个字
public class DemoPredicate{public static void main(String[] args){String[] arra = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"}} }
public class DemoTest{public class ArrayList<String> filter(String[] arr,Predicate<String> pre1,Predicate<String> pre1){ArrayList<String> list = new ArrayList<>();for(String a:arr){// 使用接口之间中的test方法对获取的字符进行判断boolean b = pre1.and(pre2).test(s);if(b){list.add(s)}}return list}public static void main(String[] args){String[] array = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"}// 调用filterffafilter(array,(String s) ->{s.split(",")[1].equals("女");},(String s) ->{s.split(",")[0].length == 4;});// 遍历集合for(String s:list){System.out.println(s);}} }
Function接口
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个数据的数据, 前者称为前置条件, 后者称为后置条件
抽象方法 : apply
Function
接口中最主要的抽象方法为 :R apply(T t)
,根据类型的T的参数获取类型R的结果
使用的场景 : 例如将 String 类型转换为 Integer 类型
public class DemoFunctionApply{private static void change(Function<String,Integer> function){int num = function.apply(s);System.out.println(num);}public static void main(String[] args){String s = "1234";change(s,(String str) -> return Integer.parseInt(s));}}
默认方法 : andThen
andThen
方法用来进行组合操作, JDK源码如下
default <V> Function<T,V> andThen(Function<? super R,? extends V>after){Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}
该方法 同样用于"先做什么. 再做什么"的场景, 和 Consumer中的andThen差不多:
public class DemoFunctionAndThen{public static void change(String s,Function<String,Integer> fun1,Function<String,Integer> fun2){String ss = fun1.andThen(fun2).apply(s);System.out.println(ss);}public static void main(String[] args){String = "123";change(s,(String str) ->{return Integer.parseInt(str)+10},(Integer i) ->{return i+"";});}}
练习: 自定义函数类型拼接
请使用Function
进行函数模型的拼接, 按照顺序需要执行的多个函数操作为
String str = “赵丽颖,20”;
将字符串截取数字年龄部分, 得到字符串;将上一步的字符串转换为int类型的数字;将上一步的int数字累加100, 得到结果 int 数字;
public class Demo03Test{public static int change(String s,Function<String,String> fun1,Function<String,String> fun2,Function<String,String> fun3){// 使用andThen方法将三个组合到一起return fun1.andThen(fun2).andThen(fun3).apply(s);}public static void main(String[] args){String str = "赵丽颖,20";int num =change(str,(String s)->{return s.split(",")[1];},(String s)->{return Integer.parseInt(s);},(Integer i)->{return i+100});System.out.println(num);}}