Pengantar JavaPoet

1. Ikhtisar

Dalam tutorial ini, kita akan menjelajahi fungsi dasar pustaka JavaPoet.

JavaPoet dikembangkan oleh Square, yang menyediakan API untuk menghasilkan kode sumber Java . Ini dapat menghasilkan tipe primitif, tipe referensi dan variannya (seperti kelas, antarmuka, tipe enumerasi, kelas dalam anonim), field, metode, parameter, anotasi, dan Javadocs.

JavaPoet mengelola impor kelas dependen secara otomatis. Ini juga menggunakan pola Builder untuk menentukan logika untuk menghasilkan kode Java.

2. Ketergantungan Maven

Untuk menggunakan JavaPoet, kita bisa langsung mendownload file JAR terbaru, atau mendefinisikan dependensi berikut di pom.xml kita :

 com.squareup javapoet 1.10.0 

3. Spesifikasi Metode

Pertama, mari kita lihat spesifikasi metode. Untuk menghasilkan metode, kita cukup memanggil metode methodBuilder () dari kelas MethodSpec . Kami menetapkan nama metode yang dihasilkan sebagai argumen String dari metode methodBuilder () .

Kita dapat membuat pernyataan logis apa pun yang diakhiri dengan titik koma menggunakan metode addStatement () . Sementara itu, kita dapat mendefinisikan satu aliran kontrol yang dibatasi dengan tanda kurung kurawal, seperti blok if-else , atau for loop, dalam aliran kontrol.

Berikut adalah contoh singkatnya - menghasilkan metode sumOfTen () yang akan menghitung jumlah angka dari 0 hingga 10:

MethodSpec sumOfTen = MethodSpec .methodBuilder("sumOfTen") .addStatement("int sum = 0") .beginControlFlow("for (int i = 0; i <= 10; i++)") .addStatement("sum += i") .endControlFlow() .build();

Ini akan menghasilkan keluaran sebagai berikut:

void sumOfTen() { int sum = 0; for (int i = 0; i <= 10; i++) { sum += i; } }

4. Blok Kode

Kami juga dapat membungkus satu atau lebih aliran kontrol dan pernyataan logis ke dalam satu blok kode :

CodeBlock sumOfTenImpl = CodeBlock .builder() .addStatement("int sum = 0") .beginControlFlow("for (int i = 0; i <= 10; i++)") .addStatement("sum += i") .endControlFlow() .build();

Yang menghasilkan:

int sum = 0; for (int i = 0; i <= 10; i++) { sum += i; }

Kita bisa menyederhanakan logika sebelumnya di MethodSpec dengan memanggil addCode () dan menyediakan objek sumOfTenImpl :

MethodSpec sumOfTen = MethodSpec .methodBuilder("sumOfTen") .addCode(sumOfTenImpl) .build();

Blok kode juga berlaku untuk spesifikasi lain, seperti tipe dan Javadocs.

5. Spesifikasi Lapangan

Berikutnya - mari jelajahi logika spesifikasi bidang.

Untuk membuat bidang, kami menggunakan metode builder () dari kelas FieldSpec :

FieldSpec name = FieldSpec .builder(String.class, "name") .addModifiers(Modifier.PRIVATE) .build();

Ini akan menghasilkan bidang berikut:

private String name;

Kita juga bisa menginisialisasi nilai default sebuah field dengan memanggil metode initializer () :

FieldSpec defaultName = FieldSpec .builder(String.class, "DEFAULT_NAME") .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) .initializer("\"Alice\"") .build();

Yang menghasilkan:

private static final String DEFAULT_NAME = "Alice";

6. Spesifikasi Parameter

Sekarang mari kita jelajahi logika spesifikasi parameter.

Jika kita ingin menambahkan parameter ke metode, kita bisa memanggil addParameter () di dalam rantai pemanggilan fungsi di builder.

Dalam kasus jenis parameter yang lebih kompleks, kita dapat menggunakan pembuat ParameterSpec :

ParameterSpec strings = ParameterSpec .builder( ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)), "strings") .build();

