特點
-
只能標記在"有且僅有一個抽象方法"的接口上,表示函數式接口;
-
接口如果重寫了Object中的方法,如toString(),equals()方法,不算抽象方法;
之所以只能有且僅有一個抽象方法是因爲在調用函數編程時,如果有多個抽象方法的時候,那麼()-> {}(或者a ->{…})這種寫法,編譯器就不知道這是重寫的哪個方法了!
簡單用例
-
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;
代碼實現
-
定義函數式接口
//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); }
-
定義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; } }
-
引用效果
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的序列化以及反射的相關知識,按照我自己的理解我解釋一下(如有錯誤,感謝大家的指正):
- 在調用BeanUtils.convertToFieldName()方法的時候,由於所有類已經進行序列化,此時傳入的參數時在其調用類的序列化對象的對應的lambda屬性;
- 再然後通過getSerializedLambda()方法的暴力反射,獲取到“writeReplace”方法(後面會解釋這個方法時幹嘛用的),執行該方法後直接獲取到調用lambda方法的對象以及其執行的方法,即people對象和getAge方法;
- 最後回到convertToFieldName()方法中的lambda.getImplMethodName();再取消前綴即可獲取屬性名。
在這裏給大家解釋一下”writeReplace“,他是在類進行序列化的時候,默認會通過此方法修改序列化的對象,所以這方法可以獲取到序列化的對象,這個是關鍵點;
另外大家對序列化的知識點還模糊的話,可以參考這個:Java Serializable接口(序列化)理解及自定義序列化
引用一下最關鍵的地方:
自定義序列化是由ObjectInput/OutputStream在序列化/反序列化時候通過反射檢查該類是否存在以下方法(0個或多個):執行順序從上往下,序列化調用1和2,反序列調用3和4;transient關鍵字當某個字段被聲明爲transient後,默認序列化機制就會忽略該字段。
Object writeReplace() throws ObjectStreamException;可以通過此方法修改序列化的對象
void writeObject(java.io.ObjectOutputStream out) throws IOException; 方法中調用defaultWriteObject() 使用writeObject的默認的序列化方式,除此之外可以加上一些其他的操作,如添加額外的序列化對象到輸出:out.writeObject(“XX”)
void readObject(java.io.ObjectInputStream in) throws Exception; 方法中調用defaultReadObject()使用readObject默認的反序列化方式,除此之外可以加上一些其他的操作,如讀入額外的序列化對象到輸入:in.readObject()
Object readResolve() throws ObjectStreamException;可以通過此方法修改返回的對象
參考文獻
-
https://www.cnblogs.com/yoohot/p/6019767.html ---->序列化
-
https://segmentfault.com/a/1190000019389160 —>利用Lambda實現通過getter/setter方法引用拿到屬性名
-
https://www.jianshu.com/p/52cdc402fb5d ---->函數式接口@FunctionalInterface