CodeModel
CodeModel是什麼
CodeModel是一個Java庫,使用它能通過Java程式碼生成Java類。乍看之下,除了“不明覺厲”,想不出來還能用什麼詞來形容他,說得不好聽一點,有什麼用呢?
怎麼用
說完了是什麼,接下來就是怎麼用。本篇文章只需要講解基本用法,所以IDE就使用Intellij Idea 2017 社群版。 為了方便引用庫,所以新建一個Gradle的Java專案,然後在專案的build.gradle檔案中新增依賴:
compile 'com.sun.codemodel:codemodel:2.6'
複製程式碼
操作方法跟Android Studio裡的差不多。
開始吧
如何生成一個類
很簡單,看程式碼:
public class Main {
public static void main(String[] args) throws JClassAlreadyExistsException, IOException {
//例項化JCodeModel物件
JCodeModel jCodeModel = new JCodeModel();
//生成package
JPackage jPackage = jCodeModel._package("com.peceoqicka.cm");
//在指定package下生成類
jPackage._class("MyClass");
//將生成的類程式碼寫入檔案
jCodeModel.build(new File("src/main/java/"));
}
}
複製程式碼
API中常用的類
類名 | 說明 |
---|---|
JPackage | 包型別,可由JCodeModel#_package方法得到,主要作用是呼叫_class方法在指定包裡生成類 |
JDefinedClass | 定義類型別,提供各種方法如向定義類裡新增方法或者成員等 |
JClass | 已有類型別,通常為引用已經有的類如String |
JFieldVar | 成員變數型別 |
JMethod | 方法型別 |
JBlock | 程式碼塊型別,通常由JMethod#_body方法獲得,用於向方法中新增程式碼 |
JArray | Java陣列型別 |
JExpression | Java表示式型別 |
JExpr | Java表示式工具類,用於生成JExpression |
Jvar | 區域性變數型別 |
成員變數與區域性變數
成員變數
成員變數的定義:
JFieldVar fieldVar = jDefinedClass.field(JMod.PRIVATE+JMod.STATIC+JMod.FINAL,
String.class, "CONSTANT_STR_NAME", JExp.lit("Gradle"));
複製程式碼
程式碼執行的結果是:
public class MyClass {
public final static String CONSTANT_STR_NAME = "Gradle";
}
複製程式碼
很好理解,一共4個引數,第一個引數定義變數的修飾符,凡是你能想到的例如public、static、private等等都是引用的JMod這個類裡邊的常量;第二個引數定義變數的型別,可以直接傳入已有的型別的class,或者引用用JCodeModel生成的型別(JType);第三個引數定義變數的名稱;第四個引數是可選的,如果你需要在定義的時候初始化這個變數,那就傳入第四個引數,否則就不傳。
field方法一共有4個重構方法:
field(int mods, Class<?> type, String name)
field(int mods, JType type, String name)
field(int mods, Class<?> type, String name, JExpression init)
field(int mods, JType type, String name, JExpression init)
複製程式碼
其中JExpression是表示式類,可以通過JExpr工具類來獲得,直接賦值可以使用:
JExpr.lit(String n)
JExpr.lit(int n)
...
複製程式碼
String以及所有基本值型別都可以通過這種方法來賦值。通過等號賦值請參看方法部分的說明。
區域性變數
區域性變數的定義:
JVar jVar = methodBody.decl(jCodeModel.INT, "i");
methodBody.assign(jVar, JExpr.lit(6));
複製程式碼
程式碼執行的結果:
int i;
i = 6;
複製程式碼
decl方法的重構方法:
decl(JType type, String name)
decl(JType type, String name, JExpression init)
decl(int mods, JType type, String name, JExpression init)//用於定義final區域性變數
複製程式碼
那麼如果區域性變數的賦值是一個方法呼叫的結果怎麼辦,別急:
methodBody.assign(jVar, JExpr.invoke(jVarClass, "methodAnother")
.arg(JExpr.lit(5));
複製程式碼
這行程式碼對應的生成程式碼為:
i = someClass.methodAnother(5);
複製程式碼
可以很明顯的看出來,方法呼叫用JExpression來表達就是JExpr.invoke,第一個引數是指明呼叫的方法是屬於哪個類的例項的,第二個引數指明方法名稱。如果呼叫的方法要傳入引數,那麼直接在invoke後方呼叫arg方法傳入,有多少個引數就呼叫多少次arg方法,都是鏈式呼叫。
方法
方法的定義:
JMethod jMethodGWC = jDefinedClass.method(JMod.PUBLIC, jCodeModel.INT, "getWheelCount");
JBlock jBlockGWC = jMethodGWC.body();
JVar jVarC = jBlockGWC.decl(jCodeModel.INT, "count");
jBlockGWC.assign(jVarC, JExpr.lit(6));
jBlockGWC._return(jVarC);
複製程式碼
對應的程式碼:
public int getWheelCount() {
int count;
count = 6;
return count;
}
複製程式碼
為方法新增引數:
jMethod.param(jCodeModel.INT, "type");//int type
複製程式碼
傳入引數與定義區域性變數的decl方法的一致。JBlock的_return方法定義了方法體的返回語句。
建構函式
建構函式就是沒有返回值的方法,定義:
JMethod constructor = jDefinedClass.constructor(JMod.PUBLIC);
//public Car(){}
複製程式碼
那麼新增引數和在方法體中新增程式碼的方法和普通方法完全一樣。
類和介面
類的定義:
JDefinedClass jDefinedClass = jPackage._class("Car");
//public class Car{}
複製程式碼
預設為public,定義抽象類直接在引數裡新增JMod.ABSTRACT。
JDefinedClass jDefinedClass = jPackage._class(JMod.ABSTRACT, "Car");
//public abstract class Car{}
複製程式碼
定義介面:
JDefinedClass jDefinedClass = jPackage._class(JMod.ABSTRACT, "ICar", ClassType.INTERFACE);
//public interface ICar{}
複製程式碼
繼承和實現(介面):
jDefinedClass._extends(MyClass.class);
jDefinedClass._implements(MyInterface.class);
複製程式碼
引用已有的型別:
JClass jClass = jCodeModel.ref(String.class);
jClass = jCodeModel.ref("java.lang.String");
複製程式碼
條件語句
if...else
定義if條件語句:
JConditional jConditionalGGN = jBlockGGN._if(JExpr.ref("sex").eq(JExpr.lit(1)));
jConditionalGGN._then().block()._return(JExpr.lit("Female"));
jConditionalGGN._else().block()._return(JExpr.lit("Male"));
複製程式碼
對應的程式碼:
if (sex == 1) {
return "Female";
} else {
return "Male";
}
複製程式碼
block方法返回的同樣是JBlock型別。
switch
定義switch語句:
JSwitch jSwitchGGNS = jBlockGGNS._switch(JExpr.ref("sex"));
jSwitchGGNS._case(JExpr.lit(1)).body()._return(JExpr.lit("Female"));
jSwitchGGNS._case(JExpr.lit(0));
jSwitchGGNS._default().body()._return(JExpr.lit("Male"));
複製程式碼
對應的程式碼:
public static String getGenderName(int sex) {
switch (sex) {
case 1:
return "Female";
case 0:
default:
return "Male";
}
}
複製程式碼
迴圈語句
for迴圈
定義for迴圈:
ForLoop jForLoop = jBlockDS._for();
jForLoop.init(jCodeModel.INT, "i", JExpr.lit(0));//初始化部分
jForLoop.test(JExpr.ref("i").lt(JExpr.lit(100)));//條件判斷部分
jForLoop.update(JExpr.ref("i").incr());//條件變化部分
JBlock jBlockFLDS = jForLoop.body();//獲得for迴圈程式碼塊
JClass jClassSystem = jCodeModel.ref(System.class);
JFieldRef jFieldRefSOut = jClassSystem.staticRef("out");
//for迴圈程式碼塊中呼叫System.out.println
jBlockFLDS.invoke(jFieldRefSOut, "println").arg("hello");
複製程式碼
對應的程式碼:
for (int i = 0; (i< 100); i ++) {
System.out.println("hello");
}
複製程式碼
foreach迴圈
定義foreach迴圈:
//定義一個字串陣列,並新增初始化資料
JArray jArray = JExpr.newArray(jCodeModel.ref(String.class));
jArray.add(JExpr.lit("Amy"));
jArray.add(JExpr.lit("Bruce"));
jArray.add(JExpr.lit("Cherry"));
jArray.add(JExpr.lit("Douglas"));
jArray.add(JExpr.lit("Ella"));
jArray.add(JExpr.lit("Fin"));
//定義定義區域性變數引用剛才的陣列
JVar arr = jBlockDS.decl(jCodeModel.ref(String[].class), "arr", jArray);
JForEach jForEach = jBlockDS.forEach(jCodeModel.ref(String.class), "str", arr);
JFieldRef jFieldRefSOut = jCodeModel.ref(System.class).staticRef("out");
jForEach.body().invoke(jFieldRefSOut, "println").arg(jForEach.var());
複製程式碼
對應的程式碼:
String[] arr = new String[] {"Amy", "Bruce", "Cherry", "Douglas", "Ella", "Fin"};
for (String str: arr) {
System.out.println(str);
}
複製程式碼
while迴圈
定義while迴圈:
//定義區域性變數i=0
JVar jVarI = jBlockDS.decl(jCodeModel.INT, "i", JExpr.lit(0));
//生成while語句,新增條件i<10
JWhileLoop jWhileLoop = jBlockDS._while(JExpr.ref("i").lt(JExpr.lit(10)));
//同上呼叫方法System.out.println,引數為i
JFieldRef jFieldRefSOut = jCodeModel.ref(System.class).staticRef("out");
jWhileLoop.body().invoke(jFieldRefSOut, "println").arg(jVarI);
//附加語句i+=1
jWhileLoop.body().assignPlus(jVarI, JExpr.lit(1));
複製程式碼
對應的程式碼:
int i = 0;
while (i< 10) {
System.out.println(i);
i += 1;
}
複製程式碼
do...while迴圈
定義do..while迴圈的程式碼與while的程式碼幾乎完全一致,將第一行改為呼叫_do:
JVar jVarI = jBlockDS.decl(jCodeModel.INT, "i", JExpr.lit(0));
//定義do..while迴圈,同樣傳入條件,其餘程式碼幾乎完全一樣
JDoLoop jDoLoop = jBlockDS._do(JExpr.ref("i").lt(JExpr.lit(10)));
JFieldRef jFieldRefSOut = jCodeModel.ref(System.class).staticRef("out"); jDoLoop.body().invoke(jFieldRefSOut, "println").arg(jVarI);
jDoLoop.body().assignPlus(jVarI, JExpr.lit(1));
複製程式碼
對應的程式碼:
int i = 0;
do {
System.out.println(i);
i += 1;
} while (i< 10);
複製程式碼
Emmm...
這篇文章作為CodeModel庫的簡要用法參考手冊,是我在學習了僅有的少量資料的情況下自行總結的。最近發現官方文件的頁面不知什麼原因不再維護了,恐怕以後會刪除這個專案,不過庫本身還能在maven倉庫中找到,還能夠繼續使用,只是參考資料不會再有更新了。當然這個庫設計的思路還是很好的,可以作為一個範本,進而學習它的基本原理,然後自己實現這樣一個庫,豈不美哉。(其實這個庫已經落後了,還是用Freemarker吧各位,這篇文章只是歷史遺留物品而已)
本篇文章僅代表個人觀點,難免會有疏漏或者謬誤之處,歡迎批評指正。