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"
)*/
Javadoc
添加注釋可以使用addJavadoc,直接傳入注釋字串就行了,具體就不說明了,
大體上就這么多了,文末的參考文章也非常全面,推薦閱讀,如果本篇對你有所幫助,希望可以一鍵三連!
參考
- JavaPoet 看這一篇就夠了
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/250704.html
標籤:其他
