@FunctionalInterface函数式接口注解及其示例

特点

  1. 只能标记在"有且仅有一个抽象方法"的接口上,表示函数式接口;

  2. 接口如果重写了Object中的方法,如toString(),equals()方法,不算抽象方法;

    之所以只能有且仅有一个抽象方法是因为在调用函数编程时,如果有多个抽象方法的时候,那么()-> {}(或者a ->{…})这种写法,编译器就不知道这是重写的哪个方法了!

简单用例

  1. java中的Runable接口;

    源码如下

    //Runable接口源码类
    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    
    //Thread源码类:
    public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
    
    

    所以我们这样写:

    new Thread(
        ()->{
              System.out.println(Thread.currentThread().getName());
             }
      ).start();
    

    编译器对这个语法糖知道该解析成如下:

    new Thread(
      			new Runnable() {
      				@Override
      				public void run() {
      					System.out.println(Thread.currentThread().getName());
      				}
      			}
      	).start();
    

    大家注意看new Thread(Runable target),其中参数Runable是一个用@FunctionalInterface修饰的接口类,代表此参数形式可以已函数编程的形式传进来;

    其中()->{} 就是代表对run()方法的重写

    看到这里,我们是不是可以侠义上的理解:传入的参数是函数式写法,既然这样,那我们是不是可以通过这个特性来避免getter/setter拿属性名存在的硬编码问题!


项目落地(实际应用)

需求

 HashMap<String, Integer> map = new HashMap<>(8);
    map.put("age", Constant.AGE);
    map.put("sex", Constant.SEX);
    map.put("height", Constant.HEIGHT);

//其中,age,sex,height是people的属性

期望效果

HashMap<String, Integer> map = new HashMap<>(8);
    map.put(People::getAge, Constant.AGE);
    map.put(People::getSex, Constant.SEX);
    map.put(People::getHeight, Constant.HEIGHT);

这样的效果是避免字段修改时,硬编码导致出属性不存在(空指针)的bug;

代码实现

  1. 定义函数式接口

    //getter
    @FunctionalInterface
    public interface IGetter<T> extends Serializable {
        //此方法暂时用不到,只是为了使函数式接口不报错 
        Object apply(T source);
    }
    //setter
    @FunctionalInterface
    public interface ISetter<T, U> extends Serializable {
        //此方法暂时用不到,只是为了使函数式接口不报错 
        void accept(T t, U u);
    }
    
  2. 定义getter/setter引用转换属性名的工具类

    public class BeanUtils {
        ...
        /***
         * 转换方法引用为属性名
         * @param fn
         * @return
         */
        public static <T> String convertToFieldName(IGetter<T> fn) {
            SerializedLambda lambda = getSerializedLambda(fn);
            String methodName = lambda.getImplMethodName();
            String prefix = null;
            if(methodName.startsWith("get")){
                prefix = "get";
            }
            else if(methodName.startsWith("is")){
                prefix = "is";
            }
            if(prefix == null){
                log.warn("无效的getter方法: "+methodName);
            }
            // 截取get/is之后的字符串并转换首字母为小写
            return uncapFirst(StringUtils.substringAfter(methodName, prefix));
        }
        
        /***
         * 转换setter方法引用为属性名
         * @param fn
         * @return
         */
        public static <T,R> String convertToFieldName(ISetter<T,R> fn) {
            SerializedLambda lambda = getSerializedLambda(fn);
            String methodName = lambda.getImplMethodName();
            if(!methodName.startsWith("set")){
                log.warn("无效的setter方法: "+methodName);
            }
            // 截取set之后的字符串并转换首字母为小写(S为diboot项目的字符串工具类,可自行实现)
            return uncapFirst(StringUtils.substringAfter(methodName, "set"));
        }
        
        /***
         * 获取类对应的Lambda
         * @param fn
         * @return
         */
        private static SerializedLambda getSerializedLambda(Serializable fn){
            //先检查缓存中是否已存在
            SerializedLambda lambda = null;
            try{//提取SerializedLambda并缓存
                Method method = fn.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(Boolean.TRUE);
                lambda = (SerializedLambda) method.invoke(fn);
            }
            catch (Exception e){
                log.error("获取SerializedLambda异常, class="+fn.getClass().getSimpleName(), e);
            }
            return lambda;
        }
        //字符串并转换首字母为小写
        public static String uncapFirst(String input){
            if(input != null){
                return Character.toLowerCase(input.charAt(0)) + input.substring(1);
            }
            return null;
        }
    }
    
  3. 引用效果

    HashMap<String, Integer> map = new HashMap<>(8);
        map.put(BeanUtils.convertToFieldName(People::getAge), Constant.AGE);
        map.put(BeanUtils.convertToFieldName(People::getSex), Constant.SEX);
        map.put(BeanUtils.convertToFieldName(People::getHeight), Constant.HEIGHT);
    

以上代码摘自:[利用Lambda实现通过getter/setter方法引用拿到属性名]

细节分析

在以上案例中,运用到了java的序列化以及反射的相关知识,按照我自己的理解我解释一下(如有错误,感谢大家的指正):

  1. 在调用BeanUtils.convertToFieldName()方法的时候,由于所有类已经进行序列化,此时传入的参数时在其调用类的序列化对象的对应的lambda属性;
    @FunctionalInterface函数式接口注解及其示例_第1张图片
  2. 再然后通过getSerializedLambda()方法的暴力反射,获取到“writeReplace”方法(后面会解释这个方法时干嘛用的),执行该方法后直接获取到调用lambda方法的对象以及其执行的方法,即people对象和getAge方法;
    @FunctionalInterface函数式接口注解及其示例_第2张图片
  3. 最后回到convertToFieldName()方法中的lambda.getImplMethodName();再取消前缀即可获取属性名。

在这里给大家解释一下”writeReplace“,他是在类进行序列化的时候,默认会通过此方法修改序列化的对象,所以这方法可以获取到序列化的对象,这个是关键点;

另外大家对序列化的知识点还模糊的话,可以参考这个:Java Serializable接口(序列化)理解及自定义序列化

引用一下最关键的地方:

自定义序列化是由ObjectInput/OutputStream在序列化/反序列化时候通过反射检查该类是否存在以下方法(0个或多个):执行顺序从上往下,序列化调用1和2,反序列调用3和4;transient关键字当某个字段被声明为transient后,默认序列化机制就会忽略该字段。

  1. Object writeReplace() throws ObjectStreamException;可以通过此方法修改序列化的对象

  2. void writeObject(java.io.ObjectOutputStream out) throws IOException; 方法中调用defaultWriteObject() 使用writeObject的默认的序列化方式,除此之外可以加上一些其他的操作,如添加额外的序列化对象到输出:out.writeObject(“XX”)

  3. void readObject(java.io.ObjectInputStream in) throws Exception; 方法中调用defaultReadObject()使用readObject默认的反序列化方式,除此之外可以加上一些其他的操作,如读入额外的序列化对象到输入:in.readObject()

  4. Object readResolve() throws ObjectStreamException;可以通过此方法修改返回的对象

参考文献

  1. https://www.cnblogs.com/yoohot/p/6019767.html ---->序列化

  2. https://segmentfault.com/a/1190000019389160 —>利用Lambda实现通过getter/setter方法引用拿到属性名

  3. https://www.jianshu.com/p/52cdc402fb5d ---->函数式接口@FunctionalInterface

你可能感兴趣的:(Java)