函数式接口简介
注: 本篇博客主要对Consumer、Supplier、Function和Predicate四个函数式接口的使用进行说明, 阅读本文需要lambda表达式相关知识, 如不熟悉, 可参考笔者其他博客: java8特性: lambda表达式语法入门-笔记狂魔
函数式接口: 如果一个接口中有且只有一个抽象方法(可以有其他方法比如default方法等, 但是抽象方法只能有一个),则该接口就可以称之为函数式接口
@FunctionalInterface注解: 该注解可标识当前类是一个函数式接口, 如果不满足函数式接口的条件但是却使用了此注解, 则会报错
@FunctionalInterface // 该注解可标识当前类是一个函数式接口, 如果不是, 则会报错
public interface MyFunctionalInterface {
// 函数式接口中的唯一一个抽象方法
void play();
default void eat(){
System.out.println("接口中的default方法");
}
}
jdk1.8内置的4个函数式接口
jdk1.8内置了许多函数式接口供以使用, 都放在java.util.function包下, 常用的基本上是Consumer、Supplier、Function和Predicate四个函数式接口, 其余也并无太大区别
接口名 | 抽象方法 | 功能描述 |
---|---|---|
Consumer< T > | void accept(T t); | 消费型接口: 将传递进来的对象 t 作为消费对象, 对其进行逻辑处理 |
Supplier< T > | T get(); | 生产型接口: 通过一个无参方法返回一个实例对象 |
Function< T, R > | R apply(T t); | 函数型接口: 对T类型的对象进行逻辑处理, 转换成R类型(其他类型)返回 |
Predicate< T > | boolean test(T t); | 断言型接口: 对参数传的对象进行逻辑判断, 返回一个boolean类型的判断结果 |
1. Consumer: 消费型接口
将传递进来的参数对象作为消费对象, 对其进行逻辑处理
(1) 基本用法
public class ConsumerTest {
public static void main(String[] args) {
String food = "肉";
// 在调用eat()方法时用lambda表达式写出Consumer的消费逻辑
eat(food, s -> {
if (s.equals("肉")) {
System.out.println("今天开荤了!");
}
if (s.equals("菜")) {
System.out.println("今天又是斋戒的一天!");
}
});
}
// 提供一个参数中有Consumer类型的方法
public static <T> void eat(T t, Consumer<T> consumer) {
consumer.accept(t);
}
}
(2) 实际运用案例: 遍历集合
集合Collection接口继承了一个迭代器接口Iterable, 而Iterable接口中提供了一个用于遍历集合的forEach方法, 方法参数就是Consumer接口, 截图如下:
因此, 我们在遍历List或者Set集合时, 可以直接调用此方法进行遍历, 使得代码大大简化:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("孙七");
// 遍历List集合
list.forEach(name -> System.out.println(name));
}
2. Supplier: 生产型接口
通过一个无参方法返回一个实例对象
(1) 基本用法
public class SupplierTest {
public static void main(String[] args) {
// 在调用getString()方法时写出Supplier的生产逻辑
String string = getString(() -> "我是生产出来的String类型的对象");
System.out.println(string);
}
// 提供一个参数中有Supplier类型的方法
public static String getString(Supplier<String> supplier) {
String s = supplier.get();
return s;
}
}
(2) 实际运用案例: Objects.requireNonNull()对象为空时提供空指针异常的错误信息
jdk提供的对象操作工具类Objects中有一个requireNonNull()方法, 该方法的作用是用于判断对象是否为空, 如果为空, 则抛出一个空指针异常, 并指定异常信息, 截图如下:
因此, 假设我们在代码中需要设定自定义的异常信息, 就可以调用此方法:
public static void main(String[] args) {
// 根据id查询一个Student对象
Student stu = findStudentById("123456");
// 假如对象为空, 则抛出空指针异常, 并指定错误信息
Objects.requireNonNull(stu,()->"自定义异常信息: 传入的Student对象为空!");
}
3. Function: 函数型接口
对T类型的对象进行逻辑处理, 转换成R类型(其他类型)的对象
(1) 基本用法
public class FunctionTest {
public static void main(String[] args) {
// 执行过程: 给getString方法中传入一个99, 99通过Function的apply转换方法变成了String类型
String string = getString(99, i -> i.toString());
// 输出结果: 9911
System.out.println(string + 11);
}
// 传入一个Integer, 获得一个String
public static String getString(Integer integer, Function<Integer, String> function) {
String apply = function.apply(integer);
return apply;
}
}
(2) 实际运用案例: Optional中通过map()方法转换元素类型
jdk1.8提供的专为空指针判断而诞生的一个工具类Optional中, 存在一个map()方法, 用于转换Optional中的元素类型, map方法中的其中一个参数就是Function, 截图如下:
关于Optional工具类的使用, 可参考其他博客, 此处不进行说明, 使用案例:
public static void main(String[] args) {
// 根据id查询学生对象
Student student = getStuById(123456);
// map方法将元素换为另一种类型的元素(此处将Student类型转换为Boolean类型)
Optional<Boolean> optional = Optional.ofNullable(student).map(stu -> {
if (stu.getName().equals("张三")) {
return true;
} else {
return false;
}
});
}
4. Predicate: 断言型接口
对参数传的对象进行逻辑判断, 返回一个boolean类型的判断结果
(1) 基本用法
public class PredicateTest {
public static void main(String[] args) {
// 如果传入的字符串是西瓜, 就返回true, 不是西瓜就false
boolean pre = predicateTest("苹果", s -> "西瓜".equals(s));
System.out.println(pre);
}
// 提供一个参数中有Predicate类型的方法
public static boolean predicateTest(String s, Predicate<String> predicate) {
return predicate.test(s);
}
}
(2) 实际运用案例: 过滤Collection集合中的元素
集合Collection接口中存在一个removeIf()方法, 方法作用就是将集合中的元素按照指定的判断规则移除(也就是过滤), 参数就是Predicate接口, 截图如下:
假设在某种场景下, 我们要去除关于"张三"或者以"张三"开头的所有人的信息, 代码如下:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("张三2");
list.add("张三3");
list.add("张三4");
// 如果存在以"张三"开头的元素, 则从元素中移除
list.removeIf(name -> name.startsWith("张三"));
// 遍历List集合
list.forEach(name -> System.out.println(name));
}
总结
函数式接口在代码中的运用十分广泛, 特别是在Stream或者Optional这样的类中有大量的运用, 配合lambda表达式, 可以让我们在实际开发中的代码更加简洁优雅.
而常见的函数式接口几乎就是本文中提到的四种, 尽管java.util.function包下提供了许许多多的函数式接口, 但是万变不离其宗, 熟练掌握了以上四种, 其余的也是信手拈来.
其他
原创作者: 笔记狂魔, 如有错误, 请多指正