因爲性格緣由,筆者很難沉下心來進行嚴肅的系統學習,老是喜歡折騰一些奇淫技巧,很是喜歡代碼設計,扣代碼的細節,因此本次分享一下我所知道的如何寫最少的代碼
的小技巧,若是你有更好的方案,歡迎在評論區留言,方案很棒的話,加我微信,爲你送上冬天的一杯奶茶~
java
秀一下Go
的多返回值:算法
package main import "fmt" // 返回 X + Y 和 X * Y func Computer(X, Y int) (int, int) { return X + Y, X * Y }
衆所周知,Java僅支持單一返回值,通常狀況下若是須要返回多個對象,咱們會根據代碼語義選擇容器或者新建一個新的類,把咱們須要的數據包起來。編程
這樣作有沒有問題?
固然沒有問題,可是瑕疵就在於:可能會產生沒啥語義但又不得不存在的中間類
,我我的很是討論該類代碼,那麼該如何解決這種問題呢?設計模式
首先須要認識到,解決方案必須知足幾個要求:安全
既然如此,咱們能夠採用泛型
來知足複用、語義清晰的要求,用中間類
來知足代碼安全性的要求,代碼以下:微信
public class MultipleTwoReturn<A, B> { /** 第一個返回值 **/ private final A first; /** 第二個返回值 **/ private final B second; public MultipleTwoReturn(A first, B second) { this.first = first; this.second = second; } // 省略Get方法 }
同時,咱們能夠依賴於繼承
,讓該工具類拓展更多的參數:數據結構
public class MultipleThreeReturn<A, B, C> extends MultipleTwoReturn<A, B> { /** 第三個返回值 **/ private final C third; public MultipleThreeReturn(A first, B second, C third) { super(first, second); this.third = third; } }
測試類:ide
public class MultipleApp { public static void main(String[] args) { MultipleTwoReturn<Integer, String> returnTest = MultipleApp.getReturnTest(); System.out.println(returnTest.getFirst()); System.out.println(returnTest.getSecond()); } private static MultipleTwoReturn<Integer, String> getReturnTest() { MultipleTwoReturn<Integer, String> demo = new MultipleTwoReturn<>(0, "Kerwin Demo."); return demo; } }
本質仍是普通對象
,可是加上泛型
後威力劇增!因爲在方法定義時就強制了泛型約束
,語義很是清晰,同時能夠徹底杜絕上述的無語義中間類,固然一些必要的,有業務含義的組裝類,不建議使用這種方式。函數式編程
你們在學Java泛型之初有沒有這種想法?我想利用
new
一個
T
,可是Java它
new
不出來啊 😂
好久以前我在寫一個通用的Java爬蟲接口
,裏面有一個功能就是傳入目標網頁的便可獲取到針對不一樣網頁設計的Bean
,大概以下所示:
public interface SpiderBeansHandle<T> { /** 獲取Url **/ String getUrl(); /** 獲取Cookie **/ String getCookie(); /** 獲取CSS selector **/ String getSelector(); // .... }
中間關鍵的一點即如何獲取到這個Bean,那個時候我只有一個想法:new 一個 T
事實證實,我過於天真了 🙄
可是換種思路,既然new
不出來,那我就返回一下吧,因而代碼出爐了~
public interface SpiderBeansHandle<T> { /** * 獲取Url */ String getUrl(); /** * 獲取Cookie */ String getCookie(); /*** * 獲取CSS selector */ String getSelector(); /*** * 解析Element * @param element element */ T parseElement(Element element); /*** * Get Beans * @param handle Bean對象 | handle對象 * @param <T> Bean類型 * @return List<Beans> */ static <T> List<T> getBeans(SpiderBeansHandle<T> handle) { List<T> list = new ArrayList<>(); List<Element> elements = SpiderUtils.getElementWithCookie(handle.getUrl(), handle.getSelector(), handle.getCookie()); for (Element element : elements) { T bean = handle.parseElement(element); if (bean != null) { list.add(bean); } } return list; } }
關鍵一步就在於:
/*** * 解析Element * @param element element */ T parseElement(Element element);
那麼這個小技巧有什麼用呢?仔細看會不會以爲它像一種設計模式的變形體?沒錯!真相只有一個:模板方法模式
我剛提到了我須要一個處理爬蟲的通用接口
,由於簡單爬蟲無非就是拿到url而後請求,解析細節封裝到自身的Bean裏,而後獲取一個列表,那麼在開發業務代碼的時候相似,確定有某些場景和需求具備高度的一致性,那麼使用這種設計方案便可大大的減小重複代碼~
我們在寫代碼的時候有沒有遇到過這種問題?寫了一個工具類方法,可是功能又過於單一,雖然說單一原則好吧,可是一個小邏輯寫一堆方法,總感受不得勁,如何解決咧?
Java8提供的函數式編程便可幫咱們必定程度上解決這種問題,如:
// 寫一個獲取文件列表,且判斷是否爲txt結尾的工具類方法,新手會這麼寫 public static File getFileWithTxt(String path) throws IOException { File file = new File(path); if (!file.exists()) { throw new IOException("File is not exist."); } if (file.getName().endsWith(".txt")) { return file; } return null; }
老手通常會把 .txt
做爲參數傳入,可是某一天我須要判斷文件大小,文件長度,甚至是文件內容的時候,我該咋辦?再寫N個?
最好的方案即傳入 Predicate
謂詞,讓調用者自定義處理邏輯
,而後再把最經常使用的邏輯基於該方法複寫一下,拓展性Max!代碼以下:
/*** * 文件夾謂詞匹配 * @param file 文件 * @param predicate 謂詞匹配 * @return List<File> * @throws IOException IOException */ public static List<File> listFilesInDirWithFilter(File file, Predicate<String> predicate) throws IOException { if (!file.exists()) { throw new IOException("File is not exist."); } List<File> fileList = new ArrayList<>(); if (file.isDirectory()) { File[] files = file.listFiles(); for (File f : Objects.requireNonNull(files)) { fileList.addAll(listFilesInDirWithFilter(f, predicate)); } } else { if (predicate.test(file.getName())) { fileList.add(file); } } return fileList; }
相似的還好比說處理 IO
,直接上代碼:
public static void readLine(BufferedReader br, Consumer<String> handle, boolean close) { String s; try { while (((s = br.readLine()) != null)) { handle.accept(s); } } catch (IOException e) { e.printStackTrace(); } finally { if (close && br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } }
方法
說你到底想幹嗎?!算了,你想幹嗎就幹嗎吧,請隨意😎~
寫的更多也是爲了寫的更少
,這句話乍一聽感受很是矛盾,可是編程經驗比較豐富的小夥伴應該能體會到方法重載
的威力,尤爲是在寫工具類或者底層接口的時候,建議你們先寫一個大而全的內部方法
,而後一點點去根據須要重載它,會有意想不到的好處。
最簡單的例子,以下:
// Root 方法 private static void readLine(BufferedReader br, Consumer<String> handle, boolean close) { String s; try { while (((s = br.readLine()) != null)) { handle.accept(s); } } catch (IOException e) { e.printStackTrace(); } finally { if (close && br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } } // 重載方法一 public static void readLine(String path, Consumer<String> handle, boolean close) { try { BufferedReader br = new BufferedReader(new FileReader(path)); readLine(br, handle, close); } catch (FileNotFoundException e) { e.printStackTrace(); } } // 重載方法二 public static void readLine(String path, Consumer<String> handle) { readLine(path, handle, true); }
重載
可讓咱們的方法調用方式變得豐富多彩,在語義明確的狀況下,寫代碼有如神助,配合函數式編程,可讓工具類或者底層接口的能力大大加強。
同時,當咱們須要調整某一個方法邏輯時,也可使用繼續重載
的方式,將影響面降到最小,儘可能不動其餘模塊的代碼。
與其說是如何寫最少的代碼
,不如說是:如何只寫真正有價值的代碼
。
面對這種問題的時候,咱們第一反應確定就是設計模式了,例如上文的泛型章節提到的模板方法模式
,小小的推薦一下我以前的文章:
經過良好的設計模式或者其變形體,咱們能夠獲得高內聚低耦合的代碼,這是一個很是好的思路。
另外一個思路,全部人都認同一點:程序 = 算法 + 數據結構
,選擇好正確的數據結構能夠事倍功半,好比說咱們作相似文件夾需求
的時候,會想到使用鏈表
或者樹
結構,在作如:如何高效的給用戶發送生日短信
時會想到用堆
結構(用當前時間對比堆中的最大值,知足則繼續迭代,減小遍歷)等等。
這其實都是抽象,或深或淺而已,我最開始學習Java的時候,老師會說一句話:萬物皆爲對象,咱們來看看上面的技巧各自對應着什麼?
因此如何只寫真正有價值的代碼?官方一點的話就是:把變化的抽象出來
,那麼到底該怎麼抽?
這就須要咱們一點點的去探索了,畢竟奇淫技巧
只是小道爾,不過我會一直探索下去。
若是你以爲這篇內容對你有幫助的話: