簡化 Java 代碼 ——(一)使用 PropertyMapper


1 前言

在日常的開發中,我們需要使用到各種非空,非 Null 等條件判定以保證程序不出錯,因此避免不了寫出臃腫的代碼。盡管 JDK 8 提供了強大的 Stream 流,但它並不總是能滿足各種需求。

網絡上對於 PropertyMapper 類的研究甚少,寫這篇文章也是為了記下所學知識,同時也希望給大家提供一個另類的思路,以簡化日常開發。

2 Spring 與 PropertyMapper

PropertyMapper 是 Spring 框架中的工具類,廣泛應用於框架的各處,作為一個程序猿,我們不應該局限於使用框架本身,更應從里邊學到各種程序設計。

3 使用 PropertyMapper

讓我們從規定一個實體類 Container 開始:

 1 /**
 2  * 容器類
 3  *
 4  * @author pancc
 5  * @version 1.0
 6  */
 7 @Data
 8 @Accessors(chain = true)
 9 public class Container {
10     /**
11      * 容器的位置,可以為  null 或者空字符串
12      */
13     private String location;
14     /**
15      * 容器中的所有數值,可以為 null 或者空
16      */
17     private Collection<Integer> integers;
18 }

 

對於這個實體類的字段,我們需要打印 location 去空之后的結果,容器內數值的二進制值。並且這個容器在方法參數中可能為空,對於一般的寫法:

 1     private void normal(@Nullable Container container) {
 2         String local = "";
 3         List<String> binaries = new ArrayList<>();
 4 
 5         if (container != null) {
 6             if (container.getLocation() != null) {
 7                 local = container.getLocation().trim();
 8             }
 9             if (container.getIntegers() != null) {
10                 container.getIntegers().stream().map(Integer::toBinaryString).collect(Collectors.toCollection(() -> binaries));
11             }
12         }
13 
14         System.out.println("local = " + local);
15         System.out.println("binaries = " + binaries);
16     }

 

上邊的盡管結合了 Stream ,但如果處理參數變得復雜,程序將會變得冗長不可讀。使用 PropertyMapper 可以極大地改善:

 1     private void mapper(@Nullable Container container) {
 2         StringBuilder local = new StringBuilder();
 3         List<String> binaries = new ArrayList<>();
 4 
 5         PropertyMapper mapper = PropertyMapper.get();
 6         mapper.from(container).whenNonNull().as(Container::getLocation).whenNonNull().as(String::trim).to(local::append);
 7         mapper.from(container).whenNonNull().as(Container::getIntegers).whenNonNull().to(is -> is.stream().map(Integer::toBinaryString).collect(Collectors.toCollection(() -> binaries)));
 8 
 9         System.out.println("local = " + local.toString());
10         System.out.println("binaries = " + binaries);
11     }

 

4 PropertyMapper 的核心解讀

4.1 核心

PropertyMapper 類主要有四大核心:

  1. Source<T> :一個存儲提供值的 Supplier<T> 與調用方法時對 Supplier 進行合法測試的 Predicate<T>;
  2. SourceOperator:對 Source<T> 實例進行處理的 lambda 接口,與 JDK 8 的 Function 接口不同的是它的返回值總是與入參相同
  3. NullPointerExceptionSafeSupplier<T>:特殊的 Supplier,供 whenNonNull 方法調用以避免空指針(返回 null 值)
  4. CachingSupplier<T>:緩存 PropertyMapper 實例中的 Supplier<T> 的返回值 result (保存在上次調用的父實例中)

4.2 細節解讀

4.2.1 靜態入口方法 PropertyMapper::get

PropertyMapper 類內部維護一個靜態實例,我們一開始只能通過獲取它得到 PropertyMapper 實例。從類的構造方法來看,父實例與 SourceOperator  都不存在。

4.2.2 實例方法 PropertyMapper::from

向 PropertyMapper 傳遞初始值的方法有兩種,from(T value) from(Supplier<T> supplier) ,前者實際上是后者的包裝,所以我們只看后者的實現。讓我們看一下內部的流程:

from(Supplier<T> supplier) 方法首先調用 getSource 獲得  Source<T> 實例。另,見 5.1 中的 sourceOperator 操作(需完成第 4 點的閱讀)。

getSource 首先會檢查父 PropertyMapper 是否存在,如果存在則繼承父的 Source<T> 實例(對應上邊 3 點中代碼第二次調用 mapper.from(container) 方法),否則新建一個 CachingSupplier<T> 接收 from 方法傳進來的 Supplier<T> 。可以看到此時於 Source<T>  的第二個構造參數 Predicate<T> 總是返回 true.

  

4.2.3 實例方法 Source::to

方法邏輯很簡單,首先從由 PropertyMapper 傳遞進來的 CachingSupplier<T> 中獲得值,然后使用內部的 Predicate<T> 對該值進行檢查,如果檢測通過則執行 Comsumer<T> 方法。