Kami juga dapat menambahkan pengubah metode, seperti publik dan / atau statis:

MethodSpec sumOfTen = MethodSpec .methodBuilder("sumOfTen") .addParameter(int.class, "number") .addParameter(strings) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addCode(sumOfTenImpl) .build();

Beginilah tampilan kode Java yang dihasilkan:

public static void sumOfTen(int number, List strings) { int sum = 0; for (int i = 0; i <= 10; i++) { sum += i; } }

7. Jenis Spesifikasi

Setelah menjelajahi cara menghasilkan metode, bidang, dan parameter, sekarang mari kita lihat spesifikasi jenisnya.

Untuk mendeklarasikan sebuah tipe, kita dapat menggunakan TypeSpec yang dapat membangun kelas, antarmuka, dan tipe enumerasi .

7.1. Menghasilkan Kelas

Untuk menghasilkan kelas, kita bisa menggunakan metode classBuilder () dari kelas TypeSpec .

Kita juga dapat menentukan pengubahnya, misalnya, pengubah akses publik dan akhir . Selain pengubah kelas, kami juga dapat menentukan bidang dan metode menggunakan yang telah disebutkan FieldSpec dan MethodSpec kelas.

Perhatikan bahwa metode addField () dan addMethod () juga tersedia saat membuat antarmuka atau kelas dalam anonim.

Mari kita lihat contoh pembuat kelas berikut:

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

Dan seperti inilah tampilan kode yang dihasilkan:

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. Menghasilkan Antarmuka

To generate a Java interface, we use the interfaceBuilder() method of the TypeSpec.

We can also define a default method by specifying DEFAULT modifier value in the addModifiers():

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

It will generate the following Java code:

public interface Person { private static final String DEFAULT_NAME = "Alice"; void getName(); default void getDefaultName() { return DEFAULT_NAME; } }

7.3. Generating an Enum

To generate an enumerated type, we can use the enumBuilder() method of the TypeSpec. To specify each enumerated value, we can call the addEnumConstant() method:

TypeSpec gender = TypeSpec .enumBuilder("Gender") .addModifiers(Modifier.PUBLIC) .addEnumConstant("MALE") .addEnumConstant("FEMALE") .addEnumConstant("UNSPECIFIED") .build();

The output of the aforementioned enumBuilder() logic is:

public enum Gender { MALE, FEMALE, UNSPECIFIED }

7.4. Generating an Anonymous Inner Class

To generate an anonymous inner class, we can use the anonymousClassBuilder() method of the TypeSpec class. Note that we must specify the parent class in the addSuperinterface() method. Otherwise, it will use the default parent class, which is 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();

This will generate the following Java code:

new Comparator() { public int compare(String a, String b) { return a.length() - b.length(); } });

8. Annotation Specification

To add an annotation to generated code, we can call the addAnnotation() method in a MethodSpec or FieldSpec builder class:

MethodSpec sumOfTen = MethodSpec .methodBuilder("sumOfTen") .addAnnotation(Override.class) .addParameter(int.class, "number") .addParameter(strings) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addCode(sumOfTenImpl) .build();

Which generates:

@Override public static void sumOfTen(int number, List strings) { int sum = 0; for (int i = 0; i <= 10; i++) { sum += i; } }

In case we need to specify the member value, we can call the addMember() method of the AnnotationSpec class:

AnnotationSpec toString = AnnotationSpec .builder(ToString.class) .addMember("exclude", "\"name\"") .build();

This will generate the following annotation:

@ToString( exclude = "name" )

9. Generating Javadocs

Javadoc can be generated using CodeBlock, or by specifying the value directly:

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

This will generate the following Java code:

