前言
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'); }
参考链接: