特点

  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函数式接口注解及其示例
  2. 再然后通过getSerializedLambda()方法的暴力反射,获取到“writeReplace”方法(后面会解释这个方法时干嘛用的),执行该方法后直接获取到调用lambda方法的对象以及其执行的方法,即people对象和getAge方法;
    @FunctionalInterface函数式接口注解及其示例
  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

  • 版权声明:文章来源于网络采集,版权归原创者所有,均已注明来源,如未注明可能来源未知,如有侵权请联系管理员删除。

发表回复

后才能评论