Pengenalan JavaPoet

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan meneroka fungsi asas perpustakaan JavaPoet.

JavaPoet dikembangkan oleh Square, yang menyediakan API untuk menghasilkan kod sumber Java . Ia dapat menghasilkan jenis primitif, jenis rujukan dan variannya (seperti kelas, antara muka, jenis yang dihitung, kelas dalaman tanpa nama), bidang, kaedah, parameter, anotasi, dan Javadocs.

JavaPoet menguruskan import kelas bergantung secara automatik. Ia juga menggunakan corak Builder untuk menentukan logik untuk menghasilkan kod Java.

2. Ketergantungan Maven

Untuk menggunakan JavaPoet, kita dapat memuat turun fail JAR terbaru secara langsung, atau menentukan ketergantungan berikut di pom.xml kami :

 com.squareup javapoet 1.10.0 

3. Spesifikasi Kaedah

Pertama, mari kita teliti spesifikasi kaedah. Untuk menghasilkan kaedah, kita hanya memanggil kaedah MethodBuilder () dari kelas MethodSpec . Kami menentukan nama kaedah yang dihasilkan sebagai argumen String dari kaedah methodBuilder () .

Kita boleh menghasilkan satu penyataan logik tunggal yang diakhiri dengan titik koma menggunakan kaedah addStatement () . Sementara itu, kita dapat menentukan satu aliran kawalan yang dibatasi dengan kurungan keriting, seperti blok if-else , atau untuk gelung, dalam aliran kawalan.

Berikut adalah contoh ringkas - menghasilkan kaedah sumOfTen () yang akan mengira jumlah nombor 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 output berikut:

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

4. Blok Kod

Kami juga dapat membungkus satu atau lebih aliran kawalan dan penyataan logik ke dalam satu blok kod :

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

Yang menjana:

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

Kita dapat mempermudah logik sebelumnya dalam MethodSpec dengan memanggil addCode () dan memberikan objek sumOfTenImpl :

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

Blok kod juga berlaku untuk spesifikasi lain, seperti jenis dan Javadocs.

5. Spesifikasi Bidang

Seterusnya - mari kita meneroka logik spesifikasi medan.

Dalam usaha untuk menjana medan, kita menggunakan pembina () kaedah yang FieldSpec kelas:

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

Ini akan menghasilkan medan berikut:

private String name;

Kita juga dapat menginisialisasi nilai lalai bidang dengan memanggil kaedah inisialisasi () :

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

Yang menjana:

private static final String DEFAULT_NAME = "Alice";

6. Spesifikasi Parameter

Sekarang mari kita meneroka logik spesifikasi parameter.

Sekiranya kita ingin menambahkan parameter ke metode, kita dapat memanggil addParameter () dalam rantai panggilan fungsi di pembangun.

Sekiranya jenis parameter yang lebih kompleks, kita dapat menggunakan pembangun ParameterSpec :

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

Kami juga dapat menambahkan pengubah kaedah, seperti umum dan / atau statik:

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

Begini rupa kod Java yang dihasilkan:

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

7. Spesifikasi Jenis

Setelah meneroka kaedah untuk menghasilkan kaedah, medan, dan parameter, sekarang mari kita lihat spesifikasi jenisnya.

Untuk menyatakan jenis, kita dapat menggunakan TypeSpec yang dapat membina kelas, antara muka, dan jenis yang dihitung .

7.1. Menjana Kelas

Untuk menghasilkan kelas, kita boleh menggunakan kaedah classBuilder () dari kelas TypeSpec .

Kami juga dapat menentukan pengubahnya, misalnya, pengubah akses awam dan akhir . Selain pengubah kelas, kita juga boleh menentukan bidang dan kaedah menggunakan telah disebutkan FieldSpec dan MethodSpec kelas.

Perhatikan bahawa kaedah addField () dan addMethod () juga tersedia semasa menghasilkan antara muka atau kelas dalaman tanpa nama.

Mari lihat contoh pembina 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 inilah kod yang dihasilkan seperti:

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. Menjana Antaramuka

Untuk menghasilkan antara muka Java, kami menggunakan kaedah interfaceBuilder () dari TypeSpec.

Kita juga dapat menentukan kaedah lalai dengan menentukan nilai pengubah DEFAULT di 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();

Ia akan menghasilkan kod Java berikut:

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

7.3. Menjana Enum

Untuk menghasilkan jenis yang dihitung, kita boleh menggunakan kaedah enumBuilder () dari TypeSpec . Untuk menentukan setiap nilai yang dihitung, kita boleh memanggil kaedah addEnumConstant () :

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

Keluaran logik enumBuilder () yang disebutkan di atas adalah:

public enum Gender { MALE, FEMALE, UNSPECIFIED }

7.4. Menjana Kelas Dalaman Tanpa Nama

Untuk menjana kelas dalaman tanpa nama, kita boleh menggunakan anonymousClassBuilder () kaedah yang TypeSpec kelas. Perhatikan bahawa kita mesti menyatakan kelas induk dalam addSuperinterface () kaedah . Jika tidak, ia akan menggunakan kelas induk lalai, iaitu Objek :

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

Ini akan menghasilkan kod Java berikut:

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

8. Spesifikasi Anotasi

Untuk menambah anotasi kepada kod yang dihasilkan, kita boleh memanggil addAnnotation () kaedah dalam MethodSpec atau FieldSpec kelas builder:

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

Yang menjana:

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

Dalam kes kita perlu menentukan nilai anggota itu, kita boleh memanggil addMember () kaedah yang AnnotationSpec kelas:

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 menjadi pengantar kepada fungsi JavaPoet, seperti menghasilkan kaedah, bidang, parameter, jenis, anotasi, dan Javadocs.

JavaPoet direka untuk penjanaan kod sahaja. Sekiranya kami ingin melakukan pemrograman dengan Java, JavaPoet pada versi 1.10.0 tidak menyokong penyusunan kod dan berjalan.

Seperti biasa, contoh dan coretan kod boleh didapati di GitHub.