概述
方法引用(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());}
}