原文作者:kotlin.christmas/
发布时间:2020年12月14日
如果你能自动编写代码,那不是很好吗。嗯,这就是我们今天要看的。元编程,代码生成,或者简而言之;编写kotlin代码,生成更多的kotlin代码🤯。
免责声明:本文专注于源代码生成,不对byte buddy、asm、javaassist或其他进行任何比较。
今天我们来看看kotlinpoet和代码生成提供的一些可能性。
在它最简单的形式下,代码生成可以简单地成为任何程序,当执行时吐出有效的源代码。举个例子,一个简单的函数,返回一个打印语句。
fun createHello(who: String) = "println(\"Hello, $who\")"
createHello("world") // return println("Hello, world")
这里没有什么了不起的事情发生。当它被执行时,它打印出了一些有效的代码,我们以后可能会用到。但是,让我们把它变得更复杂,同时介绍一下今天的明星Kotlinpoet。
val packageName = "com.christmas.kotlin"
val className = "Code"
val cls = TypeSpec
.classBuilder(className)
val file = FileSpec.get(packageName, cls.build())
println(file)
// ---- Returns ----
package com.christmas.kotlin
public class Code
Kotlinpoet在上面的例子中并没有做什么,但我们会看到它将进一步帮助我们。FileSpec和TypeSpec是kotlinpoet对文件和类的表示,一旦我们有了TypeSpec,我们就可以添加更多的东西,比如属性、构造函数和其他功能。
为了给我们的代码类添加一个简单的属性,我们使用定义一个PropertySpec并把它添加到我们已经存在的TypeSpec中。
val cls = TypeSpec
.classBuilder(className)
.addProperty(PropertySpec
// MUTABLE_LIST and STRING are predefined TypeName's included in kotlinpoet
.builder("statements", MUTABLE_LIST.parameterizedBy(STRING))
.initializer("mutableListOf()")
.build()
)
// ---- Returns ----
package com.christmas.kotlin
import kotlin.String
import kotlin.collections.MutableList
public class Code {
public val statements: MutableList<String> = mutableListOf()
}
突然间,我们的类有了一个属性和一些导入语句。Kotlinpoet一直在跟踪哪些导入语句是需要的,以使我们的代码工作,而不需要我们明确编程。这时的复杂性有些有限,但即使现在我们也能从库中得到一些帮助。
在我们的类中添加一个函数,也是以类似的方式继续。
val cls = TypeSpec.classBuilder(className)
// .addProperty(...)
.addFunction(FunSpec
.builder("addStatement")
.returns(ClassName(packageName, className))
.addParameter("statement", STRING)
.addStatement("this.statements += statement")
.addStatement("return this")
.build()
)
// ---- Returns ----
package com.christmas.kotlin
import kotlin.String
import kotlin.collections.MutableList
public class Code {
public val statements: MutableList<String> = mutableListOf()
public fun addStatement(statement: String): Code {
this.statements += statement
return this
}
}
Kotlinpoet正确地识别出Code类在我们的类中是可见的,因此没有添加导入或使用该类的全称。然而,如果我们把我们的函数的返回类型改为LONG,它就会把它作为一个导入包括在内。因此,它也会产生不能编译的代码,因为我们的函数的返回类型是this,而不是Long,whoopsi。幸运的是,我们正在生成kotlin源代码,而kotlin编译器可能会告诉我们有问题。
为了得到一个小的类,这似乎是相当多的工作。虽然这可能是真的,但它也为自动处理一些非常酷的东西提供了可能性。通过查看mvnrepository上kotlinpoet的使用情况,我们可以看到它被Arrow(Arrow-Meta)、Microsoft Thrifty编译器插件、apollo-compiler和ExpediaGroup的graphql-kotlin使用。
通过www.DeepL.com/Translator(免费版)翻译