4.2.4 實例方法 Source::toInstance

與實例方法 to(Consumer<T> consumer) 相似但在判斷有所不同,實例方法 toInstance(Function<T, R> factory) 首先檢測值的合法與否,合法則進行轉換並返回轉換結果,否則拋出錯誤。實際開發中用到這個方法的場景可以說是沒有。

4.2.5 實例方法 Source::toCall

toCall(Runnable runnable) 方法的代碼不貼出,細節很簡單,當值合法,則執行參數 Runnable 

4.2.6 實例方法 Source::as

as 方法首先檢查值是否合法,如果合法則對值進行轉換,非法則將值設為 null,Predicate<T> 則繼續傳遞。

4.2.7 實例方法 Source::asInt

asInt 是對 as 方法的雙重調用,參數 Function<T, R> 需要負責映射當前值 T 到 Number

4.2.8 實例方法 Source::whenNonNull

whenNonNull 方法繼承了原有實例的 Supplier<T> 字段,覆蓋了原有的 Predicate<T> 字段以供后續值判斷或者條件附加使用。

4.2.10 實例方法 Source::when

when 方法是最靈活的,它接收一個 Predicate<T> 參數,用來組合自身持有的 Predicate<T> 字段(如果有的話)。以 when  方法為基礎的 whenEqualTo,whenFalse,whenHasText,whenNot,whenTrue 都是對它的進一步包裝,因此不展開。

4.2.11 實例方法 Source::whenInstanceOf

這個方法實際上組合了 when 方法 與 as 方法,因此具有判斷與轉換的雙重方法,需要格外注意。

 

5 額外的補充

5.1 實例方法 PropertyMapper::alwaysApplying

alwaysApplying 用於向 PropertyMapper 添加一個 when 條件判定,這個判定在次 from 方法中都被調用(見 4.2.2 )。alwaysApplyingWhenNonNull 則是對這個方法的包裝。

 

6 PropertyMapper 的副作用

  1. 內存/時間開銷:由於每次調用方法都返回一個新的 PropertyMapper 實例,在壓測中有明顯的性能影響 (對於附錄的代碼,常規與簡化方式的執行時間對比是 7ms:48ms)。
  2. 同一個對象不可復用:從上邊的例子中可以看到,對於常規的 if 判斷,后邊都可以享受到判斷結果,但是在 PropertyMapper 中一般則不可,套用則有可能影響閱讀(可看后邊的代碼)。

  

7 附 代碼

 1 /**
 2  * @author pancc
 3  * @version 1.0
 4  */
 5 public class PropertyTest {
 6 
 7 
 8     private static Container allNullContainer;
 9     private static Container container;
10 
11     @BeforeAll
12     static void initContainer() {
13         allNullContainer = new Container().setLocation(null).setIntegers(null);
14         container = new Container().setLocation(" xs").setIntegers(IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList()));
15     }
16 
17     @Test
18     public void testNormal() {
19         normal(allNullContainer);
20         normal(container);
21     }
22 
23     @Test
24     void testMapper() {
25         mapper(allNullContainer);
26         mapper(container);
27     }
28 
29     private void normal(@Nullable Container container) {
30         String local = "";
31         List<String> binaries = new ArrayList<>();
32 
33         if (container != null) {
34             if (container.getLocation() != null) {
35                 local = container.getLocation().trim();
36             }
37             if (container.getIntegers() != null) {
38                 container.getIntegers().stream().map(Integer::toBinaryString).collect(Collectors.toCollection(() -> binaries));
39             }
40         }
41 
42         System.out.println("local = " + local);
43         System.out.println("binaries = " + binaries);
44     }
45 
46     private void mapper(@Nullable Container container) {
47         StringBuilder local = new StringBuilder();
48         List<String> binaries = new ArrayList<>();
49 
50         PropertyMapper mapper = PropertyMapper.get();
51 
52 /*        mapper.from(container).whenNonNull().to(c -> {
53             mapper.from(c.getLocation()).whenNonNull().as(String::trim).to(local::append);
54             mapper.from(c.getIntegers()).whenNonNull().to(is -> is.stream().map(Integer::toBinaryString).collect(Collectors.toCollection(() -> binaries)));
55         });*/
56         mapper.from(container).whenNonNull().as(Container::getLocation).whenNonNull().as(String::trim).to(local::append);
57         mapper.from(container).whenNonNull().as(Container::getIntegers).whenNonNull().to(is -> is.stream().map(Integer::toBinaryString).collect(Collectors.toCollection(() -> binaries)));
58 
59         System.out.println("local = " + local.toString());
60         System.out.println("binaries = " + binaries);
61     }
62 }

 

CachingSupplier<T>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM