概述
方法引用(MethodReference)是Lambda表达式的另一种格式,在某些场景下可以提高代码的可读性
使用条件
只可以替换单方法的Lambda表达式
什么意思呢 ?
例如下面这个Lambda表达式就不可以使用方法引用替换,因为其不是单方法的,有好几行呢。如果想要使用方法引用就需要将Lambda结构体重构为一个方法。
Predicate<Integer> p2 = integer -> {System.out.println("Hello World");return TestUtil.isBiggerThan3(integer);};
下面这个就可以使用方法引用替换了
Predicate<Integer> p2 = integer -> TestUtil.isBiggerThan3(integer);
使用场景
当使用方法引用替换Lambda表达式具有更好的可读性时,考虑使用。
方法引用的类型
方法引用可以分为分四类,只要掌握了类型区别一切就变得易如反掌了。为行文方便,这里先列出要使用的类:
TestUtil 里面有一个静态方法,一个实例方法。Student 是一个普通实体类,具体如下代码所示
public class MethodReference {...//示例类public static class TestUtil {public static boolean isBiggerThan3(int input) {return input > 3;}public void printDetail(Student student) {System.out.println(student.toString());}}public static class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}public String getStatus(String thing) {return String.format("%d岁的%s正在%s", age, name, thing);}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}}
}
调用类的静态方法
Lambda 表达式的那个单方法是某个类的静态方法
有如下格式,args 是参数,可以是多个,例如(a1,a2,a3)
Lambda:
(args) -> Class.staticMethod(args)
Method Reference:
Class::staticMethod
符合上面形式的调用,不管有多少参数,都省略掉,编译器自动会帮我们传入
看看下面的实例代码,其中 isBiggerThan3 是TestUtil 类的 static 方法。从上面的代码你可以清晰的看到,方法从匿名类到 Lambda 再到方法引用的演变。
public void testStaticMethodRef() {//匿名内部类形式Predicate<Integer> p1 = new Predicate<Integer>() {@Overridepublic boolean test(Integer integer) {return TestUtil.isBiggerThan3(integer);}};//lambda表达式形式Predicate<Integer> p2 = integer -> TestUtil.isBiggerThan3(integer);//MethodReference形式Predicate<Integer> p3 = TestUtil::isBiggerThan3;Stream.of(1, 2, 3, 4, 5).filter(p3).forEach(System.out::println);
}
调用传入的实例参数的方法
Lambda:
(obj, args) -> obj.instanceMethod(args)
Method Reference:
ObjectType::instanceMethod
看到我们 Lambda 的入参 obj 了吗?它是一个类型,假设为 ObjectType,的实例对象。然后再看 Lambda 表达式,是在调用此实例 obj 的方法。这种类型的 Lambda 就可以写成上面的形式了,看起来和静态方法那个一样。
来看看下面的实例代码
public void testInstanceMethodRef1() {//匿名类BiFunction<Student, String, String> f1 = new BiFunction<Student, String, String>() {@Overridepublic String apply(Student student, String s) {return student.getStatus(s);}};//lambdaBiFunction<Student, String, String> f2 = (student, s) -> student.getStatus(s);//method referenceBiFunction<Student, String, String> f3 = Student::getStatus;System.out.println(getStudentStatus(new Student("erGouWang", 18), "study", f3));
}
private String getStudentStatus(Student student, String action, BiFunction<Student, String, String> biFunction) {return biFunction.apply(student, action);
}
调用已经存在的实例的方法
Lambda:
(args) -> obj.instanceMethod(args)
Method Reference:
obj::instanceMethod
我们观察一下我们 Lambda 表达式,发现obj对象不是当做参数传入的,而是已经存在的,所以写成方法引用时就是实例::方法。
来看看下面的实例代码,可见 utilObj 对象是我们提前 new 出来的,是已经存在了的对象,不是 Lambda 的入参。
public void testInstanceMethodRef2() {TestUtil utilObj = new TestUtil();//匿名类Consumer<Student> c1 = new Consumer<Student>() {@Overridepublic void accept(Student student) {utilObj.printDetail(student);}};//Lambda表达式Consumer<Student> c2 = student -> utilObj.printDetail(student);//方法引用Consumer<Student> c3 = utilObj::printDetail;//使用consumeStudent(new Student("erGouWang", 18), c3);
}private void consumeStudent(Student student, Consumer<Student> consumer) {consumer.accept(student);
}
调用类的构造函数
Lambda:
(args) -> new ClassName(args)
Method Reference:
ClassName::new
当 Lambda 中的单方法是调用某个类的构造函数,我们就可以将其写成如上形式的方法引用。
public void testConstructorMethodRef() {BiFunction<String, Integer, Student> s1 = new BiFunction<String, Integer, Student>() {@Overridepublic Student apply(String name, Integer age) {return new Student(name, age);}};//lambda表达式BiFunction<String, Integer, Student> s2 = (name, age) -> new Student(name, age);//对应的方法引用BiFunction<String, Integer, Student> s3 = Student::new;//使用System.out.println(getStudent("cuiHuaNiu", 20, s3).toString());
}private Student getStudent(String name, int age, BiFunction<String, Integer, Student> biFunction) {return biFunction.apply(name, age);
}
来看看下面的实例代码,上面代码值得注意的就是,Student 类必须有一个与 Lambda 入参相匹配的构造函数。例如此例中,(name, age) -> new Student(name, age); 需要两个入参的构造函数,为什么呢?
因为我们的 Lambda 表达式的类型是 BiFunction,而其正是通过两个入参来构建一个返回的。其签名如下:
@FunctionalInterface
public interface BiFunction<T, U, R> {R apply(T t, U u);...
}
通过入参 (t,u) 来生成 R 类型的一个值。
我们可以写一个如下的方法引用:
Function<String, Student> s4 = Student::new;
但是IDE就会报错,提示我们的 Student 类没有对应的构造函数,我们必须添加一个如下签名的构造函数才可以
public Student(String name) {this.name = name;
}
补充
下面这段代码是一个Spring Security的配置类,可以看到在这段代码中用到了this::onAuthenticationSuccess,this::onAuthenticationFailure,this::onLogoutSuccess,这样的方法引用写法。
具体来说,this::onAuthenticationSuccess 表示引用当前类中的 onAuthenticationSuccess 方法。
这里使用 this 表示当前对象(通常是一个类的实例),:: 是方法引用操作符,onAuthenticationSuccess 则是方法的名称。
在 Spring Security 配置中,使用方法引用可以简洁地传递方法作为参数,而不必显式地编写 lambda 表达式。在这种情况下,this::onAuthenticationSuccess 会将当前类的 onAuthenticationSuccess 方法作为参数传递给 successHandler 方法。
/*** @ClassName : SecurityConfiguration* @Description : Security配置* @Author : LYQ* @Date: 2024-02-17 19:24*/
@Configuration
public class SecurityConfiguration {@Autowiredprivate JwtUtils jwtUtils;@Autowiredprivate JwtAuthorizeFilter jwtAuthorizeFilter;@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {return http.authorizeHttpRequests(conf -> conf.requestMatchers("/api/auth/**").permitAll().anyRequest().authenticated()).formLogin(conf -> conf.loginProcessingUrl("/api/auth/login").successHandler(this::onAuthenticationSuccess).failureHandler(this::onAuthenticationFailure)).logout(conf -> conf.logoutUrl("/api/auth/logout").logoutSuccessHandler(this::onLogoutSuccess)).exceptionHandling(conf -> conf.accessDeniedHandler(this::onAccessDeny).authenticationEntryPoint(this::onUnauthorized)).csrf(AbstractHttpConfigurer::disable).sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).addFilterBefore(jwtAuthorizeFilter, UsernamePasswordAuthenticationFilter.class).build();}public void onAccessDeny(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {response.setContentType("application/json;charset=UTF-8");response.getWriter().write(RestBean.forbidden(accessDeniedException.getMessage()).asJsonString());}public void onUnauthorized(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {response.setContentType("application/json;charset=UTF-8");response.getWriter().write(RestBean.unauthorized(exception.getMessage()).asJsonString());}public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("application/json");response.setCharacterEncoding("utf-8");User user = (User) authentication.getPrincipal();String token = jwtUtils.createJwt(user, 1, "MrVK");AuthorizeVO vo = new AuthorizeVO();vo.setExpire(jwtUtils.expireTime());vo.setRole("");vo.setToken(token);vo.setUsername("MrVK");response.getWriter().write(RestBean.success(vo).asJsonString());}public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().write(RestBean.unauthorized(exception.getMessage()).asJsonString());}public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("application/json;charset=utf-8");PrintWriter writer = response.getWriter();String authorization = request.getHeader("Authorization");if(jwtUtils.invalidateJwt(authorization)) {writer.write(RestBean.success("退出登录成功").asJsonString());return;}writer.write(RestBean.failure(400, "退出登录失败").asJsonString());}
}