65.9K
CodeProject 正在变化。 阅读更多。
Home

functionExtensions 技术 3:存储库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (2投票s)

2018年5月10日

CPOL

17分钟阅读

viewsIcon

5592

这是三集中的第三集,旨在介绍我的开源库 functionExtensions 中 Repository 类所应用的一些考量和技术。

引言

functionExtentions 是一个 Java 库,它实现了 Throwable 函数式接口、元组和 Repository,旨在加速 Java 8 的函数式编程。它已在 Maven 上发布,并实现了以下目标:

  • 声明了一组丰富的可抛出异常的函数式接口,这些接口可以通过共享的 exceptionHandler 处理已检查异常,从而允许开发人员仅在 lambda 表达式中定义相关的业务逻辑,并将其转换为常规接口。
  • 实现了一个不可变数据结构,用于以一组 Tuple 类存储和检索最多 20 个强类型值。
  • 提供 Repository 作为一种基于 Map 的智能工具,它具有预定义的业务逻辑,用于评估给定的一个或多个键(最多 7 个强类型值作为单个 Tuple 键)以获取相应的一个或多个值(最多 7 个强类型值作为单个 Tuple 值),并在没有发生异常时缓冲并返回它们。
  • 多个强大的通用工具支持上述三种类型的工具,主要通过 Repository 构建,支持各种原始类型、对象类型和数组的组合。例如:
    • Object getNewArray(Class clazz, int length):新建一个**任何**类型的数组,其中包含新建实例的元素类型和长度。
    • String deepToString(Object obj):返回指定数组“深层内容”的字符串表示形式。
    • Object copyOfRange(Object array, int from, int to):复制数组的全部或部分作为相同类型的新数组,无论数组是否由原始值组成。
    • T convert(Object obj, Class<T> toClass):将对象转换为任何等效或可赋值的类型。
    • boolean valueEquals(Object obj1, Object obj2):比较任意两个对象。如果两者都是数组,则将原始值视为等于其包装器,并使用预定义的默认策略处理 null 和空数组元素进行比较。

本系列文章包括:

  1. 可抛出异常的函数式接口
  2. 元组
  3. 存储库

引言

在这篇文章中,我将讨论 Repository 类的设计目标、考量和实现,该类使用泛型函数作为 JAVA Map 的核心,以评估、缓存和检索一组数据作为从另一组数据(作为键)产生的值。

它从 Lazy<T> 开始,接着是 Repository<TKey, TValue> 和更强类型的 TupleRepository 类,最后是一些由它们支持的强大工具,作为如何使用此库实现复杂工具的示例。

背景

Wiki 上对惰性初始化的定义是:惰性初始化是一种策略,即延迟创建对象、计算或某些其他昂贵的进程,直到第一次需要它时才进行。它是一种惰性求值,特指对象或其他资源的实例化。

在我看来,将初始化推迟到使用时以加快应用程序启动只是我们从后期评估中可以获得的好处之一,通常可以:

  • 将数据检索封装在 Lazy 实例中,从而使相关对象的用户可以专注于使用它们,而不是创建它们。
  • 当与底层记忆化机制结合时,进行缓存以提高处理效率。
  • 如果正确实现了 AutoCloseable,则有可能轻松管理所涉及的资源。
  • 类似的想法可以应用于 MapTuple,以使一组数据能够 Lazy 求值。

Lazy<T>

Lazy<T> 的实现基于我在Throwable 函数式接口的第一篇文章中讨论的技术:通过保存获取感兴趣数据的业务逻辑,而不是数据本身,然后在数据即将使用时才评估业务逻辑。

注意:在当前版本中,Lazy<T> 的实现不是线程安全的。

Lazy<T> 的实现非常简单,通过保留一个 final SupplierThrowable<T> supplier 实例,它扮演着 C# 中 Lazy(T) 的 Func<T> 的相同角色,Lazy<T> 允许抛出异常,让用户专注于业务逻辑。

然后,有四个辅助字段用于保存强类型数据缓存和 AutoCloseable 支持。

private boolean isInitialized = false;
private T value;
private boolean isClosed = false;
protected List<AutoCloseable> dependencies;

