JavaPoet使用攻略
Posted 唯鹿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaPoet使用攻略相关的知识,希望对你有一定的参考价值。
JavaPoet是用于生成
.java
源文件的库 。通过自动生成代码,可以替代许多重复的工作。在安卓项目中添加依赖时,凡是需要添加apt、kpt、annotationProcessor这种前缀标识时,说明其内部使用到了注解处理器,也基本可以确定使用到了JavaPoet
或KotlinPoet
,例如大名鼎鼎的Butterknife
和Dagger
就使用到了JavaPoet
。
我在两三年前也有使用注解处理器和JavaPoet
写过一个Saber的库,在前一阵的维护中,突然对JavaPoet
有些生疏所以想着记录总结一下,以便以后使用时快速上手。
1.准备
添加依赖:
dependencies
...
implementation 'com.squareup:javapoet:1.13.0'
2.基本使用
既然是生成java文件,那么需要的基本元素就包括:
- 创建字段(属性)
- 创建方法
- 创建类、接口或枚举
- 输出文件
我们就按上面的顺序来一次介绍:
FieldSpec
FieldSpec
就是用来创建字段的类,使用起来很简单:
FieldSpec.builder(String.class, "android").build()
以上代码等价于String android;
如果添加修饰符可以使用addModifiers
,例如:
FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.build()
// private final String android;
其中Modifier
包括我们用到的所有修饰符,下面介绍的类,方法都是使用它:
public enum Modifier
/** The modifier @code public */ PUBLIC,
/** The modifier @code protected */ PROTECTED,
/** The modifier @code private */ PRIVATE,
/** The modifier @code abstract */ ABSTRACT,
/**
* The modifier @code default
* @since 1.8
*/
DEFAULT,
/** The modifier @code static */ STATIC,
/** The modifier @code final */ FINAL,
/** The modifier @code transient */ TRANSIENT,
/** The modifier @code volatile */ VOLATILE,
/** The modifier @code synchronized */ SYNCHRONIZED,
/** The modifier @code native */ NATIVE,
/** The modifier @code strictfp */ STRICTFP;
如果需要初始化参数,使用initializer
,例如:
FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer("$S", "Android")
.build()
// private final String android = "Android";
MethodSpec
MethodSpec
用来创建方法,一个最简单的方法包含方法名、返回类型。
MethodSpec.methodBuilder("test")
.returns(void.class)
.build()
// void test()
添加修饰符同上,这里不重复说明,如果添加方法参数,需要使用addParameter
:
MethodSpec.methodBuilder("test")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "str")
.returns(void.class)
.build()
/*public void test(String str)
*/
最后就是给方法添加语句,需要使用addStatement
:
MethodSpec.methodBuilder("test")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "str")
.addStatement("System.out.println(str)")
.returns(void.class)
.build()
/*public void test(String str)
System.out.println(str);
*/
TypeSpec
TypeSpec
用来创建类,接口,或者枚举。
// class Test
TypeSpec.classBuilder("Test").build();
//interface Test
TypeSpec.interfaceBuilder("Test").build();
/*
enum Test
ONE
*/
TypeSpec typeSpec = TypeSpec.enumBuilder("Test").addEnumConstant("ONE").build();
JavaFile
JavaFile
用于输出包含单个顶级类的Java文件。
JavaFile.builder("com.weilu.test", typeSpec).build();
用法很简单,指定包名和顶级类即可。
到这里,我们就可以将上面的用法串起来了:
class Test
public static void main(String[] args)
FieldSpec fieldSpec = FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer("$S", "Android")
.build();
MethodSpec methodSpec = MethodSpec.methodBuilder("test")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "str")
.returns(void.class)
.addStatement("System.out.println(str)")
.build();
TypeSpec typeSpec = TypeSpec.classBuilder("Test")
.addField(fieldSpec)
.addMethod(methodSpec)
.build();
JavaFile javaFile = JavaFile.builder("com.weilu.test", typeSpec).build();
System.out.println(javaFile.toString());
输出结果如下:
package com.weilu.test;
import java.lang.String;
class Test
private final String android = "Android";
public void test(String str)
System.out.println(str);
3.进阶用法
前面的用例过于简单,实际情况生成的代码是大量且复杂的。例如实现一个for循环:
MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\\n"
+ "for (int i = 0; i < 10; i++) \\n"
+ " total += i;\\n"
+ "\\n")
.build();
可以看到使用addCode
方法,可以一股脑的添加所有代码。但是我们需要自己换行,输入分号和缩进,这明显是个费力不讨好的方式,所以我个人不推荐使用。
ControlFlow
使用addStatement
可以帮我们添加分号和换行,而使用beginControlFlow
和 endControlFlow
组合可以帮我们轻松实现控制流代码。所以上面的代码等价于:
MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for (int i = 0; i < 10; i++)")
.addStatement("total += i")
.endControlFlow()
.build();
其他的使用场景,我举一些例子,大家一看便知:
// do... while
.beginControlFlow("do")
.endControlFlow("while (true)")
// if... else if... else...
.beginControlFlow("if (true)")
.nextControlFlow("else if (false)")
.nextControlFlow("else")
.endControlFlow()
// try... catch... finally
.beginControlFlow("try")
.nextControlFlow("catch ($T e)", Exception.class)
.addStatement("e.printStackTrace()")
.nextControlFlow("finally")
.endControlFlow()
占位符
上面的例子中,我们的代码都是固定的字符串,显得不灵活。因此JavaPoet
为我们提供了多种占位符来满足要求。
$S (String)
当代码中包含字符串的时候, 可以使用 $S
表示。
private static MethodSpec whatsMyName(String name)
return MethodSpec.methodBuilder(name)
.returns(String.class)
.addStatement("return $S", name)
.build();
$L (Literal)
$L
是字面量替换,它与$S
相似,但是它并不需要转义,也就是不包含字符串的引号。
private MethodSpec computeRange(String name, int from, int to, String op)
return MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 0")
.beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
.addStatement("result = result $L i", op)
.endControlFlow()
.addStatement("return result")
.build();
$T (Type)
上面例子为了简单,都使用的是一些基础类型,为的是不需要导包。实际中我们需要使用大量对象,如果只是在字符串中写死,代码虽没有问题,但是没有导包还是会保错。这是可以考虑使用$T
,它的作用是替换类型。
MethodSpec today = MethodSpec.methodBuilder("today")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
$N ( Name)
$N
是名称替换。例如我们定义了一个getXXX
的方法,我们调用它时可以使用addStatement("get$L()", "XXX")
这种写法实现,但是每次拼接"get"未免太麻烦了,一个不留心说不定还忘记了。那么使用addStatement("$N()", methodSpec)
就更加方便了。
MethodSpec methodSpec = MethodSpec.methodBuilder("get" + name)
.returns(String.class)
.addStatement("return $S", name)
.build();
MethodSpec.methodBuilder("getValue")
.returns(String.class)
.addStatement("return $N()", methodSpec)
.build();
继承与接口
TypeSpec.classBuilder("Test")
.superclass(String.class)
.addSuperinterface(Serializable.class)
.build();
//class Test extends String implements Serializable
泛型
FieldSpec.builder(TypeVariableName.get("T"), "mT", Modifier.PRIVATE).build();
// private T mT;
TypeVariableName mTypeVariable = TypeVariableName.get("T");
ParameterizedTypeName mListTypeName = ParameterizedTypeName.get(ClassName.get(List.class), mTypeVariable);
FieldSpec fieldSpec = FieldSpec.builder(mListTypeName, "mList", Modifier.PRIVATE).build();
//private List<T> mList;
方法和类中使用addTypeVariable
添加泛型。
初始化块
TypeSpec.classBuilder("Test")
.addStaticBlock(CodeBlock.builder().build())
.addInitializerBlock(CodeBlock.builder().build())
.build();
/*
class Test
static
*/
构造方法
MethodSpec methodSpec = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.build();
TypeSpec typeSpec = TypeSpec.classBuilder("Test")
.addMethod(methodSpec)
.build();
/*
class Test
public Test()
*/
Annotations
添加注解的方法可以直接使用addAnnotation
。
MethodSpec toString = MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.returns(String.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "Hoverboard")
.build();
如果需要给注解设置属性,那么需要使用AnnotationSpec
:
AnnotationSpec.builder(Headers.class)
.addMember("accept", "$S", "application/json; charset=utf-8")
.addMember("userAgent", "$S", "Square Cash")
.build()
/*@Headers(
accept = "application/json; charset=utf-8",
userAgent = "Square Cash"
)*/
匿名内部类
TypeSpec.anonymousClassBuilder("") // <- 也可添加参数
.superclass(Runnable.class)
.addMethod(MethodSpec.methodBuilder("run")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(TypeName.VOID)
.build())
.build();
/*
new Runnable()
@Override
public void run()
*/
Javadoc
添加注释可以使用addJavadoc
,直接传入注释字符串就行了,具体就不说明了。
大体上就这么多了,文末的参考文章也非常全面,推荐阅读。如果本篇对你有所帮助,希望可以一键三连!
参考
以上是关于JavaPoet使用攻略的主要内容,如果未能解决你的问题,请参考以下文章