前言
JavaPoet,顾名思义是Java诗人,可以用来为我们生成模板代码,通过Java代码去生成Java代码,是不是觉得很神奇,本篇博客主要介绍其基础使用,更多信息可以查看其Github简介以及本文的附录部分。
引入
implementation 'com.squareup:javapoet:1.11.1'
基本对象
JavaPoet将一个Java文件的不同部分使用不同的对象去表示,分别如下,当我们需要生成代码的时候,直接通过相关的对象去查找即可。
TypeSpec 代表类、接口 MethodSpec 代表方法 FieldSpec 代表成员变量 CodeBlock 代表代码块 JavaFile 代表一个Java文件,可以指定保存位置、包名等
生成代码
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
上面的代码运行的结果为
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
首先我们使用MethodSpec.methodBuilder去创建一个名字叫main的方法,然后通过addModifiers()我们可以给方法添加标识符,通过returns(),我们可以添加返回值,通过addParameter(),用来添加参数。类似方法还有如下
addCode() //添加一句代码 addAnnotation() //添加一个注解 addException() //添加一个异常 addComment() //添加一个注释
然后我们使用TypeSpec.classBuilder去生成了一个名字叫HelloWorld的class,要想生成interface可以使用如下的方法。
TypeSpec.interfaceBuilder() //生成接口 TypeSpec.anonymousClassBuilder() //生成匿名类 TypeSpec.enumBuilder() //生成枚举
然后通过addMethod将上面构造好的方法加入到TypeSpec中,则一个类就组装好了,当然,如果我们想为这个类加入成员变量,可以如下操作,通过FieldSpec.builder生成一个属性,然后add进去
FieldSpec fieldSpec = FieldSpec.builder(int.class, "i", Modifier.PUBLIC).build();
TypeSpec mainClass = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addField(fieldSpec)
.build();
最后则是JavaFile.builder("包名", TypeSpec)去生成Java文件,你可以自己去保存,通过toString()获得字符串,也可以通过writeTo通过传递File等自动生成java文件,就这么简单。
表达式的生成
要想生成表达式,有如下几种方法,addCode(),addStatement()
MethodSpec main = MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();
生成结果
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
可以看到,我们直接使用addCode硬塞了一段代码,对于其中的变量,我们可以使用占位符代替,这个后面介绍。
MethodSpec mainMethod = MethodSpec.methodBuilder("main")
.addStatement("$T.out.println($S)", System.class, "hello wolrd")
.build();
生成结果
void main() {
System.out.println("hello wolrd");
}
这里我们使用的addStatement添加的代码,并且使用了占位符。
addCode与addStatement的区别就是addStatement会自动帮你引入需要的包和自动缩进。
循环代码生成
在上面的例子中,我介绍了使用addCode去添加整个循环语句,当然我们也可以使用addStatement,但是JavaPoet为我们提供了更好的方式。
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for (int i = 0; i < 10; i++)")
.addStatement("total += i")
.endControlFlow()
.build();
生成
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
我们可以使用beginControlFlow以及endControlFlow去设置循环开始语句以及循环结束,在这两句之间,我们可以使用addCode或者addStatement去添加循环体语句。
参数生成
在我们使用MethodSpec的时候,往往需要添加参数,这里详细介绍下如何添加参数。
方式一
MethodSpec
.methodBuilder("methodName")
.addParameter(int.class, "abc")
我们直接使用一个类型的class作为参数,对应的类型是java.lang.reflect.Type
方式二
MethodSpec
.methodBuilder("methodName")
.addParameter(ClassName.get("android.content","Context"), "context")
我们直接用ClassName.get方法去构造一个class,这样比方法一要好在,有时候我们的javaPoet可能访问不到某个对象,比如Android里面的Context等。
占位符
$L 表示常量。可以使用数字或者字符串去填充。
$S 表示字符串。
$T 表示Type。使用Class去填充
$N 表示方法名。
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
.addParameter(int.class, "i")
.returns(char.class)
.addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
.build();
MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
.addParameter(int.class, "b")
.returns(String.class)
.addStatement("char[] result = new char[2]")
.addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
.addStatement("result[1] = $N(b & 0xf)", hexDigit)
.addStatement("return new String(result)")
.build();
生成
public String byteToHex(int b) {
char[] result = new char[2];
result[0] = hexDigit((b >>> 4) & 0xf);
result[1] = hexDigit(b & 0xf);
return new String(result);
}
public char hexDigit(int i) {
return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
参考链接: