JavaPoetの紹介
1概要
このチュートリアルでは、https://github.com/square/javapoet[JavaPoet]ライブラリの基本機能について説明します。
-
JavaPoet
はhttp://square.github.io/[Square]によって開発されています。これは
Javaソースコードを生成するためのAPIを提供します** 。プリミティブ型、参照型、およびそれらの派生型(クラス、インタフェース、列挙型、匿名内部クラスなど)、フィールド、メソッド、パラメータ、アノテーション、およびJavadocを生成できます。
JavaPoetは依存クラスのインポートを自動的に管理します。また、Builderパターンを使用してJavaコードを生成するためのロジックを指定します。
2 Mavenの依存関係
JavaPoetを使用するには、最新のhttp://central.maven.org/maven2/com/squareup/javapoet/1.10.0/javapoet-1.10.0.jar[JARファイル]を直接ダウンロードするか、以下を定義してください。
pom.xml内の依存関係:
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.10.0</version>
</dependency>
** 3メソッド仕様
まず、メソッド仕様を見てみましょう。メソッドを生成するには、
MethodSpec
クラスの
methodBuilder()
メソッドを呼び出すだけです。生成されたメソッド名を
methodBuilder()
メソッドの
String
引数として指定します。
addStatement()
メソッドを使用して、セミコロンで終わる単一の論理ステートメントを生成することができます。一方、制御フローでは、
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.コードブロック
1つ以上の制御フローと論理ステートメントを1つのコードブロックにラップすることもできます。
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<String> strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
7. タイプ指定
メソッド、フィールド、およびパラメータを生成する方法を調べた後で、型指定を見てみましょう。
型を宣言するには、クラス、インタフェース、および列挙型を構築できる**
TypeSpec
を使用できます。
7.1. クラスを生成する
クラスを生成するために、
TypeSpec
クラスの
classBuilder()
メソッドを使うことができます。
その修飾子を指定することもできます。たとえば、
__ public
および
final
access modifiersです。クラス修飾子に加えて、既に述べた
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<String> 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()
メソッドを使用できます。
addSuperinterface()
メソッドで親クラスを指定しなければならないことに注意してください。
それ以外の場合は、デフォルトの親クラスである
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<String>() {
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<String> 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<String> strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
----
10フォーマット
リンクの
FieldSpec
初期化子の例をもう一度見てみましょう。
initializer("\"Alice\"")
注釈の除外されたメンバーを定義すると、
セクション8
にも同様の例があります。
addMember("exclude", "\"name\"")
私たちのJavaPoetコードが大きくなり、
String
エスケープまたは
String
連結ステートメントがよく似ていると、扱いにくくなります。
JavaPoetの文字列フォーマット機能により、
beginControlFlow()
、
addStatement()
、または
initializer()
メソッドの
String
フォーマットが簡単になります。 ** 構文は、Javaの
String.format()
機能と似ています。リテラル、文字列、型、および名前をフォーマットするのに役立ちます。
10.1. リテラルフォーマット
-
JavaPoetは
$ L
を出力中のリテラル値に置き換えます** 引数には任意のプリミティブ型と
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
__フォーマット
String
フォーマットは、引用符付きの値を生成します。これは、Javaでは
String
タイプを排他的に参照します。 ** JavaPoetは、出力で
$ S
を
String
値に置き換えます。
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は
$ T
を生成されたJavaコードの型に置き換えます** 。
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
フォーマッタで
$ 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.ラムダ式の生成
Lambda式を生成するためにすでに調べた機能を利用することができます。たとえば、
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<String> names = new ArrayList<>();
IntStream.range(0, 10).forEach(i -> names.add(name));
names.forEach(System.out::println);
12.
JavaFile
を使用した出力の生成
-
JavaFile
クラスは、生成されたコードの出力を設定および生成するのに役立ちます** 。 Javaコードを生成するには、単に
JavaFileをビルドし、パッケージ名と
TypeSpec__オブジェクトのインスタンスを指定します。
12.1. コードのインデント
デフォルトでは、JavaPoetはインデントに2つのスペースを使用します。一貫性を保つために、このチュートリアルのすべての例は
indent()
メソッドで設定された4つのスペースインデントで示されました:
JavaFile javaFile = JavaFile
.builder("com.baeldung.javapoet.person", person)
.indent(" ")
.build();
12.2. 静的インポート
静的インポートを追加する必要がある場合は、
addStaticImport()
メソッドを呼び出すことによって
JavaFile
に型と特定のメソッド名を定義できます。
JavaFile javaFile = JavaFile
.builder("com.baeldung.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.baeldung.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
の詳細については、https://square.github.io/javapoet/javadoc/javapoet/com/squareup/javapoet/JavaFile.html[Javadoc]を参照してください。
13. 結論
この記事は、メソッド、フィールド、パラメーター、タイプ、アノテーション、およびJavadocの生成などのJavaPoet機能の紹介です。
JavaPoetはコード生成専用に設計されています。 Javaでメタプログラミングを行いたい場合は、バージョン1.10.0以降のJavaPoetはコードのコンパイルと実行をサポートしていません。
いつものように、例とコードスニペットはhttps://github.com/eugenp/tutorials/tree/master/libraries[over GitHub]から入手可能です。