背后的思想非常简单,即保留获取相关值的方法,只有当用户通过调用 T getValue() public 方法真正需要该值时才进行评估,然后:

  • 通常,supplier 中保存的业务逻辑将成功触发以获取实际数据,该数据将由 value 字段缓存以供以后使用。
  • 如果发生任何异常,将使用 Functions.Default 定义的异常处理机制来:
    • 要么抛出 RuntimeException
    • 要么在发生任何异常时返回默认值

正如我在上一篇文章中讨论的 Tuple 类一样,Lazy<T> 可以通过实现 AutoCloseable 接口来释放其值的资源。

Repository<TKey, TValue>

Lazy<T> 可以用作**单个**值的缓存,这显然不足以支持递归编程计算机科学中的递归是一种问题解决方案依赖于同一问题的较小实例的解决方案的方法)。

作为 KeyValuePairs 的集合,其中唯一的 Key 具有关联的 Value,JAVA Map 超高效的哈希函数和冲突解决方案确保了对任何给定键的值的快速访问。当将键视为提供给某些业务逻辑进行评估的条件,将值视为评估结果时,Map 比 JAVA 的 switchIf+Else 条件运算符更快且更具扩展性。Repository 类旨在将业务逻辑嵌入到这种通用且组织良好的数据结构中,以实现更高的生产力。

背景

要用 Java 解决实际问题,Map 是我最喜欢用来保存中间结果的介质,特别是当这些结果通过具有固定模式的单一方法获得时,如下面的伪代码所示:

Map<String, SomeClass> cache = new HashMap();

public SomeClass get(String key){
  if(!cache.containsKey(key)){
     SomeClass newInstance = doSomethingWith(key);  //Use the concerned business logic
                                                    //to retrieve value of key
     cache.put(key, newInstance);
     return newInstance;
  }
  return cache.get(key);
}

在我看来,除了被调用的业务逻辑(在上面的例子中是 SomeClass doSomethingWith(key))之外,所有上述代码都是重复的,因此应该用一个更通用的缓存工具——Repository<TKey, TValue> 来替换。这个命名沿用了我的 C# 项目 DataCenter 在缓存不可用时的情况,如果您对此工具有更好的名称,请告诉我。

实现

作为函数式编程中一个非常重要的概念,在使用本节讨论的 Repository 类时,假定其不可变性,这意味着:

  • 要评估的键必须是不可变的;
  • 从给定键评估得到的值在不同时间绝不能不同。

Repository<TKey, TValue> 类被设计为 FunctionThrowable<TKey, TValue> 的扩展。在内部,它依赖于最终的 Map 实例来定位和缓冲 KeyValuePairs,但使用其自身的智能来计算给定键的值。

智能是指一个预定义的 final FunctionThrowable<TKey, TValue> 方法,在构造 Repository 实例时提供,如果没有发生递归调用,则可以直接调用。正如在Throwable 函数式接口中讨论的,仅定义从键获取值的核心业务逻辑,此方法可能会抛出 Exception,它会:

  • 如果键之前已成功评估:则直接检索缓存的值并返回;
  • 否则,将使用预定义的业务逻辑评估给定的键。
    • 如果评估成功,则结果将作为给定键的值保存,然后返回给调用者;
    • 如果评估失败并抛出 Exception,则调用者需要通过以下方式处理抛出的 Exception
      • 当通过调用 TValue apply(TKey tKey) throws Exception 直接查询键时,在 try{}catch{} 块中直接处理它们。
      • 或者,当调用 TValue get(TKey tKey, TValue defaultValue) 时,提供一个默认值,当发生 Exception 时,Exception 将被忽略。

当某些键无法通过预定义方法方便地评估时,建议使用一个预先填充了 KeyValuePairsMap,就像此库中稍后将讨论的许多实用工具所应用的那样。

也可以通过调用 TValue update(TKey tKey, TValue existingValue, TValue newValue) 更新 Map 以将键与新值关联以替换旧值,其中如果键尚未被评估,则 existingValuenull

要删除某些 KeyValuePairs,应调用 int clear(BiPredicate<TKey, TValue> keyValuePredicate),其中 keyValuePredicate 指示要删除的一个或多个对。

