zoukankan      html  css  js  c++  java
  • 简化 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>

  • 相关阅读:
    指定的参数已超出有效值的范围。参数名:sit ,先仔细看看错误和我的一样不一样
    简单说下C#变量的作用域
    C#常用的字符串处理方法
    驼峰命名、帕斯卡命名、匈牙利命名--三种命名方法
    Python的安装
    Python下numpy的使用
    命名法:骆驼(Camel)、帕斯卡(pascal)、匈牙利(Hungarian)、下划线(_)
    PHP中高级面试问题集锦
    利用python,生成word
    python实现网页截图
  • 原文地址:https://www.cnblogs.com/siweipancc/p/simplify_java_code_one_using_PropertyMapper.html
Copyright © 2011-2022 走看看