1. 函数式接口
1.1 函数式接口介绍
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。
1.2 格式
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 { public abstract 返回值类型 方法名称(可选参数信息); // 其他非抽象方法内容 }
1.3 @FunctionalInterface注解
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于一个接口的定义上:
@FunctionalInterface public interface MyFunctionalInterface { void myMethod(); }
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注 意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
1.4 自定义函数式接口
对于刚刚定义好的 MyFunctionalInterface 函数式接口,典型使用场景就是作为方法的参数:
public static void show(MyFunctionalInterface func){ func.mythond(); } public static void main(String[] args) { // 传入匿名内部类 show(new MyFunctionalInterface() { @Override public void mythond() { System.out.println("执行了"); } }); // 简写Lambda表达式 show(()-> System.out.println("执行了")); }
2. 函数式编程
在兼顾面向对象特性的基础上,Java语言通过Lambda表达式与方法引用等,为开发者打开了函数式编程的大门。 下面我们做一个初探。
2.1 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); } }
这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方 法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
备注:SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行 字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进 行字符串拼接。例如: LOGGER.debug("变量{}的取值为{}。", "os", "macOS") ,其中的大括号 {} 为占位 符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字 符串拼接。这也是一种可行解决方案,但Lambda可以做到更好。
体验Lambda的更优写法
使用Lambda必然需要一个函数式接口:
@FunctionalInterface public interface MessageBuilder { String message(); }
然后对log方法进行改造
public class Test01 { private static void log(int level,MessageBuilder builder){ if(level==1){ String mes = builder.message(); System.out.println(mes); } } public static void main(String[] args) { String msgA = "Hello"; String msgB = "World"; String msgC = "Java"; log(1,()-> msgA+msgB +msgC); } }
这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接
2.2 使用Lambda作为参数和返回值
如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。
如果方法的参数是一个函数 式接口类型,那么就可以使用Lambda表达式进行替代。
使用Lambda表达式作为方法参数,其实就是使用函数式 接口作为方法参数。 例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就 可以使用Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。
public static void startThead(Runnable run){ new Thread(run).start(); } public static void main(String[] args) { startThead(()-> System.out.println(Thread.currentThread().getName())); }
类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一 个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。
private static Comparator<String> newComparator() { return (o1, o2) -> o1.length()-o2.length(); } public static void main(String[] args) { // 对字符串数组排序,按照字符长度排序 String[]arr = {"ab","b","abc","abcde","abcd","aaaaaa"}; // Arrays.sort(arr); Arrays.sort(arr,newComparator()); System.out.println(Arrays.toString(arr)); // [b, ab, abc, abcd, abcde, aaaaaa] }
其中直接return一个Lambda表达式即可。
3. 常用的函数式接口
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。 下面是最简单的几个接口及使用示例。
3.1 Supplier接口
java.util.function.Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对 象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象 数据
public static String show(Supplier<String> sp){ return sp.get(); }; public static void main(String[] args) { System.out.println(show(()->"你好Java")); }
练习:求数组最大值
public static int getMax(Supplier<Integer> sp){ return sp.get(); } public static void main(String[] args) { int[]arr={11,2,3,6,4,66,22,19}; int max = getMax(()->{ int maxValue = arr[0]; for (int i = 1; i < arr.length; i++) { if(maxValue<arr[i]){ maxValue = arr[i]; } } return maxValue; }); System.out.println(max); }
3.2 Consumer接口
java.util.function.Consumer 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据, 其数据类型由泛型决定。
抽象方法 :accept,Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:
public static void printList(ArrayList<String> list, Consumer<ArrayList> con){ con.accept(list); } public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("张三"); list.add("李四"); list.add("王五"); printList(list,arrayList->{ for (int i = 0; i < arrayList.size();i++){ System.out.println("姓名:" + arrayList.get(i)); } }); }
默认方法:如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代码:
源码
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 static void printList(ArrayList<String> list, Consumer<ArrayList> one,Consumer<ArrayList> two){ one.andThen(two).accept(list); } public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("张三"); list.add("李四"); list.add("王五"); printList(list,arrayList->{ System.out.println("======顺序打印======="); for (int i = 0; i < arrayList.size();i++){ System.out.println("姓名:" + arrayList.get(i)); } },arrayList -> { System.out.println("======倒序打印======="); for (int i = arrayList.size()-1; i >=0;i--){ System.out.println("姓名:" + arrayList.get(i)); } }); }
3.3 Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用 java.util.function.Predicate 接口。
抽象方法test
Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景:
public static void main(String[] args) { // 判断一个字符串中是否包含大写的J和大写的H String str = "Hello Java"; boolean isHas = checkStr(str,data->data.contains("J")&&data.contains("H")); System.out.println(str + "是否包含大写J和H:"+isHas); } public static boolean checkStr(String str , Predicate<String> predicate){ return predicate.test(str); }
默认方法and、or、nagate
- 方法
// and 且 default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) ‐> test(t) && other.test(t); } // or 或 default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) ‐> test(t) || other.test(t); } // nagate非 default Predicate<T> negate() { return (t) ‐> !test(t); }
代码
// and public static void main(String[] args) { // 判断一个字符串中是否包含大写的J和大写的H String str = "Hello Java"; boolean isHas = checkStr(str,data->data.contains("J"),data->data.contains("H")); System.out.println(str + "包含大写J和H:"+isHas); } public static boolean checkStr(String str , Predicate<String> one,Predicate<String> two){ return one.and(two).test(str); } // or public static void main(String[] args) { // 判断一个字符串中是否包含大写的J和大写的H String str = "Hello Java"; boolean isHas = checkStr(str,data->data.contains("J"),data->data.contains("H")); System.out.println(str + "包含大写J或H:"+isHas); } public static boolean checkStr(String str , Predicate<String> one,Predicate<String> two){ return one.or(two).test(str); } } // nagate public static void main(String[] args) { // 判断一个字符串中是否包含大写的J和大写的H String str = "Hello Java"; boolean isHas = checkStr(str,data->data.contains("J")&&data.contains("H")); System.out.println(str + "没有包含大写J和H:"+isHas); } public static boolean checkStr(String str , Predicate<String> predicate){ return predicate.negate().test(str); } }
3.4 Function接口
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件, 后者称为后置条件。
抽象方法:apply
Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。 使用的场景例如:将 String 类型转换为 Integer 类型。
public static void main(String[] args) { methond(str->{ return Integer.parseInt(str); }); // 打印结果30 } public static void methond(Function<String,Integer> func){ Integer num = func.apply("20"); System.out.println(num + 10); }
默认方法:andThen
Function 接口中有一个默认的 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 static void main(String[] args) { // 将一个字符串变成数字后,再乘以10的结果 test(str->Integer.parseInt(str),num->num * 10); } public static void test(Function<String,Integer> one,Function<Integer,Integer> two){ int result = one.andThen(two).apply("20"); System.out.println("结果是:" + result); }