Java8中如何通过方法引用获取属性名详解

作者:brucelwl

这篇文章主要给大家介绍了关于Java8中如何通过方法引用获取属性名的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在我们开发过程中常常有一个需求,就是要知道实体类中Getter方法对应的属性名称(Field Name),例如实体类属性到数据库字段的映射,我们常常是硬编码指定 属性名,这种硬编码有两个缺点。

1、编码效率低:因为要硬编码写属性名,很可能写错,需要非常小心,时间浪费在了不必要的检查上。

2、容易让开发人员踩坑:例如有一天发现实体类中Field Name定义的不够明确,希望换一个Field Name,那么代码所有硬编码的Field Name都要跟着变更,对于未并更的地方,是无法在编译期发现的。只要有未变更的地方都可能导致bug的出现。

而使用了方法引用后,如果Field Name变更及其对应的Getter/Setter方法变更,编译器便可以实时的帮助我们检查变更的代码,在编译器给出错误信息。

那么如何通过方法引用获取Getter方法对应的Field Name呢?

Java8中给我们提供了实现方式,首先要做的就是定义一个可序列化的函数式接口(实现Serializable),实现如下:

* Created by bruce on 2020/4/10 14:16 @FunctionalInterface public interface SerializableFunction<T, R> extends Function<T, R>, Serializable {

而在使用时,我们需要传递Getter方法引用

//方法引用 SerializableFunction<People, String> getName1 = People::getName; Field field = ReflectionUtil.getField(getName1);

下面看具体怎么解析这个SerializableFunction,完整实现如下ReflectionUtil

public class ReflectionUtil { private static Map<SerializableFunction<?, ?>, Field> cache = new ConcurrentHashMap<>(); public static <T, R> String getFieldName(SerializableFunction<T, R> function) { Field field = ReflectionUtil.getField(function); return field.getName(); public static Field getField(SerializableFunction<?, ?> function) { return cache.computeIfAbsent(function, ReflectionUtil::findField); public static Field findField(SerializableFunction<?, ?> function) { Field field = null; String fieldName = null; try { // 第1步 获取SerializedLambda Method method = function.getClass().getDeclaredMethod("writeReplace"); method.setAccessible(Boolean.TRUE); SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function); // 第2步 implMethodName 即为Field对应的Getter方法名 String implMethodName = serializedLambda.getImplMethodName(); if (implMethodName.startsWith("get") && implMethodName.length() > 3) { fieldName = Introspector.decapitalize(implMethodName.substring(3)); } else if (implMethodName.startsWith("is") && implMethodName.length() > 2) { fieldName = Introspector.decapitalize(implMethodName.substring(2)); } else if (implMethodName.startsWith("lambda$")) { throw new IllegalArgumentException("SerializableFunction不能传递lambda表达式,只能使用方法引用"); } else { throw new IllegalArgumentException(implMethodName + "不是Getter方法引用"); // 第3步 获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象 String declaredClass = serializedLambda.getImplClass().replace("/", "."); Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader()); // 第4步 Spring 中的反射工具类获取Class中定义的Field field = ReflectionUtils.findField(aClass, fieldName); } catch (Exception e) { e.printStackTrace(); // 第5步 如果没有找到对应的字段应该抛出异常 if (field != null) { return field; throw new NoSuchFieldError(fieldName);

该类中主要有如下三个方法

  • String getFieldName(SerializableFunction<T, R> function) 获取Field的字符串name
  • Field getField(SerializableFunction<?, ?> function) 从缓存中查询方法引用对应的Field,如果没有则通过findField(SerializableFunction<?, ?> function)方法反射获取
  • Field findField(SerializableFunction<?, ?> function) 反射获取方法应用对应的Field
  • 1、首先我们看最后一个方法 Field findField(SerializableFunction<?, ?> function) ,该方法中第一步是通过SerializableFunction对象获取Class,即传递的方法引用,然后反射获取 writeReplace() 方法,并调用该方法获取导 SerializedLambda 对象。

    2、 SerializedLambda 是Java8中提供,主要就是用于封装方法引用所对应的信息,主要的就是方法名、定义方法的类名、创建方法引用所在类。

    3、拿到这些信息后,便可以通过反射获取对应的Field。

    4、而在方法 Field getField(SerializableFunction<?, ?> function) 中对获取到的Field进行缓存,避免每次都反射获取,造成资源浪费。

    除此之外似乎还有一些值得思考的问题

    writeReplace()方法是哪来的呢?

    首先简单了解一下 java.io.Serializable 接口,该接口很常见,我们在持久化一个对象或者在RPC框架之间通信使用JDK序列化时都会让传输的实体类实现该接口,该接口是一个标记接口没有定义任何方法,但是该接口文档中有这么一段描述:

    Serializable classes that need to designate an alternative object to be used when writing an object to the stream should implement this special method with the exact signature:
    ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
    This writeReplace method is invoked by serialization if the method exists and it would be accessible from a method defined within the class of the object being serialized. Thus, the method can have private, protected and package-private access. Subclass access to this method follows java accessibility rules.

    概要意思就是说,如果想在序列化时改变序列化的对象,可以通过在实体类中定义任意访问权限的Object writeReplace()来改变默认序列化的对象。

    那么我们的定义的SerializableFunction中并没有定义writeReplace()方法,这个方法是哪来的呢?
    代码中 SerializableFunction , Function 只是一个接口,但是其在最后必定也是一个实现类的实例对象,而方法引用其实是在运行时动态创建的,当代码执行到方法引用时,如 People::getName ,最后会经过
    java.lang.invoke.LambdaMetafactory
    java.lang.invoke.InnerClassLambdaMetafactory
    去动态的创建实现类。而在动态创建实现类时则会判断函数式接口是否实现了 Serializable ,如果实现了,则添加 writeReplace()

    值得注意的是,代码中多次编写的同一个方法引用,他们创建的是不同Function实现类,即他们的Function实例对象也并不是同一个
    我们可以通过如下属性配置将 动态生成的Class保存到 磁盘上

    java8中可以通过硬编码

    System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");

    jdk11 中只能使用jvm参数指定,硬编码无效,原因是模块化导致的

    -Djdk.internal.lambda.dumpProxyClasses=.

    示例代码如下:

    动态生成的Class如下:

    一个方法引用创建一个实现类,他们是不同的对象,那么ReflectionUtil中将SerializableFunction最为缓存key还有意义吗?

    答案是 肯定有意义的!!! 因为同一方法中的定义的Function只会动态的创建一次实现类并只实例化一次,当该方法被多次调用时即可走缓存中查询该方法引用对应的Field

    这里的缓存Key应该选用SerializableFunction#Class还是SerializableFunction实例对象好呢?

    看到有些实现使用SerializableFunction的Class作为缓存key,代码如下:

    public static Field getField(SerializableFunction<?, ?> function) { //使用SerializableFunction的Class作为缓存key,导致每次都调用function.getClass() return cache.computeIfAbsent(function.getClass(), ReflectionUtil::findField);

    但是个人建议采用SerializableFunction对象,因为无论方法被调用多少次,方法代码块内的方法引用对象始终是同一个,如果采用其Class做为缓存key,每次查询缓存时都需要调用native方法 function.getClass() 获取其Class,也是一种资源损耗。

    总结: Java如何通过方法引用获取属性名实现及思考至此结束。直接使用ReflectionUtil即可

    到此这篇关于Java8中如何通过方法引用获取属性名的文章就介绍到这了,更多相关Java8通过方法引用获取属性名内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

    您可能感兴趣的文章:
  • Token登陆验证机制的原理及实现
    Token登陆验证机制的原理及实现
    2021-12-12
  • springboot如何通过URL方式访问外部资源
    springboot如何通过URL方式访问外部资源
    2021-12-12
  • @valid 无法触发BindingResult的解决
    @valid 无法触发BindingResult的解决
    2021-12-12
  • java中a=a+1和a+=1的区别介绍
    java中a=a+1和a+=1的区别介绍
    2021-12-12
  • mybatisplus @Select注解中拼写动态sql异常问题的解决
    mybatisplus @Select注解中拼写动态sql异常问题的解决
    2021-12-12
  • Java如何实现读取txt文件内容并生成Word文档
    Java如何实现读取txt文件内容并生成Word文档
    2021-12-12
  • 详细解读Java Spring AOP
    详细解读Java Spring AOP
    2021-12-12
  • mybatis新增save结束后自动返回主键id详解
    mybatis新增save结束后自动返回主键id详解
    2021-12-12
  • 美国设下计谋,用娘炮文化重塑日本,已影响至中国
    美国设下计谋,用娘炮文化重塑日本,已影响至中国
    2021-11-19
  • 时空伴随者是什么意思?时空伴随者介绍
    时空伴随者是什么意思?时空伴随者介绍
    2021-11-09
  • 工信部称网盘企业免费用户最低速率应满足基本下载需求,天翼云盘回应:坚决支持,始终
    工信部称网盘企业免费用户最低速率应满足基本下载需求,天翼云盘回应:坚决支持,始终
    2021-11-05
  • 2022年放假安排出炉:五一连休5天 2022年所有节日一览表
    2022年放假安排出炉:五一连休5天 2022年所有节日一览表
    2021-10-26
  • 电脑版 - 返回首页

    2006-2023 脚本之家 JB51.Net , All Rights Reserved.
    苏ICP备14036222号