JavaPoetの概要
1. 概要
2. メーベン依存
JavaPoetを使用するには、最新のJAR fileを直接ダウンロードするか、pom.xml:に次の依存関係を定義します。
com.squareup
javapoet
1.10.0
3. メソッド仕様
まず、メソッドの仕様を見ていきましょう。 メソッドを生成するには、MethodSpecクラスのmethodBuilder()メソッドを呼び出すだけです。 生成されたメソッド名は、methodBuilder()メソッドのString引数として指定します。
addStatement()メソッドを使用してgenerate any single logical statement ending with the semi-colonを実行できます。 一方、制御フローでは、if-elseブロックやforループなど、中括弧で囲まれた1つの制御フローを定義できます。
簡単な例を次に示します。0から10までの数値の合計を計算するsumOfTen()メソッドを生成します。
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addStatement("int sum = 0")
.beginControlFlow("for (int i = 0; i <= 10; i++)")
.addStatement("sum += i")
.endControlFlow()
.build();
これにより、次のような出力が生成されます。
void sumOfTen() {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
4. コードブロック
wrap one or more control flows and logical statements into one code blockも可能です:
CodeBlock sumOfTenImpl = CodeBlock
.builder()
.addStatement("int sum = 0")
.beginControlFlow("for (int i = 0; i <= 10; i++)")
.addStatement("sum += i")
.endControlFlow()
.build();
どれが生成されます:
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
addCode()を呼び出し、sumOfTenImplオブジェクトを提供することにより、MethodSpecの以前のロジックを単純化できます。
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addCode(sumOfTenImpl)
.build();
コードブロックは、タイプやJavadocなどの他の仕様にも適用できます。
5. フィールド仕様
次へ–フィールド仕様ロジックを見てみましょう。
フィールドを生成するために、FieldSpecクラスのbuilder()メソッドを使用します。
FieldSpec name = FieldSpec
.builder(String.class, "name")
.addModifiers(Modifier.PRIVATE)
.build();
これにより、次のフィールドが生成されます。
private String name;
initializer()メソッドを呼び出すことで、フィールドのデフォルト値を初期化することもできます。
FieldSpec defaultName = FieldSpec
.builder(String.class, "DEFAULT_NAME")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("\"Alice\"")
.build();
どれが生成されます:
private static final String DEFAULT_NAME = "Alice";
6. パラメータ仕様
それでは、パラメータ仕様ロジックについて見ていきましょう。
メソッドにパラメータを追加する場合は、ビルダーの関数呼び出しのチェーン内でaddParameter()を呼び出すことができます。
より複雑なパラメータータイプの場合は、ParameterSpecビルダーを利用できます。
ParameterSpec strings = ParameterSpec
.builder(
ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)),
"strings")
.build();
publicやstatic:などのメソッドの修飾子を追加することもできます
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addParameter(int.class, "number")
.addParameter(strings)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addCode(sumOfTenImpl)
.build();
生成されたJavaコードは次のようになります。
public static void sumOfTen(int number, List strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
7. タイプ仕様
メソッド、フィールド、およびパラメーターを生成する方法を検討した後、次に型の仕様を見てみましょう。
タイプを宣言するには、TypeSpec which can build classes, interfaces, and enumerated typesを使用できます。
7.1. クラスの生成
クラスを生成するために、TypeSpecクラスのclassBuilder()メソッドを使用できます。
修飾子を指定することもできます。たとえば、public およびfinalアクセス修飾子です。 クラス修飾子に加えて、すでに説明したFieldSpecクラスとMethodSpecクラスを使用してフィールドとメソッドを指定することもできます。
インターフェースまたは匿名内部クラスを生成する場合は、addField()メソッドとaddMethod()メソッドも使用できることに注意してください。
次のクラスビルダーの例を見てみましょう。
TypeSpec person = TypeSpec
.classBuilder("Person")
.addModifiers(Modifier.PUBLIC)
.addField(name)
.addMethod(MethodSpec
.methodBuilder("getName")
.addModifiers(Modifier.PUBLIC)
.returns(String.class)
.addStatement("return this.name")
.build())
.addMethod(MethodSpec
.methodBuilder("setName")
.addParameter(String.class, "name")
.addModifiers(Modifier.PUBLIC)
.returns(String.class)
.addStatement("this.name = name")
.build())
.addMethod(sumOfTen)
.build();
生成されたコードは次のようになります。
public class Person {
private String name;
public String getName() {
return this.name;
}
public String setName(String name) {
this.name = name;
}
public static void sumOfTen(int number, List strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
}
7.2. インターフェイスの生成
Javaインターフェイスを生成するには、TypeSpec.のinterfaceBuilder()メソッドを使用します
addModifiers()にDEFAULT修飾子の値を指定することにより、デフォルトのメソッドを定義することもできます。
TypeSpec person = TypeSpec
.interfaceBuilder("Person")
.addModifiers(Modifier.PUBLIC)
.addField(defaultName)
.addMethod(MethodSpec
.methodBuilder("getName")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.build())
.addMethod(MethodSpec
.methodBuilder("getDefaultName")
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
.addCode(CodeBlock
.builder()
.addStatement("return DEFAULT_NAME")
.build())
.build())
.build();
次のJavaコードが生成されます。
public interface Person {
private static final String DEFAULT_NAME = "Alice";
void getName();
default void getDefaultName() {
return DEFAULT_NAME;
}
}
7.3. 列挙型の生成
列挙型を生成するには、TypeSpecのenumBuilder()メソッドを使用できます。 列挙された各値を指定するには、addEnumConstant()メソッドを呼び出します。
TypeSpec gender = TypeSpec
.enumBuilder("Gender")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("MALE")
.addEnumConstant("FEMALE")
.addEnumConstant("UNSPECIFIED")
.build();
前述のenumBuilder()ロジックの出力は次のとおりです。
public enum Gender {
MALE,
FEMALE,
UNSPECIFIED
}
7.4. 匿名の内部クラスの生成
匿名の内部クラスを生成するには、TypeSpecクラスのanonymousClassBuilder()メソッドを使用できます。 we must specify the parent class in the addSuperinterface() methodに注意してください。 それ以外の場合は、デフォルトの親クラスであるObjectが使用されます。
TypeSpec comparator = TypeSpec
.anonymousClassBuilder("")
.addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
.addMethod(MethodSpec
.methodBuilder("compare")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "a")
.addParameter(String.class, "b")
.returns(int.class)
.addStatement("return a.length() - b.length()")
.build())
.build();
これにより、次のJavaコードが生成されます。
new Comparator() {
public int compare(String a, String b) {
return a.length() - b.length();
}
});
8. 注釈仕様
生成されたコードにアノテーションを追加するには、MethodSpecまたはFieldSpecビルダークラスでaddAnnotation()メソッドを呼び出すことができます。
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addAnnotation(Override.class)
.addParameter(int.class, "number")
.addParameter(strings)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addCode(sumOfTenImpl)
.build();
どれが生成されます:
@Override
public static void sumOfTen(int number, List strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
メンバー値を指定する必要がある場合は、AnnotationSpecクラスのaddMember()メソッドを呼び出すことができます。
AnnotationSpec toString = AnnotationSpec
.builder(ToString.class)
.addMember("exclude", "\"name\"")
.build();
これにより、次の注釈が生成されます。
@ToString(
exclude = "name"
)
9. Javadocの生成
Javadocは、CodeBlock,を使用するか、値を直接指定することによって生成できます。
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addJavadoc(CodeBlock
.builder()
.add("Sum of all integers from 0 to 10")
.build())
.addAnnotation(Override.class)
.addParameter(int.class, "number")
.addParameter(strings)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addCode(sumOfTenImpl)
.build();
これにより、次のJavaコードが生成されます。
/**
* Sum of all integers from 0 to 10
*/
@Override
public static void sumOfTen(int number, List strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
10. フォーマット
「Alice」String値をエスケープするために使用されるエスケープ文字を含むSection 5のFieldSpec初期化子の例を再確認してみましょう。
initializer("\"Alice\"")
注釈の除外されたメンバーを定義するときのSection 8にも同様の例があります。
addMember("exclude", "\"name\"")
JavaPoetコードが大きくなり、同様のStringエスケープまたはString連結ステートメントが多数あると、扱いにくくなります。
JavaPoetの文字列フォーマット機能により、beginControlFlow()、addStatement()、またはinitializer()メソッドでのStringフォーマットが簡単になります。 The syntax is similar to String.format() functionality in Java. It can help to format literals, strings, types, and names。
10.1. リテラルフォーマット
JavaPoet replaces $L with a literal value in the output.引数に任意のプリミティブ型とString値を指定できます。
private MethodSpec generateSumMethod(String name, int from, int to, String operator) {
return MethodSpec
.methodBuilder(name)
.returns(int.class)
.addStatement("int sum = 0")
.beginControlFlow("for (int i = $L; i <= $L; i++)", from, to)
.addStatement("sum = sum $L i", operator)
.endControlFlow()
.addStatement("return sum")
.build();
}
次の値を指定してgenerateSumMethod()を呼び出す場合:
generateSumMethod("sumOfOneHundred", 0, 100, "+");
JavaPoetは次の出力を生成します。
int sumOfOneHundred() {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
10.2. String Formatting
Stringフォーマットでは、引用符が付いた値が生成されます。これは、JavaのStringタイプのみを参照します。 JavaPoet replaces $S with a String value in the output:
private static MethodSpec generateStringSupplier(String methodName, String fieldName) {
return MethodSpec
.methodBuilder(methodName)
.returns(String.class)
.addStatement("return $S", fieldName)
.build();
}
generateGetter()メソッドを呼び出して、次の値を指定する場合:
generateStringSupplier("getDefaultName", "Bob");
次の生成されたJavaコードを取得します。
String getDefaultName() {
return "Bob";
}
10.3. タイプの書式設定
JavaPoet replaces $T with a type in the generated Java code。 JavaPoetは、importステートメントのタイプを自動的に処理します。 代わりにリテラルとして型を指定した場合、JavaPoetはインポートを処理しません。
MethodSpec getCurrentDateMethod = MethodSpec
.methodBuilder("getCurrentDate")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
JavaPoetは次の出力を生成します。
Date getCurrentDate() {
return new Date();
}
10.4. 名前の書式設定
JavaPoetのStringフォーマッタでrefer to a name of a variable/parameter, field or method, we can use $Nを実行する必要がある場合。
以前のgetCurrentDateMethod()を新しい参照方法に追加できます。
MethodSpec dateToString = MethodSpec
.methodBuilder("getCurrentDateAsString")
.returns(String.class)
.addStatement(
"$T formatter = new $T($S)",
DateFormat.class,
SimpleDateFormat.class,
"MM/dd/yyyy HH:mm:ss")
.addStatement("return formatter.format($N())", getCurrentDateMethod)
.build();
どれが生成されます:
String getCurrentDateAsString() {
DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
return formatter.format(getCurrentDate());
}
11. ラムダ式の生成
すでに調べた機能を利用して、ラムダ式を生成できます。 たとえば、nameフィールドまたは変数を複数回出力するコードブロック:
CodeBlock printNameMultipleTimes = CodeBlock
.builder()
.addStatement("$T<$T> names = new $T<>()", List.class, String.class, ArrayList.class)
.addStatement("$T.range($L, $L).forEach(i -> names.add(name))", IntStream.class, 0, 10)
.addStatement("names.forEach(System.out::println)")
.build();
そのロジックは次の出力を生成します。
List names = new ArrayList<>();
IntStream.range(0, 10).forEach(i -> names.add(name));
names.forEach(System.out::println);
12. JavaFileを使用して出力を生成する
The JavaFile class helps to configure and produce the output of the generated code。 Javaコードを生成するには、JavaFile,をビルドして、パッケージ名とTypeSpecオブジェクトのインスタンスを指定します。
12.1. コードのインデント
デフォルトでは、JavaPoetはインデントに2つのスペースを使用します。 一貫性を保つために、このチュートリアルのすべての例は、indent()メソッドを介して構成された4つのスペースインデントで示されています。
JavaFile javaFile = JavaFile
.builder("com.example.javapoet.person", person)
.indent(" ")
.build();
12.2. 静的インポート
静的インポートを追加する必要がある場合は、addStaticImport()メソッドを呼び出すことにより、JavaFileでタイプと特定のメソッド名を定義できます。
JavaFile javaFile = JavaFile
.builder("com.example.javapoet.person", person)
.indent(" ")
.addStaticImport(Date.class, "UTC")
.addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*")
.build();
次の静的なインポート文が生成されます。
import static java.util.Date.UTC;
import static java.time.ZonedDateTime.*;
12.3. 出力
writeTo()メソッドは、標準出力ストリーム(System.out)やFileなどの複数のターゲットにコードを書き込む機能を提供します。
Javaコードを標準出力ストリームに書き込むには、単にwriteTo()メソッドを呼び出し、引数としてSystem.outを指定します。
JavaFile javaFile = JavaFile
.builder("com.example.javapoet.person", person)
.indent(" ")
.addStaticImport(Date.class, "UTC")
.addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*")
.build();
javaFile.writeTo(System.out);
writeTo()メソッドは、java.nio.file.Pathおよびjava.io.Fileも受け入れます。 Javaソースコードファイルを宛先フォルダ/パスに生成するために、対応するPathまたはFileオブジェクトを提供できます。
Path path = Paths.get(destinationPath);
javaFile.writeTo(path);
JavaFileの詳細については、Javadocを参照してください。
13. 結論
この記事は、メソッド、フィールド、パラメーター、型、注釈、Javadocsの生成など、JavaPoetの機能の紹介です。
JavaPoetはコード生成専用に設計されています。 Javaでメタプログラミングを行いたい場合、バージョン1.10.0以降のJavaPoetはコードのコンパイルと実行をサポートしていません。
いつものように、例とコードスニペットはover on GitHubで利用できます。