Repository<TKey, TValue> 的实现没有应用特殊技术,一个简单的测试将展示其操作符的用法。

Repository<String, Integer> repository = new Repository<>(s -> s.length());
assertEquals(Integer.valueOf(0), repository.apply(""));
assertEquals(Integer.valueOf(1), repository.apply("a"));
assertEquals(Integer.valueOf(3), repository.apply("abc"));

assertEquals(Integer.valueOf(33),
   repository.update("abc", Integer.valueOf(3), Integer.valueOf(33)));
assertEquals(Integer.valueOf(33), repository.apply("abc"));

//Insert new value with oldValue as null
assertEquals(Integer.valueOf(-1), repository.update("xyz", null, -1));
assertEquals(Integer.valueOf(-1), repository.apply("xyz"));

TupleRepositories

Java Map 存储的通用 KeyValuePairs 默认由强类型 Key 和与该键关联的强类型值组成,这暴露了两个不便之处:

  1. 大多数 Java 方法接受多个输入参数并返回单个值作为结果,Map 不能直接将所有输入参数作为单个键保存。
  2. Java 方法只能返回 0 或 1 个值作为评估结果,因此需要多个具有相同 Key 集合的 Map 来保存多个关联值。

不可变、通过实际值进行高效比较、由任意数量的具有强类型访问器的元素组成,Tuples 类非常适合:

  1. 将一组一个或多个元素作为 Map 的单个强类型 Tuple 键;
  2. 将关联的一个或多个元素作为由给定强类型函数返回的单个强类型 Tuple 值,该函数将所有评估结果合并为一个 Tuple

通过扩展 Repository 类TupleRepository<TKey> 提供了上述目标的部分实现。

public class TupleRepository<TKey> extends Repository<TKey, Tuple> {
    protected TupleRepository(FunctionThrowable<TKey, Tuple> valueFunction){
        this(new HashMap(), null, valueFunction);
    }

    public Tuple retrieve(TKey key){
        return get(key, null);
    }

    @Override
    public Tuple update(TKey tKey, Tuple existingValue, Tuple newValue) throws Exception {
        Objects.requireNonNull(newValue);
        return super.update(tKey, existingValue, newValue);
    }
...
}

只需提供一个泛型类型来指定键,而值只是类型擦除的 Tuple 类型,可以具有任意长度和任意类型的元素。尽管这种模糊性带来的灵活性可以在某些特殊场景中加以利用,但对于大多数应用程序而言,支持多键和多值的基于 TupleRepository 类型更为实用。

多键到多值的映射

在该库的当前版本(1.0.1)中,最多可以使用 **7** 个元素来组成 Map 的键,最多可以使用 **7** 个元素来组成值:这意味着至少需要定义 **7 * 7 = 49** 个泛型类,以及无数的强类型访问器来检索关联值中位置 1 到 7 的元素。

那将是一场噩梦,甚至比我为启用 Tuples 的强类型访问器所面临的还要糟糕。幸运的是,就像在类之间共享重复的访问器作为继承接口的默认方法链一样,相同的技术首先用于在 TupleRepositories 中共享它们。

从处理单个泛型类型作为 Key 的简单情况开始,实现如下所示:

public interface TupleValues<TKey> {
    Tuple retrieve(TKey key);
    boolean containsKey(Object key);
}
public interface TupleValues1<TKey, T> extends TupleValues<TKey> {
    default T getFirstValue(TKey key) {
        Tuple tuple = retrieve(key);
        if(tuple == null)
            return null;
        return (T) tuple.getValueAt(0);
    }
}
...
public interface TupleValues7<TKey, T,U,V,W,X,Y,Z> extends TupleValues6<TKey, T,U,V,W,X,Y> {
    default Z getSeventhValue(TKey key) {
        Tuple tuple = retrieve(key);
        if(tuple == null)
            return null;
        return (Z) tuple.getValueAt(6);
    }
}

这与 Tuples 的实现几乎相同。然而,它并不能帮助我们以多个输入作为键来检索第一个、第二个...第七个元素。例如,当键由两个元素组成时,如何以这两个元素作为输入来访问关联值的元素呢?显然,在 JAVA 中,必须定义一些方法,如 T getFirst(K1, K2), U getSecond(K1, K2) ... Z getSeventh(K1, K2),以使相应的 Repositories 易于使用。

同样,这是通过继承接口的默认方法实现的。为了避免命名这些接口,并使代码易于复制+更新+粘贴,TupleKeys 首先被定义为一个简单接口。

public interface TupleKeys <TKey> {
    boolean containsKey(Object key);
}

然后,通过扩展 TupleKeys,并分别添加键元素的详细类型,定义了 TupleKeys1TupleKeys2、... TupleKeys7,以处理由 1、2...7 个元素组成的 Tuple。以 TupleKeys2 为例,其仅有的两个默认方法如下:

public interface TupleKeys2<K1,K2> extends TupleKeys<Tuple2<K1,K2>> {
    default Tuple2<K1, K2> getKey(K1 k1, K2 k2){
        return Tuple.create(k1, k2);
    }
    default boolean containsKeyOf(K1 k1, K2 k2){
        return containsKey(getKey(k1, k2));
    }...

然后,类似于上面继承的 TupleValues1, TupleValues2, ... TupleValues7 接口,另一组内部接口被定义,它们共享相同的名称,并包含构成键和值的所有泛型类型。

interface TupleValues1<K1,K2, T> extends TupleKeys2<K1,K2>,
        io.github.cruisoring.repository.TupleValues1<Tuple2<K1, K2>, T> {

    default Tuple retrieve(K1 k1, K2 k2){
        return retrieve(getKey(k1, k2));
    }
    default T getFirst(K1 k1, K2 k2) {
        return getFirstValue(getKey(k1, k2));
    }
}

interface TupleValues2<K1,K2, T,U> extends TupleValues1<K1,K2, T>,
        io.github.cruisoring.repository.TupleValues2<Tuple2<K1, K2>, T,U> {
    default U getSecond(K1 k1, K2 k2) {
        return getSecondValue(Tuple.create(k1, k2));
    }
}
...
interface TupleValues7<K1,K2, T,U,V,W,X,Y,Z> extends TupleValues6<K1,K2, T,U,V,W,X,Y>,
        io.github.cruisoring.repository.TupleValues7<Tuple2<K1,K2>, T,U,V,W,X,Y,Z> {
    default Z getSeventh(K1 k1, K2 k2) {
        return getSeventhValue(getKey(k1, k2));
    }
}

同样的情况也发生在 TupleKeys1TupleKeys2、... TupleKeys7 的相应内部接口中,形成了一个 1-7 输入和 1-7 输出的矩阵。

有几点值得注意:

  • TupleValuesabstract 方法 Tuple retrieve(TKey key) 已被 Tuple retrieve(K1 k1, K2 k2) 的默认方法调用,以获取 Tuple 值。
  • 同样地,TupleValues1T getFirstValue(TKey key)TupleKeys2.TupleValues1T getFirst(K1 k1, K2 k2) 调用,以及 getSecond()...getSeventh():所有这些都直接依赖于 TupleValues1, TupleValues2, ... TupleValues7 的默认方法,在扩展它们以及 TupleKeys2 内部的先前的内部接口之后。
  • 这些代码呈现了一个清晰的模式,实际上,大多数这些文件都是通过使用正则表达式更新一个文件并以其他名称保存而生成的。

TupleRepository 类(TupleRepository1TupleRepository2 ...TupleRepository7)被定义为使用 Tuple1<T>Tuple2<T,U>...Tuple7<T,U,V,W,X,Y,Z> 分别保存由 1-7 个元素组成的 Tuple 作为值。然后,遵循类似的模式,它们的内部类使用 TupleKeys1TupleKeys2、... TupleKeys7 的重复名称来保存强类型 Tuple,将 1-7 个元素作为键。在 Tuple 不可变和可比较的假设下,任何一组强类型值作为唯一键,都应该始终与另一组强类型值作为关联的唯一值匹配。

TupleRepository2 为例,它处理值为由两个元素组成的 Repository,并且来自任何泛型类型 Key

public class TupleRepository2<TKey, T, U>
        extends Repository<TKey, Tuple2<T,U>>
        implements TupleValues2<TKey, T, U> {
@Override
public Tuple2<T,U> retrieve(TKey key) {
    return get(key, null);
}
@Override
public Tuple2<T,U> update(TKey tKey, Tuple2<T,U> existingValue,
       Tuple2<T,U> newValue) throws Exception {
    Objects.requireNonNull(newValue);
    return super.update(tKey, existingValue, newValue);
}
...
}

它的 Tuple2<T,U> retrieve(TKey key) 实际上已经覆盖了 TupleValuesabstract 方法 Tuple retrieve(TKey key),这得益于 Java 类型擦除,并且通过调用 get(key, null) 并使用默认值 nullTupleRepository 类在评估出现任何异常时将始终返回 null

然后,其 static internal class TupleKeys3<K1,K2,K3, T,U> 的实现具有更具体的键元素类型信息,以表示一个 Repository,其 Key 是由 3 个类型为 K1, K2, K3 的元素组成的 Tuple,而 Value 是由 2 个类型为 T 和 U 的元素组成的 Tuple

public static class TupleKeys3<K1,K2,K3, T,U> extends TupleRepository2<Tuple3<K1,K2,K3>, T,U>
        implements io.github.cruisoring.repository.TupleKeys3.TupleValues2<K1,K2,K3, T,U> {

    protected TupleKeys3(Map<Tuple3<K1,K2,K3>, Tuple2<T,U>> map,
                         TriConsumerThrowable<Tuple3<K1,K2,K3>,
                         Tuple2<T,U>, Tuple2<T,U>> changesConsumer,
                         TriFunctionThrowable<K1, K2, K3, Tuple2<T, U>> valueFunction) {
        super(map, changesConsumer, triple -> valueFunction.apply(triple.getFirst(),
              triple.getSecond(), triple.getThird()));
    }
    protected TupleKeys3(TriFunctionThrowable<K1, K2, K3, Tuple2<T, U>> valueFunction) {
        this(new HashMap(), null, valueFunction);
    }
}

即使只定义了构造函数,通过简单地扩展 TupleKeys3.TupleValues2 接口,便可免费获得有用的强类型单值元素访问器 T getFirst(K1 k1, K2 k2, K3 k3)U getSecond(K1 k1, K2 k2, K3 k3),以及其他默认方法。

所有的 TupleRepository 类都定义了 protected 构造函数,并且应该使用工厂方法来创建带有键值检索器方法以及可选的预填充 KeyValuePairsMap 实例。

以这种方式组织方法,不仅将它们定义为继承接口的默认方法,而且还由内部接口/类作为矩阵包含,具有一些明显的优点:

  • 这些数十个类没有重复代码。
  • 可扩展的代码,可以在源文件中使用简化的命名方案进行扩展。
  • 编译或运行时未观察到性能损失。

一些可能的缺点:

  • 在审查整个项目时,方法不容易定位。
  • 复杂的继承结构可能导致 JavaDoc 运行消耗 5.6G 内存,我已将其报告为 Oracle 的一个 bug。

无论如何,这些技术可以被视为一种组织一组共享类似方法的类的选项。

如何使用

该项目托管在 github.com 上,要包含该库,请将以下信息添加到您的 Maven 项目中:

<dependency>
			<groupId>io.github.cruisoring</groupId>
			<artifactId>functionExtensions</artifactId>
			<version>1.0.1</version>
</dependency>

Lazy<T>Repository<TKey, TValue> 都相当简单,使用 TupleRepository 类来保存简单的数据集也相当直接,这可以从库的许多测试中看出。

使用 TupleRepository 类从多个输入获取多个输出更具挑战性和趣味性,本文将对此进行讨论。

首先,TupleRepository 的定义和构造需要结合相关的业务逻辑进行规划,以评估一组输入并获取另一组输出。

  1. 首先,**给定输入及其关联输出之间是否存在不可变关系?**如果不存在,例如,7 个参数的评估在不同时间会得到不同的结果,那么基于范式的 TupleRepository 可能不是正确的选择。
  2. **应该持久化多少个输出值?**由于 1 到 7 个元素可以作为给定 keyset 的值的一个 Tuple1Tuple2...Tuple7 实例一起持久化,因此自然会选择 TupleRepository1TupleRepository2 ...TupleRepository7 的强类型版本来使用 Tuple1<T>Tuple2<T,U>...Tuple7<T,U,V,W,X,Y,Z> 来保存 1-7 个输出值。例如,对于一组输入,需要持久化 2 个关联值,那么 TupleRepository2 是显而易见的选择。
  3. 需要多少输入参数?
  4. 是否定义了接收多个输入并以单个强类型元组返回多个输出的方法?
    • 可以使用 Lambda 表达式或遵循相应 Throwable 函数式接口的方法,通常对于复杂逻辑更倾向于使用方法,以便于调试和理解。
    • 此方法仍将遵循定义 JAVA 方法的规则,具有单一返回类型,但该返回类型应为识别所有元素类型的强类型 Tuple
    • 输入参数列表使得方法易于理解,而不是接受包含所有输入元素的单个强类型 Tuple。例如:
    • 此方法无需处理操作抛出的异常,如果捕获到 ExceptionTupleRepository 将简单地返回 null,这表示在预期强类型 Tuple 时发生异常情况。
    • 例如,TypeHelpermakeBaseTypeConverters 方法如下所示,用于提取特定 Class 的 6 个属性/运算符:
    private static Tuple6<Boolean, Object, Class, Function<Object,Object>,
     Function<Object,Object>, Function<Object,Object>> makeBaseTypeConverters(Class clazz){
        Boolean isArray = clazz.isArray();
        if(!isArray && !clazz.isPrimitive())
            return Tuple.create(false, null, OBJECT_CLASS, 
                                returnsSelf, returnsSelf, returnsSelf);
    
        Class componentClass = clazz.getComponentType();
        Boolean isPrimitive = isPrimitive(componentClass);
    
        Object defaultValue = EMPTY_ARRAY_AS_DEFAULT ?
               ArrayHelper.getNewArray(componentClass, 0) : null;
        Class equivalentComponentClass = getEquivalentClass(componentClass);
    
        Class equivalentClass = classOperators.getThirdValue(equivalentComponentClass);
        Function<Object, Object> componentConverter =
                         getToEquivalentSerialConverter(componentClass);
        TriConsumerThrowable<Object, Integer, Object> equivalentSetter =
                         getArrayElementSetter(equivalentComponentClass);
    
        TriFunctionThrowable.TriFunction<Object, Object, Integer, Boolean>
                getExceptionWhileMapping =
                getExceptionWithMapping(equivalentSetter, componentConverter);
    
        Function<Object, Object> parallelConverter = ...;
    
        Function<Object, Object> serialConverter = ...;
    
        Function<Object, Object> defaultConverterr = ...;
    
        return Tuple.create(isPrimitive, defaultValue, equivalentClass,
                            parallelConverter, serialConverter, defaultConverterr);
    }
  5. 是否有任何需要直接提供的 KeyValuePairs?
    • 有时,这些 KeyValuePairs 需要作为种子提供,以便上述业务逻辑评估其他键以获取正确的值;
    • 在其他情况下,默认值将被覆盖,然后提供的键值对将阻止进一步的评估。
    • 这些 KeyValuePairs 应填充到一个与 TupleRepository 签名完全匹配的 Map 实例中。
    • 例如,以下 Map 实例直接提供给 TupleRepository 的构造函数:
    new HashMap(){{
        //For primitive values, return itself as object would convert it
        //to the wrapper type automatically
        Function<Object,Object> convertWithCasting = returnsSelf;
        put(boolean.class, Tuple.create(
                true
                , false
                , Boolean.class
                , convertWithCasting
                , convertWithCasting
                , convertWithCasting));
        convertWithCasting = fromElement -> ((Boolean)fromElement).booleanValue();
        put(Boolean.class, Tuple.create(
                false
                , false
                , boolean.class
                , convertWithCasting
                , convertWithCasting
                , convertWithCasting));
        convertWithCasting = returnsSelf;
        put(byte.class, Tuple.create(
                true
                , (byte)0
                , Byte.class
                , convertWithCasting
                , convertWithCasting
                , convertWithCasting));
        convertWithCasting = fromElement -> Byte.class.cast(fromElement).byteValue();
    .......
    }
  6. 然后将预填充的 Map 与业务逻辑绑定在一起,以获得完全功能的 Repositories。
    • 对于步骤 5) 和 6) 中列出的示例,以下伪代码将创建一个持有与特定类相关的 6 个属性的 TupleRepository
    private static final TupleRepository6<
                            Class,      // original Class of the concerned object
    
                            Boolean     // isPrmitiveType, when the original class is
                                        // primitive type, or it is array of primitive type
                            , Object    // default value of the concerned class
                            , Class     // equivalent class: could be the wrapper
                                        // if original class is primitive,
                                        // or primitive type if original class is wrapper
                            , Function<Object, Object>  // convert the value of original class
                                                        // to equivalent class parallelly
                            , Function<Object, Object>  // convert the value of original class
                                                        // to equivalent class in serial
                            , Function<Object, Object>  // convert the value of original class
                                                        // to equivalent class either 
                                                        // parallelly or serially
                    > baseTypeConverters = TupleRepository6.fromKey(
            new HashMap(){{
               ...
            }},
            null,
            TypeHelper::makeBaseTypeConverters
    );
  7. 最后,为了暴露任何类的属性/运算符,需要创建许多访问器方法。
    • 这些方法使用户更容易使用 TupleRepository 相关的属性/运算符。
    • 使存储值 Tuple 的任何元素的检索对最终用户透明。
    • 对于上述 TupleRepository 示例,定义了以下方法:
      • Boolean isPrimitive(Class clazz) 通过检索与类关联的值 Tuple 的第一个元素:对于 int.classchar[].class 返回 true,但对于 String.classDouble[].class 返回 false

      • Object getDefaultValue(Class clazz) 通过检索与类关联的值 Tuple 的第二个元素:对于 int.class 返回 0,对于 boolean.class 返回 false,对于 char[].class 返回 null(或 new char[0]),对于 Byte[][].class 返回 null(或 new Byte[0][]),这取决于标志 TypeHelper.EMPTY_ARRAY_AS_DEFAULT 是否设置为 true

      • Class getEquivalentClass(Class clazz) 通过检索与类关联的值 Tuple 的第三个元素:对于 Integer.class 返回 int.class,对于 char[].class 返回 Character[].class
      • 然后,其他 3 个访问器 Function<Object, Object> getToEquivalentParallelConverter(Class clazz)Function<Object, Object> getToEquivalentSerialConverter(Class clazz)Function<Object, Object> getToEquivalentConverter(Class clazz) 被其他方法包装,例如 TypeHelperObject toEquivalent(Object obj),作为非常实用的工具:int[] 实例将被转换为 Integer[] 实例,Character[][] 实例将被转换为 char[][] 实例。
      • 这些方法可以进一步封装以获得更友好的签名。例如,TypeHelper 的上述 Object toEquivalent(Object obj) 用于支持 ArrayHelper 的以下方法:
        • Boolean[] toObject(boolean[] values)
        • Byte[] toObject(byte[] values)
        • Character[] toObject(char[] values)
        • ...
        • boolean[] toPrimitive(Boolean[] values)
        • byte[] toPrimitive(Byte[] values)

因此,TupleRepository 类可以用作一个平台,支持高阶函数创建多个运算符,并具有以下优点:

  • 对于任何一组输入参数,评估只发生一次,即可获得一批运算符。
  • 消除广泛使用但仍不足以涵盖复杂场景的 if{}else{} 循环。
  • 与一组输入相关的运算符可以在评估期间进行优化:与这些输入相关的一些属性/方法将被一次检索并直接与生成的运算符合并。
  • 对评估值的任何元素进行强类型访问。
  • 嵌入式异常处理。
  • 易于扩展或更新。

看点

TypeHelper 和其他辅助类中提供了许多强大的实用工具,它们可以提供帮助。

历史

  • 2018年5月10日:初始版本
© . All rights reserved.