【Java】Java 中的方法引用写法

7 篇文章 0 订阅

概述

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

        @Override
        public 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>() {
        @Override
        public 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>() {
        @Override
        public String apply(Student student, String s) {
            return student.getStatus(s);
        }
    };
    
    //lambda
    BiFunction<Student, String, String> f2 = (student, s) -> student.getStatus(s);
    
	//method reference
    BiFunction<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>() {
        @Override
        public 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>() {
        @Override
        public 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 {

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private JwtAuthorizeFilter jwtAuthorizeFilter;

    @Bean
    public 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());
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值