JavaPoetの紹介

JavaPoetの概要

1. 概要

このチュートリアルでは、JavaPoetライブラリの基本的な機能について説明します。

JavaPoetSquareによって開発され、provides APIs to generate Java source codeです。 プリミティブ型、参照型、およびそれらのバリアント(クラス、インターフェース、列挙型、匿名内部クラスなど)、フィールド、メソッド、パラメーター、注釈、およびJavadocを生成できます。

JavaPoetは、依存クラスのインポートを自動的に管理します。 また、Builderパターンを使用して、Javaコードを生成するロジックを指定します。

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();

publicstatic:などのメソッドの修飾子を追加することもできます

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. 列挙型の生成

列挙型を生成するには、TypeSpecenumBuilder()メソッドを使用できます。 列挙された各値を指定するには、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 5FieldSpec初期化子の例を再確認してみましょう。

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で利用できます。