前言
最近写了一个IDL转Kotlin Model Class的Android Studio插件,用到了KotlinPoet这个Square开发的开源库,KotlinPoet是用于生成.kt
源文件的Kotlin和JavaAPI。在进行注释处理开发或者处理元数据文件时(例如数据库schemas、protocol、idl)KotlinPoet很有用。开发过程中,发现KotlinPoet的手册没人翻译,也没有一个系统性的教程。所以就对KotlinPoet的手册进行了翻译和收拾。
下载和运用
能够经过下载并引用最新的jar包或经过Maven依赖:
<dependency>
<groupId>com.squareup</groupId>
<artifactId>kotlinpoet</artifactId>
<version>[version]</version>
</dependency>
或经过gradle
依赖:
implementation("com.squareup:kotlinpoet:[version]")
最新的开发版本能够在官网中查找。
第一个比如
用咱们最了解的HelloWorld
举例:
val greeterClass = ClassName("", "Greeter")
val file = FileSpec.builder("", "HelloWorld")
.addType(
TypeSpec.classBuilder("Greeter")
.primaryConstructor(
FunSpec.constructorBuilder()
.addParameter("name", String::class)
.build()
)
.addProperty(
PropertySpec.builder("name", String::class)
.initializer("name")
.build()
)
.addFunction(
FunSpec.builder("greet")
.addStatement("println(%P)", "Hello, $name")
.build()
)
.build()
)
.addFunction(
FunSpec.builder("main")
.addParameter("args", String::class, VARARG)
.addStatement("%T(args[0]).greet()", greeterClass)
.build()
)
.build()
file.writeTo(System.out)
会生成:
class Greeter(val name: String) {
fun greet() {
println("""Hello, $name""")
}
}
fun main(vararg args: String) {
Greeter(args[0]).greet()
}
为了最大化可移植性和兼容性,如果不指定润饰符,KotlinPoet生的类、办法和变量都带有public。为简练起见,示例中省掉了润饰符。
文件(File)
KotlinPoet顶用FileSpec.builder
创立文件,文件是最终代码输出的载体,能够增加一些顶级的目标例如类,objects,办法,特点,类型别号。
val file = FileSpec.builder("", "HelloWorld")
.addComment()
.addAnnotation()
.addImport()
.addProperty()
.addType()
.addFunction()
.build()
文件创立之后,能够用许多办法输出:
fun writeTo(out: Appendable)
fun writeTo(directory: Path)
fun writeTo(directory: File): Unit = writeTo(directory.toPath())
fun writeTo(filer: Filer)
fun toString(): String = buildString { writeTo(this) }
输出的时分,会按照注释、注解、包名、import、其他成员这个次序输出。
类(Type)
KotlinPoet中运用TypeSpec.classBuilder
来创立类、object、接口和枚举,这里咱们只说类,其他的后面会提到。类中能够增加注释、注解、特点、办法、润饰符等候:
TypeSpec.classBuilder("Greeter")
.addKdoc()
.addAnnotations()
.addProperty()
.addFunction()
.addModifiers()
.build()
能够运用superclass
指定父类,运用addSuperinterface
指定实现的接口:
TypeSpec.classBuilder("Greeter")
.superclass(BaseResponse::class)
.addSuperinterface(KeepElement::class)
.build()
办法(Functions)
KotlinPoet没有给代码块结构模型,没有表达式类、语句类或语法树节点。KotlinPoet直接运用字符串作为代码块,所以方体运用带占位符(KotlinPoet中的占位符用法比较复杂所以咱们在下中讨论)的字符串构成,能够用Kotlin的多行字符串改进代码风格:
val main = FunSpec.builder("main")
.addCode("""
|var total = 0
|for (i in 0 until 10) {
| total += i
|}
|""".trimMargin())
.build()
会生成:
fun main() {
var total = 0
for (i in 0 until 10) {
total += i
}
}
还能够运用beginControlFlow
和endControlFlow
来协助处理换行符、大括号和缩进:
val main = FunSpec.builder("main")
.addStatement("var total = 0")
.beginControlFlow("for (i in 0 until 10)")
.addStatement("total += i")
.endControlFlow()
.build()
能够进一步把循环的范围改成能够装备的:
private fun computeRange(name: String, from: Int, to: Int, op: String): FunSpec {
return FunSpec.builder(name)
.returns(Int::class)
.addStatement("var result = 1")
.beginControlFlow("for (i in $from until $to)")
.addStatement("result = result $op i")
.endControlFlow()
.addStatement("return result")
.build()
}
然后调用computeRange("multiply10to20", 10, 20, "*")
,会生成:
fun multiply10to20(): kotlin.Int {
var result = 1
for (i in 10 until 20) {
result = result * i
}
return result
}
上面比如中的办法是有办法体的。咱们能够用KModifier.ABSTRACT
生成没有办法体的笼统办法。当然,笼统办法有必要写在笼统类或接口里。
val flux = FunSpec.builder("flux")
.addModifiers(KModifier.ABSTRACT, KModifier.PROTECTED)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(KModifier.ABSTRACT)
.addFunction(flux)
.build()
会生成:
abstract class HelloWorld {
protected abstract fun flux()
}
咱们还能够用FunSpec.Builder
来给办法增加其他的润饰符,比如KModifier.INLINE
。除此之外,FunSpec.Builder
还能够用来装备办法的参数、可变参数、注释、注解、类型变量、返回类型、接收器类型等等。
扩展办法(Extension functions)
用FunSpec.Builder
指定接收器类型就能生成扩展办法。
val square = FunSpec.builder("square")
.receiver(Int::class)
.returns(Int::class)
.addStatement("var s = this * this")
.addStatement("return s")
.build()
会生成:
fun Int.square(): Int {
val s = this * this
return s
}
Kotlin中的单行办法
KotlinPoet会把以return
最初的办法输出成单行办法。
val abs = FunSpec.builder("abs")
.addParameter("x", Int::class)
.returns(Int::class)
.addStatement("return if (x < 0) -x else x")
.build()
会生成:
fun abs(x: Int): Int = if (x < 0) -x else x
参数的默许值
如果期望给办法的参数增加默许值。例如,期望给办法的参数b
增加默许值为0。
fun add(a: Int, b: Int = 0) {
print("a + b = ${a + b}")
}
能够用ParameterSpec.builder
来装备参数的defaultValue()
默许值。
FunSpec.builder("add")
.addParameter("a", Int::class)
.addParameter(
ParameterSpec.builder("b", Int::class)
.defaultValue("%L", 0)
.build()
)
.addStatement("print("a + b = ${a + b}")")
.build()
空格默许换行!
当代码行或许超越长度约束的时分,KotlinPoet会用换行符替换代码块中的空格。例如下面的办法:
val funSpec = FunSpec.builder("foo")
.addStatement("return (100..10000).map { number -> number * number }.map { number -> number.toString() }.also { string -> println(string) }")
.build()
会生成下面的代码,能够看出also后面就换行了:
fun foo() = (100..10000).map { number -> number * number }.map { number -> number.toString() }.also
{ string -> println(string) }
这不是期望的成果also
和后面的{
需求在同一行,不过咱们能够用符号来声明完全不想被替换的空格。看下面的比如:
val funSpec = FunSpec.builder("foo")
.addStatement("return (100..10000).map{ number -> number * number }.map{ number -> number.toString() }.also{ string -> println(string) }")
.build()
现在将发生以下成果:
fun foo() = (100..10000).map { number -> number * number }.map { number ->
number.toString()
}.also { string -> println(string) }
结构办法(Constructors)
FunSpec
也能够用于结构函数,运用FunSpec.constructorBuilder
:
val flux = FunSpec.constructorBuilder()
.addParameter("greeting", String::class)
.addStatement("this.%N = %N", "greeting", "greeting")
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addProperty("greeting", String::class, KModifier.PRIVATE)
.addFunction(flux)
.build()
会生成:
class HelloWorld {
private val greeting: String
constructor(greeting: String) {
this.greeting = greeting
}
}
结构办法和其他办法的生成办法是一样的。KotlinPoet会把结构办法放在其他办法的最前面。
当咱们需求指定主结构办法的时分,运用primaryConstructor()
:
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.primaryConstructor(flux)
.addProperty("greeting", String::class, KModifier.PRIVATE)
.build()
会生成:
class HelloWorld(greeting: String) {
private val greeting: String
init {
this.greeting = greeting
}
}
不过这么生成的代码太冗余了,一般Kotlin中咱们会集并同名的主结构办法参数和特点。但KotlinPoet默许不这么干,需求咱们运用initializer
办法,告知KotlinPoet特点会被主结构办法初始化:
val flux = FunSpec.constructorBuilder()
.addParameter("greeting", String::class)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.primaryConstructor(flux)
.addProperty(
PropertySpec.builder("greeting", String::class)
.initializer("greeting")
.addModifiers(KModifier.PRIVATE)
.build()
)
.build()
现在会生成:
class HelloWorld(private val greeting: String)
参数(Parameters)
能够用ParameterSpec.builder()
或者直接用FunSpec.addParameter()
来声明一个参数:
val android = ParameterSpec.builder("android", String::class)
.defaultValue(""pie"")
.build()
val welcomeOverlords = FunSpec.builder("welcomeOverlords")
.addParameter(android)
.addParameter("robot", String::class)
.build()
会生成:
fun welcomeOverlords(android: String = "pie", robot: String) {
}
如果参数有注解等额定特点,那就有必要用ParameterSpec.builder()
这种办法。
特点(Properties)
和参数一样,特点能够运用PropertySpec.builder
或TypeSpec.addProperty
创立:
val android = PropertySpec.builder("android", String::class)
.addModifiers(KModifier.PRIVATE)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addProperty(android)
.addProperty("robot", String::class, KModifier.PRIVATE)
.build()
会生成:
class HelloWorld {
private val android: String
private val robot: String
}
如果需求给特点设定KDoc、注释或初始值,需求运用PropertySpec.builder
。初始值运用initializer
和String.format()
:
val android = PropertySpec.builder("android", String::class)
.addModifiers(KModifier.PRIVATE)
.initializer("%S + %L", "Oreo v.", 8.1)
.build()
会生成:
private val android: String = "Oreo v." + 8.1
PropertySpec.Builder
默许会生成val
的特点,能够经过mutable()
把特点设置成var
:
val android = PropertySpec.builder("android", String::class)
.mutable()
.addModifiers(KModifier.PRIVATE)
.initializer("%S + %L", "Oreo v.", 8.1)
.build()
内联特点(Inline properties)
需求特别提一下KotlinPoet对内联特点的处理:
val android = PropertySpec.builder("android", String::class)
.mutable()
.addModifiers(KModifier.INLINE)
.build()
上面的代码会抛出反常:
java.lang.IllegalArgumentException: KotlinPoet doesn't allow setting the inline modifier on
properties. You should mark either the getter, the setter, or both inline.
这是因为被inline
润饰的特点应该至少有一个getter办法,会被编译器内联:
val android = PropertySpec.builder("android", String::class)
.mutable()
.getter(
FunSpec.getterBuilder()
.addModifiers(KModifier.INLINE)
.addStatement("return %S", "foo")
.build()
)
.build()
增加了getter办法之后,成果如下:
var android: kotlin.String
inline get() = "foo"
如果咱们想在上面的特点中增加一个非内联的setter怎么办,咱们能够:
val android = PropertySpec.builder("android", String::class)
.mutable()
.getter(
FunSpec.getterBuilder()
.addModifiers(KModifier.INLINE)
.addStatement("return %S", "foo")
.build()
)
.setter(
FunSpec.setterBuilder()
.addParameter("value", String::class)
.build()
)
.build()
生成的成果在预期之中:
var android: kotlin.String
inline get() = "foo"
set(`value`) {
}
最终如果咱们又期望运用KModifier.INLINE
把setter变成inline办法,KotlinPoet能够做到生成包装好的代码:
inline var android: kotlin.String
get() = "foo"
set(`value`) {
}
删去getter办法或setter办法的润饰符会把这种包装再翻开。
之所以KotlinPoet不允许inline
直接符号特点而是符号getter/setter办法的原因是,如果KotlinPoet允许inline
直接符号特点,那么每次getter/setter办法状况发生变化的时分,程序员有必要手动增加/删去润饰符才能取得正确且可编译的输出。
接口(Interfaces)
KotlinPoet中运用TypeSpec.interfaceBuilder
定义接口,留意定义接口办法时,有必要有ABSTRACT
的润饰符:
val helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
.addProperty("buzz", String::class)
.addFunction(
FunSpec.builder("beep")
.addModifiers(KModifier.ABSTRACT)
.build()
)
.build()
生成成果如下,留意ABSTRACT
的润饰符在生成代码时被省掉了:
interface HelloWorld {
val buzz: String
fun beep()
}
Kotlin在1.4版本加入了fun interface
语法,增加了对函数式(SAM)接口的支撑。在KotlinPoet 中能够运用TypeSpec.funInterfaceBuilder()
创立函数式接口:
val helloWorld = TypeSpec.funInterfaceBuilder("HelloWorld")
.addFunction(
FunSpec.builder("beep")
.addModifiers(KModifier.ABSTRACT)
.build()
)
.build()
// Generates...
fun interface HelloWorld {
fun beep()
}
目标声明(Object)
KotlinPoet中运用TypeSpec.objectBuilder
声明Object:
val helloWorld = TypeSpec.objectBuilder("HelloWorld")
.addProperty(
PropertySpec.builder("buzz", String::class)
.initializer("%S", "buzz")
.build()
)
.addFunction(
FunSpec.builder("beep")
.addStatement("println(%S)", "Beep!")
.build()
)
.build()
KotlinPoet相同也支撑用TypeSpec.companionObjectBuilder
声明伴生目标,并用addType()
加到类中:
val companion = TypeSpec.companionObjectBuilder()
.addProperty(
PropertySpec.builder("buzz", String::class)
.initializer("%S", "buzz")
.build()
)
.addFunction(
FunSpec.builder("beep")
.addStatement("println(%S)", "Beep!")
.build()
)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addType(companion)
.build()
伴生目标也能够指定名称,如果不指定的话会运用默许名称Companion
。
枚举(Enums)
KotlinPoet运用于TypeSpec.enumBuilder
创立枚举类型,并运用addEnumConstant()
来增加枚举值:
val helloWorld = TypeSpec.enumBuilder("Roshambo")
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build()
会生成:
enum class Roshambo {
ROCK,
SCISSORS,
PAPER
}
KotlinPoet支撑Kotlin中花哨的枚举,支撑枚举值重写办法或调用超类的结构函数。示例:
val helloWorld = TypeSpec.enumBuilder("Roshambo")
.primaryConstructor(
FunSpec.constructorBuilder()
.addParameter("handsign", String::class)
.build()
)
.addEnumConstant(
"ROCK", TypeSpec.anonymousClassBuilder()
.addSuperclassConstructorParameter("%S", "fist")
.addFunction(
FunSpec.builder("toString")
.addModifiers(KModifier.OVERRIDE)
.addStatement("return %S", "avalanche!")
.returns(String::class)
.build()
)
.build()
)
.addEnumConstant(
"SCISSORS", TypeSpec.anonymousClassBuilder()
.addSuperclassConstructorParameter("%S", "peace")
.build()
)
.addEnumConstant(
"PAPER", TypeSpec.anonymousClassBuilder()
.addSuperclassConstructorParameter("%S", "flat")
.build()
)
.addProperty(
PropertySpec.builder("handsign", String::class, KModifier.PRIVATE)
.initializer("handsign")
.build()
)
.build()
会生成:
enum class Roshambo(private val handsign: String) {
ROCK("fist") {
override fun toString(): String = "avalanche!"
},
SCISSORS("peace"),
PAPER("flat");
}
匿名内部类(Anonymous Inner Classes)
上面枚举的代码中,咱们运用了TypeSpec.anonymousClassBuilder()
来声明匿名内部类,也能够在代码块顶用%L
引用:
val comparator = TypeSpec.anonymousClassBuilder()
.addSuperinterface(Comparator::class.parameterizedBy(String::class))
.addFunction(
FunSpec.builder("compare")
.addModifiers(KModifier.OVERRIDE)
.addParameter("a", String::class)
.addParameter("b", String::class)
.returns(Int::class)
.addStatement("return %N.length - %N.length", "a", "b")
.build()
)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addFunction(
FunSpec.builder("sortByLength")
.addParameter("strings", List::class.parameterizedBy(String::class))
.addStatement("%N.sortedWith(%L)", "strings", comparator)
.build()
)
.build()
会生成如下的类:
class HelloWorld {
fun sortByLength(strings: List<String>) {
strings.sortedWith(object : Comparator<String> {
override fun compare(a: String, b: String): Int = a.length - b.length
})
}
}
能够运用TypeSpec.Builder
.addSuperclassConstructorParameter()
办法传递超类结构函数的参数。
注解(Annotations)
不带参数的,简略的注解能够直接用addAnnotation()
:
val test = FunSpec.builder("test string equality")
.addAnnotation(Test::class)
.addStatement("assertThat(%1S).isEqualTo(%1S)", "foo")
.build()
在办法上生成了@Test
注解:
@Test
fun `test string equality`() {
assertThat("foo").isEqualTo("foo")
}
AnnotationSpec.builder()
能够用addMember
设置注解的参数:
val logRecord = FunSpec.builder("recordEvent")
.addModifiers(KModifier.ABSTRACT)
.addAnnotation(
AnnotationSpec.builder(Headers::class)
.addMember("accept = %S", "application/json; charset=utf-8")
.addMember("userAgent = %S", "Square Cash")
.build()
)
.addParameter("logRecord", LogRecord::class)
.returns(LogReceipt::class)
.build()
生成的注解会带着accept
和userAgent
特点:
@Headers(
accept = "application/json; charset=utf-8",
userAgent = "Square Cash"
)
abstract fun recordEvent(logRecord: LogRecord): LogReceipt
注解的特点也能够是注解,用%L
做占位符:
val headerList = ClassName("", "HeaderList")
val header = ClassName("", "Header")
val logRecord = FunSpec.builder("recordEvent")
.addModifiers(KModifier.ABSTRACT)
.addAnnotation(
AnnotationSpec.builder(headerList)
.addMember(
"[\n⇥%L,\n%L⇤\n]",
AnnotationSpec.builder(header)
.addMember("name = %S", "Accept")
.addMember("value = %S", "application/json; charset=utf-8")
.build(),
AnnotationSpec.builder(header)
.addMember("name = %S", "User-Agent")
.addMember("value = %S", "Square Cash")
.build()
)
.build()
)
.addParameter("logRecord", logRecordName)
.returns(logReceipt)
.build()
会生成:
@HeaderList(
[
Header(name = "Accept", value = "application/json; charset=utf-8"),
Header(name = "User-Agent", value = "Square Cash")
]
)
abstract fun recordEvent(logRecord: LogRecord): LogReceipt
KotlinPoet支撑注解运用处目标(Annotation use-site targets):
val utils = FileSpec.builder("com.example", "Utils")
.addAnnotation(
AnnotationSpec.builder(JvmName::class)
.useSiteTarget(UseSiteTarget.FILE)
.build()
)
.addFunction(
FunSpec.builder("abs")
.receiver(Int::class)
.returns(Int::class)
.addStatement("return if (this < 0) -this else this")
.build()
)
.build()
会生成:
@file:JvmName
package com.example
import kotlin.Int
import kotlin.jvm.JvmName
fun Int.abs(): Int = if (this < 0) -this else this
结束
下部分咱们会讨论占位符的用法,和剩下的一小部分KotinPoet手册的内容。