1. 概述
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
提到函数式接口肯定少不了 Lambda 表达式,函数式接口可以隐式的转换为 Lambda 表达式。
我们可以选择向各种各样的方法和构造函数传递 Lambda 表达式,包括在 Java 8 之前创建的一些方法和构造函数。因为 Lambda 表达式在 Java 中表示为函数接口。
2. 什么是函数式接口?
先来看看传统的创建线程是怎么写的
Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("t1"); } }); t1.start();
再来看看使用了函数式接口是怎么写的
Thread t2 = new Thread(() -> System.out.println("函数式接口")); t2.start();
Runnable 接口直接可以使用 Lambda 表达式来编写,这是因为 Runnable 接口是一个函数式接口,来看看 Runnable 的源码。
@FunctionalInterface public interface Runnable { public abstract void run(); }
发现该接口加上了函数式接口的定义注解: @FunctionalInterface ,表明该接口是一个函数式接口。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FunctionalInterface { }
函数式接口:简单地说,就是 Java 通过实现接口来间接性的将代码段传入使用。
JDK 1.8 之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
- java.util.function
我们主要了解一下 Java 8 专门新增的函数式接口:function
3. function
函数式接口规范:
1、@FunctionalInterface 标识为一个函数式接口只能用在只有一个抽象方法的接口上。
2、接口中的静态方法、默认方法、覆盖了 Object 类的方法都不算抽象方法。
3、@FunctionalInterface 注解不是必须的,如果该接口只有一个抽象方法可以不写,它默认就符合函数式接口,但建议都写上该注解,编译器会检查该接口是否符合函数式接口的规范
任何满足单一抽象方法法则的接口,都会被自动视为函数接口。这包括 Runnable 和 Callable 等传统接口,以及自己构建的自定义接口。
java.util.function 包中最常用的接口包括有四个:Consumer<T>、Supplier<T>、Function<T> 和 Predicate<T>。
4. 四大函数式接口
① Consumer:消费型接口
顾名思义:只进不出
java.util.function.Consumer<T> 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。
public class ConsumerDemo { /** * 使用Consumer接口消费字符串 * * @param str - 传递一个字符串 * @param consumer - 传递Consumer接口,泛型使用String */ public static void method1(String str, Consumer<String> consumer) { consumer.accept(str); } /** * 将两个Consumer接口组合到一起,再对数据进行消费 * * @param str - 传递一个字符串 * @param cons1 - 连接con2参数 * @param cons2 - 传递Consumer接口,泛型使用String */ public static void method2(String str, Consumer<String> cons1, Consumer<String> cons2) { cons1.andThen(cons2).accept(str); } public static void main(String[] args) { // 调用method方法,传递字符串,方法的另一个参数是Consumer接口, // Consumer是一个函数式接口,所以可以传递Lambda表达式 method1("consumer", (String str) -> { System.out.println(str.toUpperCase()); // 消费方式:把字符串转换为大写输出 } ); // 优化Lambda method1("consumer", str -> System.out.println(str.toUpperCase())); System.out.println("=============================="); // 调用method方法,传递一个字符串,两个Lambda表达式 method2("jjjjjjjjj_CCCCCCCC", (str) -> { System.out.println(str.toUpperCase()); // 消费方式:把字符串转换为大写输出 }, (str) -> { System.out.println(str.toLowerCase()); // 消费方式:把字符串转换为小写输出 }); // 优化Lambda method2("EEEEEEEEEEEEE_ccccccccccccccc", str -> System.out.println(str.toUpperCase()), str -> System.out.println(str.toLowerCase())); } }
② Supplier:供给型接口
顾名思义:只出不进
1. java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
2. Stream流中 forEach(Supplier函数型接口)
public class SupplierDemo { /** * 取一个泛型参数指定类型的对象数据 * * @param supplier - Supplier<String>接口 * @return - 泛型执行String,get方法就会返回一个String */ public static String getString(Supplier<String> supplier) { return supplier.get(); } /** * 求数组元素最大值 * * @param supplier - Supplier<Integer>接口 * @return - 获取int类型数组中元素的最大值 */ public static Integer getMax(Supplier<Integer> supplier) { return supplier.get(); } public static void main(String[] args) { // 调用getString方法,方法的参数Supplier是一个函数式接口,可以传递Lambda表达式 System.out.println( getString(() -> { return "啊哈"; }) ); // 优化Lambda表达式 System.out.println( getString(() -> "呵呵") ); System.out.println("===================================="); int[] arr = {188, 210, 52, 263, 66, 312, 75, 29, 125, 55}; // 调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式 int maxValue = getMax( () -> { int max = arr[0]; // 定义一个变量,把数组中的第一个元素赋值给该变量,记录数组中元素的最大值 for (int i : arr) { // 遍历数组,获取数组中的其他元素 if (i > max) { // 使用其他的元素和最大值比较 max = i; // 如果i大于max,则替换max作为最大值 } } return max; // 返回最大值 } ); System.out.println("数组中元素的最大值是:" + maxValue); } }
③ Function:函数式接口
顾名思义:y=fun(x),做一个类型转换
1. java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
2. Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。
3. 使用的场景例如:将 String 类型转换为 Integer 类型。Stream 中 map(Function函数型接口。
public class FunctionDemo { /** * 把字符串类型的整数,转换为Integer类型的整数返回 * * @param str - 字符串类型的整数 * @param function - Function接口,泛型使用<String,Integer> */ public static void change1(String str, Function<String, Integer> function) { //Integer in = function.apply(str); int in = function.apply(str);//自动拆箱 Integer->int System.out.println(in); } /** * 将字符串类型的整数转换为Integer类型进行运算后(x<<1),再把结果转换为字符串进行输出 * * @param str - 字符串类型的整数 * @param function1 - 将字符串整数转换为Integer类型 * @param function2 - 将Integer类型转换为字符串整数 */ public static void change2(String str, Function<String, Integer> function1, Function<Integer, String> function2) { String result = function1.andThen(function2).apply(str); System.out.println(result); } public static void main(String[] args) { // 调用change方法,传递字符串类型的整数,和Lambda表达式 change1("1234", (str) -> { return Integer.parseInt(str); }); // 优化Lambda change1("4321", str -> Integer.parseInt(str)); System.out.println("=============================="); change2("8", (str) -> { return Integer.parseInt(str) << 1; // 把字符串转换为整数后左移一位 }, (i) -> { return String.valueOf(i); // 把整数转换为字符串 }); // 优化Lambda change2("16", s -> Integer.parseInt(s) << 1, i -> String.valueOf(i)); } }
④ Predicate:断定型接口
顾名思义:断定是否
有时候我们需要对某种类型的数据进行判断,从而得到一个 boolean 值结果。这时可以使用java.util.function.Predicate<T> 接口。接口中包含一个抽象方法: boolean test(T t)
2. Stream 流中 filter(Predicate断定型接口)
public class PredicateDemo { /** * 对字符串进行校验 * * @param str - String类型的字符串 * @param predicate - Predicate接口,泛型使用String * @return - 对字符串进行判断,并把判断的结果返回 */ public static boolean checkString1(String str, Predicate<String> predicate) { return predicate.test(str); } /** * 连接多个判断的条件,模拟逻辑与 (&&) * * @param str - String类型的字符串 * @param p1 - 判断字符串的长度 * @param p2 - 判断字符串中是否包含 * @return - 两个结果为true返回true,有一个false返回false */ public static boolean checkString2(String str, Predicate<String> p1, Predicate<String> p2) { return p1.and(p2).test(str); } public static void main(String[] args) { // 调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式 System.out.println("abcde.length() > 5 = " + checkString1("abcde", (str) -> { return str.length() > 5; // 对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回 }) ); // 优化Lambda System.out.println("123456.length() > 5 = " + checkString1("123456", str -> str.length() > 5) ); System.out.println("==============================="); // 调用checkString方法,参数传递字符串和两个Lambda表达式 System.out.println( checkString2("abcde", (str) -> { return str.length() >= 5; //判断字符串的长度是否大于等于5 }, (str) -> { return str.contains("a"); // 判断字符串中是否包含a }) ); // 优化Lambda System.out.println( checkString2("qwertasd", str -> str.length() > 6, str -> str.contains("qwe")) ); } }
5. 自定义接口
如果哪一天,你觉得它的接口已经无法满足你的需求时,那么,你就能自定义接口了,hhh,Java 8 很友好,知道你想要,满足你的个性化定制。
我们只需要做两件事:
- 使用 @FunctionalInterface 注释该接口,这是 Java 8 对自定义函数接口的约定。
- 确保该接口只有一个抽象方法。
仔细观察 java.util.function 的接口,也一样,都做了两件事,自定义就是那么简单。
使用 @FunctionalInterface 注释可以确保,如果在未来更改该接口时意外违反抽象方法数量规则,您会获得错误消息。
实列就不写了,因为不是很难理解,我们参考参考 Function<T,R>
@FunctionalInterface public interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t); }