@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屬性;
    在這裏插入圖片描述
  2. 再然後通過getSerializedLambda()方法的暴力反射,獲取到“writeReplace”方法(後面會解釋這個方法時幹嘛用的),執行該方法後直接獲取到調用lambda方法的對象以及其執行的方法,即people對象和getAge方法;
    在這裏插入圖片描述
  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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章