Expert Beginner

Learning by doing

0%

Javac (JC) Cookbook - How to create instance via invoke default constructor

lombok除了JSR 269之外的黑科技。簡單來說lombok是透過Java 6推出的JSR 269 (Pluggable Annotation Processing API)和修改AST這兩個東西,讓我們寫程式更輕鬆。

JCTree就是讓我們可以處理Java AST的API。兩件事要先講,首先根據JCTree的JavaDoc寫類別名稱看到JC是javac的縮寫。接著,這些API是internal使用,有可能改變或刪除,如果要用後果自行負責。

This is NOT part of any supported API. If you write code that depends on this, you do so at your own risk. This code and its internal interfaces are subject to change or deletion without notice.

Question

如何透過JCTree API增加一個呼叫default constructor產生物件?

Answer

透過TreeMaker#NewClass()

1
2
3
4
5
6
7
8
public JCNewClass NewClass(JCExpression encl,
List<JCExpression> typeargs,
JCExpression clazz,
List<JCExpression> args,
JCClassDecl def)
{
return SpeculativeNewClass(encl, typeargs, clazz, args, def, false);
}

JCTree可以看到JCNewClass類別。

1
2
3
4
5
/**
* A new(...) operation.
*/
public static class JCNewClass extends JCPolyExpression implements NewClassTree {

下面是一個簡單的例子,透過呼叫StringBuilder的default constructor產生一個StringBuilder,後續使用StringBuilder#append(str: String)

1
2
3
4
5
6
7
8
9
10
11
12
JCTree.JCExpression varType = query("java", "lang", "StringBuilder");

Name result = names.fromString("result");
JCTree.JCModifiers resultModifiers = treeMaker.Modifiers(Flags.PARAMETER);

JCTree.JCNewClass stringBuilderClass = treeMaker.NewClass(null,
com.sun.tools.javac.util.List.nil(),
varType,
com.sun.tools.javac.util.List.nil(),
null);

JCTree.JCVariableDecl resultIdent = treeMaker.VarDef(resultModifiers, result, varType, stringBuilderClass);

對應產生的Java code就是

1
StringBuilder result = new StringBuilder();

Discussion

Anonymous class: 以Runnable為例

舉例來說,我們希望在AST裡加上如下列的內容

1
2
3
4
5
Runnable r = new Runnable() {
public void run() {

}
};

就會使用到TreeMaker#NewClass()的第五個參數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
JCTree.JCExpression runnableType = query("java", "lang", "Runnable");

Name runnable = names.fromString("r");
JCTree.JCModifiers rModifiers = treeMaker.Modifiers(Flags.PARAMETER);
ListBuffer<JCTree.JCStatement> runStatements = new ListBuffer<>();
JCTree.JCBlock rBody = treeMaker.Block(0, runStatements.toList());

JCTree.JCMethodDecl runMethod = treeMaker.MethodDef(
treeMaker.Modifiers(PUBLIC),
names.fromString("run"),
treeMaker.Type(new Type.JCVoidType()),
com.sun.tools.javac.util.List.nil(),
com.sun.tools.javac.util.List.nil(),
com.sun.tools.javac.util.List.nil(),
rBody,
null
);

List<JCTree> defs = List.of(runMethod);

JCTree.JCClassDecl classDecl = treeMaker.AnonymousClassDef(treeMaker.Modifiers(PARAMETER),
defs);

JCTree.JCNewClass rClass = treeMaker.NewClass(null,
com.sun.tools.javac.util.List.nil(),
runnableType,
com.sun.tools.javac.util.List.nil(),
classDecl);

JCTree.JCVariableDecl rIdent = treeMaker.VarDef(rModifiers, runnable, runnableType, rClass);