/** * 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. Formatting

Let's recheck the example of the FieldSpec initializer in Section 5 which contains an escape char used to escape the “Alice” String value:

initializer("\"Alice\"")

There is also a similar example in Section 8 when we define the excluded member of an annotation:

addMember("exclude", "\"name\"")

It becomes unwieldy when our JavaPoet code grows and has a lot of similar String escape or String concatenation statements.

The String formatting feature in JavaPoet makes String formatting in beginControlFlow(), addStatement() or initializer() methods easier. The syntax is similar to String.format() functionality in Java. It can help to format literals, strings, types, and names.

10.1. Literal Formatting

JavaPoet replaces $L with a literal value in the output. We can specify any primitive type and String values in the argument(s):

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

In case we call the generateSumMethod() with the following values specified:

generateSumMethod("sumOfOneHundred", 0, 100, "+");

JavaPoet will generate the following output:

int sumOfOneHundred() { int sum = 0; for (int i = 0; i <= 100; i++) { sum = sum + i; } return sum; }

10.2. String Formatting

String formatting generates a value with the quotation mark, which refers exclusively to String type in Java. 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(); }

In case we call the generateGetter() method and provide these values:

generateStringSupplier("getDefaultName", "Bob");

We will get the following generated Java code:

String getDefaultName() { return "Bob"; }

10.3. Type Formatting

JavaPoet replaces $T with a type in the generated Java code. JavaPoet handles the type in the import statement automatically. If we had provided the type as a literal instead, JavaPoet would not handle the import.

MethodSpec getCurrentDateMethod = MethodSpec .methodBuilder("getCurrentDate") .returns(Date.class) .addStatement("return new $T()", Date.class) .build();

JavaPoet will generate the following output:

Date getCurrentDate() { return new Date(); }

10.4. Name Formatting

In case we need to refer to a name of a variable/parameter, field or method, we can use $N in JavaPoet's String formatter.

We can add the previous getCurrentDateMethod() to the new referencing method:

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

Which generates:

String getCurrentDateAsString() { DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); return formatter.format(getCurrentDate()); }

11. Generating Lambda Expressions

We can make use of the features that we've already explored to generate a Lambda expression. For instance, a code block which prints the name field or a variable multiple times:

CodeBlock printNameMultipleTimes = CodeBlock .builder() .addStatement("$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();

That logic generates the following output:

List names = new ArrayList(); IntStream.range(0, 10).forEach(i -> names.add(name)); names.forEach(System.out::println);

12. Producing the Output Using JavaFile

The JavaFile class helps to configure and produce the output of the generated code. To generate Java code, we simply build the JavaFile, provide the package name and an instance of the TypeSpec object.

12.1. Code Indentation

By default, JavaPoet uses two spaces for indentation. To keep the consistency, all examples in this tutorial were presented with 4 spaces indentation, which is configured via indent() method:

JavaFile javaFile = JavaFile .builder("com.baeldung.javapoet.person", person) .indent(" ") .build();

12.2. Static Imports

In case we need to add a static import, we can define the type and specific method name in the JavaFile by calling the addStaticImport() method:

JavaFile javaFile = JavaFile .builder("com.baeldung.javapoet.person", person) .indent(" ") .addStaticImport(Date.class, "UTC") .addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*") .build();

Which generates the following static import statements:

import static java.util.Date.UTC; import static java.time.ZonedDateTime.*;

12.3. Output

The writeTo() method provides functionality to write the code into multiple targets, such as standard output stream (System.out) and File.

To write Java code to a standard output stream, we simply call the writeTo() method, and provide the System.out as the argument:

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

The writeTo() method also accepts java.nio.file.Path and java.io.File. We can provide the corresponding Path or File object in order to generate the Java source code file into the destination folder/path:

Path path = Paths.get(destinationPath); javaFile.writeTo(path);

For more detailed information regarding JavaFile, please refer to the Javadoc.

13. Conclusion

Artikel ini telah memperkenalkan fungsionalitas JavaPoet, seperti metode pembuatan, bidang, parameter, jenis, anotasi, dan Javadocs.

JavaPoet dirancang hanya untuk pembuatan kode. Jika kami ingin melakukan metaprogramming dengan Java, JavaPoet pada versi 1.10.0 tidak mendukung kompilasi kode dan berjalan.

Seperti biasa, contoh dan cuplikan kode tersedia di GitHub.