在本文中 ,我们创建了非常简单的hello-world生成器,以介绍框架以及通常如何生成生成器。 在本文中,我们将研究访问器生成器,它是在Java :: Geci的核心模块中定义的,它是商业级的,而不是仅用于演示的生成器。 即使生成器是商业级的,使用框架的服务,生成器也具有简单的代码,因此可以在文章中表示。
访问器生成器有什么作用
访问器是设置器和获取器。 当一个类有很多字段并且我们希望帮助封装时,我们将这些字段声明为private
字段并创建setter和getter,每个字段一对,可以设置该字段的值(setter)并获取该字段的值(吸气剂)。 请注意,与许多初中生认为的相反,创建setter和getter并不是单独封装,而是可能是进行正确封装的工具。 同时请注意,它也可能不是正确封装的工具。 您可以在“ Joshua Bloch:有效的Java第三版”项目16中了解更多信息。
仔细阅读它。 该书说它是为Java 9更新的。该Java版本包含模块系统。 Item 16一章中没有提及,甚至该版本仍然说要使用带有setter和getter的私有成员作为公共类,在Java 9的情况下,这也意味着模块中不会导出的包中的类。
许多开发人员认为,设置者和获取者本质上是邪恶的,并且是不良设计的标志。 不要犯错! 他们不主张直接使用原始字段。 那会更糟。 他们认为您应该以更加面向对象的思维方式进行编程。 在我看来,它们是正确的,并且按照我的专业实践,我必须使用很多类来维护旧版应用程序,这些类使用包含setter和getter的旧版框架来维护旧版应用程序,这些框架是应用程序周围的编程工具所需的。 理论是一回事,现实生活是另一回事。 除非我们在添加新字段时忘记执行它们,否则不同的集成开发环境和许多其他工具(例如,生成器和获取器)会为我们生成。
设置器是一种方法,其参数与字段的类型相同,并返回void
。 (Aka不返回任何值。)按照约定set
的名称,并使用首字母大写的字段名称。 对于字段businessOwner
,设置者通常是setBusinessOwner
。 设置器将字段的值设置为设置器的参数的值。
getter也是一种不带任何参数但返回参数值的方法,因此它的返回类型与字段的类型相同。 按照惯例,getter的名称是get
,再次是大写的字段名称。 这样,获取者将是getBusinessOwner
。
在的情况下, boolean
或Boolean
类型fiels吸气剂具有is
前缀,所以isBusinessOwner
还可以的情况下,该领域是一些布尔类型的有效名称。
访问器会为其必需的所有字段生成setter和getter。
如何生成访问器
访问器生成器必须为该类的某些字段生成代码。 该生成器是Java :: Geci中过滤字段生成器的理想候选者。 过滤后的字段生成器扩展了AbstractFilteredFieldsGenerator
类,并且它的process()
方法为每个过滤后的字段调用一次。 除了几周前在文章中看到的常规Source
和CompoundParams
参数之外,该方法还将Field
作为第三个参数。
类AbstractFilteredFieldsGenerator
类使用配置参数filter
来过滤字段。 这样,对于扩展该类的每个生成器,要考虑的字段的选择都是相同的,并且生成器不必关心字段过滤:它是为它们完成的。
生成器代码的主要部分如下:
public class Accessor extends AbstractFilteredFieldsGenerator { ... @Override public void process(Source source, Class<?> klass, CompoundParams params, Field field) throws Exception { final var id = params.get( "id" ); source.init(id); var isFinal = Modifier.isFinal(field.getModifiers()); var name = field.getName(); var fieldType = GeciReflectionTools.typeAsString(field); var access = check(params.get( "access" , "public" )); var ucName = cap(name); var setter = params.get( "setter" , "set" + ucName); var getter = params.get( "getter" , "get" + ucName); var only = params.get( "only" ); try (var segment = source.safeOpen(id)) { if (!isFinal && ! "getter" .equals(only)) { writeSetter(name, setter, fieldType, access, segment); } if (! "setter" .equals(only)) { writeGetter(name, getter, fieldType, access, segment); } } } }
省略号处的代码包含更多方法,我们将在后面介绍。 第一个调用是获取参数id
。 这是一个特殊参数,如果未定义,则默认params.get("id")
返回是生成器的助记符。 这是唯一具有这样的全局默认值的参数。
对source.init(id)
的调用可确保即使生成器未向该段写入任何内容,该段也将被视为“已触摸”。 在某些情况下可能会发生这种情况,并且在编写生成器时,对于生成器要写入的任何段调用source.init(id)
不会受到伤害。
该代码查看实际字段以检查该字段是否为最终字段。 如果该字段为final,则必须在创建对象时获取值,此后,设置器将无法对其进行修改。 在这种情况下,将仅为该字段创建一个吸气剂。
setter / getter生成器需要的下一项功能是字段名称,以及字段类型的字符串表示形式。 静态实用程序方法GeciReflectionTools.typeAsString()
是框架中提供此功能的便捷工具。
可选的配置参数access
将进入相同名称的变量,并在setter和getter的access修饰符需要不同于public
。 默认值是public
,它被定义为方法params.get()
的第二个参数。 方法check()
是生成器的一部分。 它检查该改性剂是正确并防止在大多数情况下产生的语法差错代码(例如:创建setter和吸气与访问修饰符pritected
)。 我们将在一段时间后研究该方法。
接下来的事情是getter和setter的名称。 默认情况下是set/get
+字段的大写名称,但也可以由配置参数setter
和getter
定义。 这样,如果绝对需要,您可以拥有isBusinessOwner
。
最后一个配置参数only
是密钥。 如果代码指定only='setter'
或only='getter'
则仅生成setter或仅生成getter。
生成器要写入的段在try-with-resources块的开头打开,然后调用本地writeSetter
和writeGetter
方法。 有两种不同的方法可以从源对象打开细分。 一个正在调用open(id)
,另一个正在调用safeOpen(id)
。 第一种方法将尝试打开该段,如果在类源文件中未定义名称的段,则该方法将返回null
。 生成器可以检查无效性,并且如果已编程,则可以使用其他段名称。 另一方面,如果无法打开该段,则safeOpen()
会引发GeciException
。 这是更安全的版本,以避免以后生成器中出现空指针异常。 不是很好。
请注意,仅当字段不是final且未将only
配置键未配置为getter
(仅)时,才写入setter。
让我们看一下这两种方法。 毕竟,这些是真正生成代码的生成器的真正核心方法。
private static void writeGetter(String name, String getterName, String type, String access, Segment segment) { segment.write_r(access + " " + type + " " + getterName + "(){" ) .write( "return " + name + ";" ) .write_l( "}" ) .newline(); } private static void writeSetter(String name, String setterName, String type, String access, Segment segment) { segment.write_r(access + " void " + setterName + "(" + type + " " + name + "){" ) .write( "this." + name + " = " + name + ";" ) .write_l( "}" ) .newline(); }
这些方法获取字段的名称,访问者的名称,作为字符串的字段的类型,访问修饰符字符串以及必须将代码写入的Segment
。 代码生成器不会直接写入源文件。 框架提供的segment对象用于发送生成的代码,如果需要,框架会将编写的行插入源代码中。
该段的write()
, write_l()
和write_r()
方法可用于编写代码。 如果有多个参数,它们的工作方式与String.format
非常相似,但是它们也关心适当的制表。 当代码调用write_r()
该段将记住必须将其后的行列表在右边的四个空格处。 当代码调用write_l()
该段知道制表必须减少四个字符(即使对于实际的写入行也是如此)。 它们还处理多行字符串,以便将它们全部正确制成表格。
生成的代码也应该可读。
最终的重要方法是访问修饰符检查。
private static final Set<String> accessModifiers = Set.of( "public" , "private" , "protected" , "package" ); ... private String check( final String access) { if (!access.endsWith( "!" ) && !accessModifiers.contains(access)) { throw new GeciException( "'" +access+ "' is not a valid access modifier" ); } final String modifiedAccess; if ( access.endsWith( "!" )){ modifiedAccess = access.substring( 0 ,access.length()- 1 ); } else { modifiedAccess = access; } if ( modifiedAccess.equals( "package" )){ return "" ; } return modifiedAccess; }
进行此检查的目的是防止程序员错误地访问access修饰符。 它检查访问修饰符是private
(虽然我看不到这个的实际用例), protected
, public
还是package
。 最后一个转换为空字符串,因为包保护的访问是类方法的默认设置。 同时在配置中使用空字符串来表示程序包私有访问实际上是不可读的。
这样,如果配置pritected
包含拼写错误的代码,则代码生成器将抛出异常,并拒绝生成已知包含语法错误的代码。 另一方面,访问修饰符也可以更复杂。 在极少数情况下,该程序可能需要同步的吸气剂和吸气剂。 我们不会试图自动找出类似检查字段是否可变的内容,因为这是边界情况。 但是,生成器提供了克服有限语法检查的可能性,并且仅提供任何字符串作为访问修饰符即可。 如果访问修饰符字符串以感叹号结尾,则意味着使用生成器的程序员对访问修饰符的正确性承担全部责任,并且生成器将按原样使用它(当然没有感叹号)。
剩下的是mnemonic
和cap
方法:
private static String cap(String s) { return s.substring( 0 , 1 ).toUpperCase() + s.substring( 1 ); } @Override public String mnemonic() { return "accessor" ; }
框架使用mnemonic()
方法来识别需要此生成器服务的源,并将其用作配置参数id
的默认值。 所有生成器都应提供此功能。 另一种是cap
的是大写的字符串。 我不会解释它是如何工作的。
样品使用
@Geci ( "accessor filter='private | protected'" ) public class Contained1 { public void callMe() { } private final String apple = "" ; @Geci ( "accessors only='setter'" ) private int birnen; int packge; @Geci ( "accessor access='package' getter='isTrue'" ) protected boolean truth; @Geci ( "accessor filter='false'" ) protected int not_this; public Map<String,Set<Map<Integer,Boolean>>> doNothingReally( int a, Map b, Set<Set> set){ return null ; } //<editor-fold id="accessor" desc="setters"> //</editor-fold> }
该类使用Geci
注释进行注释。 参数为accessor filter='private | protected'
accessor filter='private | protected'
,定义将在此源文件上使用的生成器的名称并配置过滤器。 它说,我们需要私有和受保护字段的设置者和获取者。 逻辑表达式应为:“过滤字段是私有的还是受保护的”。
一些字段也有注释。 birnen
将得到的只是一个二传手, truth
setter和getter将被封装保护剂和吸气将被命名为isTrue()
字段not_this
不会获得setter或getter,因为在字段注释中覆盖了过滤器表达式,并说: false
永远不会为true
,生成器需要对其进行处理。
未注释字段apple
,它将根据类级别的配置进行处理。 它是私有的,因此将获得访问器,并且由于它是final
因此将仅获得吸气剂。
之间的代码
// <editor- fold id = "accessor" desc= "setters" > // < /editor-fold >
将包含生成的代码。 (您必须运行代码才能看到它,我没有在这里复制它。)
摘要
在本文中,我们研究了一个生成器,它是Java :: Geci框架中真实的商业级生成器。 遍历代码,我们讨论了代码的工作方式,还讨论了编写代码生成器的其他一些更一般的方面。 下一步是使用Java :: Geci作为测试依赖项来启动一个项目,使用访问器生成器而不是IDE代码生成器(这使您忘记重新执行setter getter生成),然后也许可以创建您的拥有自己的生成器,而不仅仅是setter和getter。
翻译自: https://www.javacodegeeks.com/2019/06/generating-setters-and-getters-using-javageci.html