主要内容:函数式接口是什么
函数式接口玩法介绍
使用场景1
使用场景2
函数式接口是什么
定义:有且仅有一个抽象方法的接口(不包括默认方法、静态方法以及对Object方法的重写)
大家对函数式接口的认识应该都来自于Java8的Stream API,比如Predicate、Function,借助这些函数式接口,Stream才能写出一个个骚操作:
public class StreamTest {
public static void main(String[] args) {
List userList = Lists.newArrayList();
userList.add(new User(1L, "彼得", 18));
userList.add(new User(2L, "鲍勃", 19));
userList.add(new User(3L, "威廉", 20));
userList.stream()
.filter(user -> user.getAge() > 18)
.map(User::getName)
.forEach(System.out::println);
}
}
点进filter方法,你会发现它的参数就是一个函数式接口Predicate:
我们可以从中得到启发:函数式接口不同于以往的普通接口,它最大的作用其实是为了支持行为参数传递,比如传递Lambda、方法引用、函数式接口对应的实例对象等。
函数式接口的玩法介绍
public class FunctionalInterfaceTest {
// 1.写了一个方法,参数是函数式接口,你可以传递Runnable的实现,也可以使用Lambda或方法引用
public static void execute(Runnable runnable) {
try {
runnable.run();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 2.传入匿名对象
execute(new Runnable() {
@Override
public void run() {
System.out.println("匿名对象");
}
});
// 3.使用Lambda,()表示Runnable#run()的参数,println()是Runnable#run()的方法体
execute(() -> System.out.println("使用lambda"));
// 5.因为wrapPrintln和上面的println做的是同样的事,可以替换
UserService userService = new UserService();
execute(() -> userService.wrapPrintln());
// 6.IDEA提示上面的代码可以优化成 方法引用
execute(userService::wrapPrintln);
// 8.你会发现上面的写法仍是对的,因为“仅有一个抽象方法”是对Runnable的约束,不要搞混
}
// 4.我们试着把println()移到wrapPrintln中
static class UserService {
public void wrapPrintln() {
System.out.println("包装后的println");
}
// 7.给UserService新增一个方法
public void anotherMethod() {
System.out.println("另一个方法,不影响execute使用wrapPrintln");
}
}
}
使用场景1
经过上面的铺垫,你应该能理解下面的写法,这是我在某课时间看到的一段代码。
public void sendBook() {
try {
this.service.sendBook();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
public void sendChapter() {
try {
this.service.sendChapter();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
public void startTranslation() {
try {
this.service.startTranslation();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
代码本身没有问题,但可以优化。上面三个方法都有相同的异常处理方式,可以抽取成通用模板:
private void execute(final Runnable runnable) {
try {
runnable.run();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
然后把原来的方法作为参数传进去:
public void sendBook() {
execute(this.service::sendBook);
}
public void sendChapter() {
execute(this.service::sendChapter);
}
public void startTranslation() {
execute(this.service::startTranslation);
}
使用场景2
再介绍一个前几天写代码时遇到的一个场景:
我自定义了一个权限注解@RequiresPermission,如果被加在Controller上,那么内部所有接口都要权限校验,否则只校验加了@RequiresPermission的接口。但无论如何,我要先收集需要权限校验的接口。
@Component
public class PermissionMethodCollectionListener
implements ApplicationListener,
ApplicationContextAware {
// 省略其他方法
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
// ...
for (Object bean : beans) {
Class> controllerClazz = bean.getClass();
Method[] methods = controllerClazz.getMethods();
/*
* 1.判断并收集接口:
* 1.1 如果Controller上有@RequiresPermission,收集所有的接口
* 1.2 如果Controller上没有@RequiresPermission,那么只收集有@RequiresPermission的接口
* */
// 2.存储需要权限认证的接口
}
// ...
}
}
最直观的代码是:
@Component
public class PermissionMethodCollectionListener
implements ApplicationListener,
ApplicationContextAware {
// ...
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
// ...
for (Object bean : beans) {
Class> controllerClazz = bean.getClass();
Method[] methods = controllerClazz.getMethods();
Annotation annotationOnController = AnnotationUtils.findAnnotation(controllerClazz, RequiresPermission.class);
if (annotationOnController == null) {
// Controller上没注解,那么收集 @RequiresPermission + @RequestMapping 的方法
Arrays.stream(methods)
.filter(this::hasPermissionAnnotation)
.map(method -> {
StringBuilder sb = new StringBuilder();
String methodName = method.getName();
return sb.append(controllerClazz.getSimpleName()).append("#").append(methodName).toString();
})
.collect(Collectors.toSet());
} else {
// Controller有注解,那么收集 @RequestMapping 的方法
Arrays.stream(methods)
.filter(this::isApiMethod)
.map(method -> {
StringBuilder sb = new StringBuilder();
String methodName = method.getName();
return sb.append(controllerClazz.getSimpleName()).append("#").append(methodName).toString();
})
.collect(Collectors.toSet());
}
// ...
}
}
private boolean hasPermissionAnnotation(Method method) {
// 打了@RequiresPermission + @RequestMapping
return AnnotationUtils.findAnnotation(method, RequestMapping.class) != null
&& AnnotationUtils.findAnnotation(method, RequiresPermission.class) != null;
}
private boolean isApiMethod(Method method) {
// 打了@RequestMapping注解
return AnnotationUtils.findAnnotation(method, RequestMapping.class) != null;
}
}
你会发现,有两坨Stream代码,他们的差别就是filter,所以可以把Stream的代码抽出来,把filter作为参数传递:
@Component
public class PermissionMethodCollectionListener
implements ApplicationListener,
ApplicationContextAware {
// ...
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
// ...
for (Object bean : beans) {
Class> controllerClazz = bean.getClass();
// 如果Controller上有@RequiresPermission,那么所有接口都要收集(isApiMethod),否则只收集打了@Permission的接口(hasPermissionAnnotation)
Predicate filter = AnnotationUtils.findAnnotation(controllerClazz, RequiresPermission.class) != null
? this::isApiMethod
: this::hasPermissionAnnotation;
// 过滤出Controller中需要权限验证的method
Set permissionMethodsWithinController = getPermissionMethodsWithinController(
controllerClazz.getName(),
controllerClazz.getMethods(),
filter
);
// ...
}
}
private Set getPermissionMethodsWithinController(String controllerName, Method[] methods, Predicate filter) {
return Arrays.stream(methods)
.filter(filter)
.map(method -> {
StringBuilder sb = new StringBuilder();
String methodName = method.getName();
return sb.append(controllerName).append("#").append(methodName).toString();
})
.collect(Collectors.toSet());
}
private boolean hasPermissionAnnotation(Method method) {
return AnnotationUtils.findAnnotation(method, RequestMapping.class) != null
&& AnnotationUtils.findAnnotation(method, RequiresPermission.class) != null;
}
private boolean isApiMethod(Method method) {
return AnnotationUtils.findAnnotation(method, RequestMapping.class) != null;
}
}
博主自己写的Java小册已经开始出售,欢迎加入一起学习。
小册介绍:https://zhuanlan.zhihu.com/p/212191791zhuanlan.zhihu.com