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]から入手可能です。