函数式接口
有且只有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而java中的函数式编程体现就是Lambda,所以函数式接口 就是可以适用于Lambda使用的接口。只要确保接口中有且只有一个抽象方法,而java中的Lambda才能顺利进行推导。
格式
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称{ public abstract 返回值类型 方法名称(可选参数信息); //其他非抽象方法内容 }
由于接口当中抽象方法 public abstract是可以被省略的,所以定义一个函数式接口很简单:
public interface MyFunctionInterface{ void myMethod(); }
@FunctionalInterface注解
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上:
@FunctionalInterface public interface MyFunctionInterface{ void myMethod(); }
一旦使用该注解来定义接口,编译器将会被强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用此注解,只要满足函数式接口的定义,这仍然是一个函数式 接口,使用起来都一样。
自定义函数式接口
对于刚刚定义好的 MyFunctionalInterface 函数式接口,典型使用场景就是作为方法的参数:
package com.demo.java.study7; /** * @ClassName: * @PackageName: com.demo.java.study7 * @author: youjp * @create: 2020-02-15 13:48 * @description: * @Version: 1.0 */ public class Test { // 使用自定义的函数式接口作为方法参数 public static void doSomeThing(MyFunctionInterface my){ my.myMethod();// 调用自定义的函数式接口方法 } public static void main(String[] args) { //调用函数式接口的方法 doSomeThing(()->{ System.out.println("Lambda执行了"); }); } }
执行结果
四大函数式接口
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。
下面是最简单的几个接口及使用示例。
Supplier接口
java.util.function.Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
import java.util.function.Supplier; /** * @ClassName: * @PackageName: com.demo.java.study7 * @author: youjp * @create: 2020-02-15 14:51 * @description: * @Version: 1.0 */ public class Test5 { private static String getString(Supplier<String> supplier){ return supplier.get(); } public static void main(String[] args) { String msgA="hello"; String msgB="world"; String msgC="java"; System.out.println(getString(()->msgA+msgB+msgC)); } }
练习:求数组元素最大值
使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用java.lang.Integer 类。
import java.util.function.Supplier; /** * @ClassName: * @PackageName: com.demo.java.study7 * @author: youjp * @create: 2020-02-15 14:55 * @description: * @Version: 1.0 */ public class Test6 { /** * @Param [supplier] * @return java.lang.Integer * @Author youjp * @Description //TODO 获取最大数 * @throw **/ private static Integer getMax(Supplier<Integer> supplier) { return supplier.get(); } public static void main(String[] args) { int arr[] = {2, 3, 44, 55, 8}; int maxNum = getMax(() -> { int max = 0; for (int i = 0; i < arr.length; i++) { if (arr[i] >= max) { max = arr[i]; } } return max; }); System.out.println(maxNum); } }
Consumer接口
java.util.function.Consumer 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
抽象方法:accept
Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:
import java.util.function.Consumer; /** * @ClassName: * @PackageName: com.demo.java.study7 * @author: youjp * @create: 2020-02-15 15:22 * @description: * @Version: 1.0 */ public class Test7 { private static void consumeString(Consumer<String> function){ function.accept("hello"); } public static void main(String[] args) { consumeString(s -> System.out.println(s)); } }
执行结果
默认方法:andThen
如果一个方法的参数和返回值全都是 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 的语义正是“一步接一步”操作。例如两个步骤组合的情况:
package com.demo.java.study7; import java.util.function.Consumer; /** * @ClassName: * @PackageName: com.demo.java.study7 * @author: youjp * @create: 2020-02-15 15:22 * @description: * @Version: 1.0 */ public class Test7 { private static void consumeString(Consumer<String> one,Consumer<String> two){ one.andThen(two).accept("Hello"); } public static void main(String[] args) { //先转大写字母,再转小写字母 consumeString( s -> System.out.println(s.toUpperCase()), s-> System.out.println(s.toLowerCase())); } }
运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的组合。
练习:格式化打印信息
下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。 ”的格式将信息打印出来。要求将打印姓名的动作作为第一个 Consumer 接口的Lambda实例,将打印性别的动作作为第二个 Consumer 接口的Lambda实例,将两个 Consumer 接口按照顺序“拼接”到一起。
public static void main(String[] args) { String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" }; }
解答
import java.util.function.Consumer; /** * @ClassName: * @PackageName: com.demo.java.study7 * @author: youjp * @create: 2020-02-15 15:53 * @description: * @Version: 1.0 */ public class Test8 { private static void consumeString(Consumer<String> one,Consumer<String> two,String[] array){ for(String info:array){ one.andThen(two).accept(info); } } public static void main(String[] args) { String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" }; consumeString( s-> System.out.print("姓名:"+s.split(",")[0]), s -> System.out.println(" 性别:"+s.split(",")[1]+"。"),array); } }
输出: