在本文中,我将描述如何使用Java :: Geci生成器Repeated
以克服泛型不能为原始类型的Java语言不足。 该示例是对Apache Commons Lang库的建议扩展。
介绍
当您复制粘贴代码时,您做错了什么。 至少那是感知。 您必须创建更通用的代码结构,以便可以多次使用不同的参数而不是类似的代码。
这并非总是如此。 有时您必须重复一些代码,因为您使用的语言(尚)不支持该问题所需的功能。
这太抽象了。 让我们看一个特定的示例,以及如何使用在Java :: Geci框架中运行的Repeated
源生成器来管理它。
问题
Apache Commons Lang库中的org.apache.commons.lang3.Functions
类定义了一个内部接口FailableFunction
。 这是一个通用接口,定义为
@FunctionalInterface public interface FailableFunction<I, O, T extends Throwable> { /** * Apply the function. * @param pInput the input for the function * @return the result of the function * @throws T if the function fails */ O apply(I pInput) throws T; }
这本质上与Function<I,O>
,后者将I
转换为O
但是由于接口是有故障的,因此它也可能引发类型T
的异常。
新的需要是
public interface Failable<I>Function<O, T extends Throwable>
每个<I>
原语值的面。 问题是泛型在Java中还不能是原始的,因此我们应该为每种原始类型分离接口,例如
@FunctionalInterface public interface FailableCharFunction<O, T extends Throwable> { O apply( char pInput) throws T; } @FunctionalInterface public interface FailableByteFunction<O, T extends Throwable> { O apply( byte pInput) throws T; } @FunctionalInterface public interface FailableShortFunction<O, T extends Throwable> { O apply( short pInput) throws T; } @FunctionalInterface public interface FailableIntFunction<O, T extends Throwable> { O apply( int pInput) throws T; } ... and so on ...
这是许多非常相似的方法,可以很容易地由模板描述,然后由某些代码生成工具生成。
使用Java :: Geci进行模板处理
Java :: Geci框架附带了许多现成的生成器。 其中之一是功能强大的Repeated
生成器,正是出于这个目的。 如果必须重复使用可能的参数的代码,则可以定义模板,值和Repeated
将生成解析模板参数的代码。
向POM添加依赖项
我们要做的第一件事是将Java :: Geci依赖项添加到pom.xml
文件中。 由于Apache Commons Language仍基于Java 8,因此我们必须使用Java :: Geci 1.2.0的Java 8反向端口:
< dependency > < groupId >com.javax1.geci</ groupId > < artifactId >javageci-core</ artifactId > < version >1.2.0</ version > < scope >test</ scope > </ dependency >
注意,依赖项的范围是test
。 Repeated
生成器可以方便地使用,而字节码中不会保留任何Geci注释,因此它们是编译时的依赖项。 实际上,所有生成器都可以使用而无需注释,因此不需要任何编译依赖关系,而这对于生产来说是额外的依赖关系。 在Repeated
的情况下,这甚至很容易做到。
运行发电机的单元测试
我们要做的第二件事是创建一个将执行生成器的单元测试。 Java :: Geci生成器在单元测试阶段运行,因此它们可以使用反射以及实际的源代码访问已编译的代码。 如果生成的任何代码与源文件中已有的代码不同,则测试将失败,并且应再次执行生成过程。 由于发生器是(应该)幂等的,因此测试第二次不应失败。
以我的经验,很遗憾,此工作流程会影响开发人员的行为。 运行测试/失败,再次运行! 这是一个糟糕的循环。 有时我碰巧发现自己不是在代码生成器发生故障的情况下重新执行单元测试。 但是,这就是Java :: Geci的工作方式。
有一些关于Java :: Geci工作流程的文章
- 您的代码是多余的,请继续使用!
- 保持JavaDoc为最新
- 将对象转换为映射并返回
- 反射选择器表达式
- 使用Java :: Geci生成Getter和Setters
- 创建一个Java :: Geci生成器
- 如何生成源代码
因此,在此我不会重复总体架构及其工作流程。
单元测试如下:
@Test void generatePrimitiveFailables() throws Exception { final Geci geci = new Geci(); Assertions.assertFalse(geci.source(Source.maven().mainSource()) .only( "Functions" ) .register(Repeated.builder() .values( "char,byte,short,int,long,float,double,boolean" ) .selector( "repeated" ) .define((ctx, s) -> ctx.segment().param( "Value" , CaseTools.ucase(s))) .build()) .generate(), geci.failed()); }
调用source()
, register()
和only()
配置框架。 此配置告诉框架使用项目主Java src
目录中的源文件,并仅使用文件名"Functions"
。 在我们调用generate()
开始代码生成之前,对register()
的调用会register()
Repeated
生成器实例。
生成器实例本身是使用内置的生成器创建的,该生成器使我们可以配置生成器。 在这种情况下,对values()
的调用定义了我们要重复使用模板的逗号分隔的值列表(稍后在注释中的代码中定义)。 对selector()
的调用定义了此代码重复代码的标识符。 单个源文件可能包含多个模板。 每个模板可以使用不同的值列表进行处理,结果将插入到不同的输出段中,并插入源文件中。 在这种情况下,仍然只有一个这样的代码生成模板,它必须用一个名称标识,并且该名称也必须在editor-fold
部分中使用,该部分是生成代码的占位符。
实际使用生成器的名称有两个作用。 其一是它标识了编辑器折叠段和模板。 另一个是框架将看到带有该标识符的编辑器折叠段,并且它将识别出该源文件需要该生成器的注意。 另一种可能性是在类中添加@Repeated
或@Geci("repeated")
批注。
如果标识符是别的东西并且不repeated
那么生成器“ Repeated
将不会触及源代码,或者我们需要将另一个段标识为“ repeated
,除了触发生成器之外,它实际上不会被使用。
对define()
的调用define()
了一个BiConsumer
,该BiConsumer
获取了上下文引用和实际值。 在这种情况下, BiConsumer
计算大写值,并将其放入与名称Value
相关联的实际细分参数集中。 默认情况下,实际值与名称value
关联,并且传递给方法define()
的BiConsumer
可以定义和注册其他参数。 在这种情况下,它将添加新值
value Value char --> Char byte --> Byte short --> Short int --> Int long --> Long float --> Float double --> Double boolean --> Boolean
源代码
第三件事是在源文件中准备模板和输出段。
输出段的准备非常简单。 它只是一个编辑器折叠:
//<editor-fold id="repeated"> //</editor-fold>
生成的代码将自动插入两行之间,并且编辑器(Eclipse,IntelliJ或NetBeans)将允许您关闭折叠。 您不想编辑此代码:它已生成。
该模板将如下所示:
/* TEMPLATE repeated @FunctionalInterface public interface Failable{{Value}}Function<O, T extends Throwable> { O apply({{value}} pInput) throws T; } */
代码生成器找到模板的开始,以查找与/* TEMPLATE name
格式匹配的行,并收集连续的行,直到注释的结尾。
该模板使用小胡子模板的占位符格式,即用双括号括起来的值的名称。 双括号在Java中很少见。
当我们运行单元测试时,它将生成我在本文开头已经列出的代码。 (然后,它当然会失败:修改了源代码,然后再次编译。)
摘要和总结
最重要的要点和警告:源代码生成是旨在弥补编程语言不足的一种工具。 不要使用代码生成来修正语言的不足,而不仅仅是语言的不足,技能或知识的不足。 生成代码的简单方法不是生成不必要的冗余代码的借口。
另一个收获是,在Java中使用此生成器非常容易。 该功能可以与Java所没有的C预处理器相媲美,而且效果良好。 需要时使用它。 即使依赖项的设置和单元测试的开销可能很小,但可维护性通常会偿还此成本。
翻译自: https://www.javacodegeeks.com/2019/09/handling-repeated-code-automatically.html