1. Overview
1.概述
In this tutorial, we’ll explore the basic functionalities of the JavaPoet library.
在本教程中,我们将探讨JavaPoet>库的基本功能。
JavaPoet is developed by Square, which provides APIs to generate Java source code. It can generate primitive types, reference types and their variants (such as classes, interfaces, enumerated types, anonymous inner classes), fields, methods, parameters, annotations, and Javadocs.
JavaPoet是由Square开发的,它提供了生成Java源代码的API。它可以生成原始类型、引用类型及其变体(如类、接口、枚举类型、匿名内类)、字段、方法、参数、注释和Javadocs。
JavaPoet manages the import of the dependent classes automatically. It also uses the Builder pattern to specify the logic to generate Java code.
JavaPoet自动管理依赖类的导入。它还使用生成器模式来指定生成Java代码的逻辑。
2. Maven Dependency
2.Maven的依赖性
In order to use JavaPoet, we can directly download the latest JAR file, or define the following dependency in our pom.xml:
为了使用JavaPoet,我们可以直接下载最新的JAR文件,或者在我们的pom.xml中定义以下依赖关系:。
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.10.0</version>
</dependency>
3. Method Specification
3.方法规范
First, let’s go through the method specification. To generate a method, we simply call the methodBuilder() method of MethodSpec class. We specify the generated method name as a String argument of the methodBuilder() method.
首先,让我们来看看方法的规范。要生成一个方法,我们只需调用MethodSpec类的methodBuilder()方法。我们将生成的方法名称指定为String方法的一个参数methodBuilder()。
We can generate any single logical statement ending with the semi-colon using the addStatement() method. Meanwhile, we can define one control flow bounded with curly brackets, such as if-else block, or for loop, in a control flow.
我们可以使用addStatement()方法,生成任何以分号结尾的单一逻辑语句。同时,我们可以在一个控制流中定义一个以大括号为界的控制流,如if-else块,或for循环。
Here’s a quick example – generating the sumOfTen() method which will calculate the sum of numbers from 0 to 10:
这里有一个快速的例子–生成sumOfTen()方法,它将计算0到10的数字之和。
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addStatement("int sum = 0")
.beginControlFlow("for (int i = 0; i <= 10; i++)")
.addStatement("sum += i")
.endControlFlow()
.build();
This will produce the following output:
这将产生以下输出。
void sumOfTen() {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
4. Code Block
4.代码块
We can also 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();
Which generates:
这就产生了。
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
We can simplify the earlier logic in the MethodSpec by calling addCode() and providing the sumOfTenImpl object:
我们可以通过调用addCode()并提供sumOfTenImpl对象来简化MethodSpec中先前的逻辑。
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addCode(sumOfTenImpl)
.build();
A code block is also applicable to other specifications, such as types and Javadocs.
一个代码块也适用于其他规范,如类型和Javadocs。
5. Field Specification
5.领域规范
Next – let’s explore the field specification logic.
接下来–让我们来探讨一下字段规范逻辑。
In order to generate a field, we use the builder() method of the FieldSpec class:
为了生成一个字段,我们使用builder()类的FieldSpec方法。
FieldSpec name = FieldSpec
.builder(String.class, "name")
.addModifiers(Modifier.PRIVATE)
.build();
This will generate the following field:
这将产生以下字段。
private String name;
We can also initialize the default value of a field by calling the initializer() method:
我们也可以通过调用initializer()方法来初始化一个字段的默认值。
FieldSpec defaultName = FieldSpec
.builder(String.class, "DEFAULT_NAME")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("\"Alice\"")
.build();
Which generates:
这就产生了。
private static final String DEFAULT_NAME = "Alice";
6. Parameter Specification
6.参数规格
Let’s now explore the parameter specification logic.
现在我们来探讨一下参数规范逻辑。
In case we want to add a parameter to the method, we can call the addParameter() within the chain of the function calls in the builder.
如果我们想给方法添加一个参数,我们可以在构建器的函数调用链中调用addParameter()。
In case of more complex parameter types, we can make use of ParameterSpec builder:
如果是更复杂的参数类型,我们可以使用ParameterSpec构建器。
ParameterSpec strings = ParameterSpec
.builder(
ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)),
"strings")
.build();
We can also add the modifier of the method, such as public and/or static:
我们还可以添加方法的修饰语,如public和/或static:。
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addParameter(int.class, "number")
.addParameter(strings)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addCode(sumOfTenImpl)
.build();
Here’s how the generated Java code looks like:
下面是生成的Java代码的样子。
public static void sumOfTen(int number, List<String> strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
7. Type Specification
7.类型规格
After exploring the ways to generate methods, fields, and parameters, now let’s take a look at the type specifications.
在探索了生成方法、字段和参数的方法后,现在让我们来看看类型规范。
To declare a type, we can use the TypeSpec which can build classes, interfaces, and enumerated types.
为了声明一个类型,我们可以使用TypeSpec,它可以构建类、接口和枚举类型。
7.1. Generating a Class
7.1.生成一个类
In order to generate a class, we can use the classBuilder() method of the TypeSpec class.
为了生成一个类,我们可以使用classBuilder()类的TypeSpec方法。
We can also specify its modifiers, for instance, public and final access modifiers. In addition to class modifiers, we can also specify fields and methods using already mentioned FieldSpec and MethodSpec classes.
我们还可以指定它的修饰符,例如,public和final访问修饰符。除了类的修改器,我们还可以使用已经提到的FieldSpec和MethodSpec类指定字段和方法。
Note that addField() and addMethod() methods are also available when generating interfaces or anonymous inner classes.
请注意,addField()和addMethod()方法在生成接口或匿名内类时也可用。
Let’s take a look at the following class builder example:
让我们来看看下面这个建类的例子。
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();
And here’s how the generated code looks like:
下面是生成的代码的样子。
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. Generating an Interface
7.2.生成一个界面
To generate a Java interface, we use the interfaceBuilder() method of the TypeSpec.
为了生成一个Java接口,我们使用interfaceBuilder()方法的TypeSpec。TypeSpec的interfaceBuilder方法。
We can also define a default method by specifying DEFAULT modifier value in the addModifiers():
我们也可以通过在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();
It will generate the following Java code:
它将生成以下Java代码。
public interface Person {
private static final String DEFAULT_NAME = "Alice";
void getName();
default void getDefaultName() {
return DEFAULT_NAME;
}
}
7.3. Generating an Enum
7.3.生成一个枚举
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的enumBuilder()/em>方法。为了指定每个枚举值,我们可以调用addEnumConstant()方法。
TypeSpec gender = TypeSpec
.enumBuilder("Gender")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("MALE")
.addEnumConstant("FEMALE")
.addEnumConstant("UNSPECIFIED")
.build();
The output of the aforementioned enumBuilder() logic is:
前面提到的enumBuilder()逻辑的输出是。
public enum Gender {
MALE,
FEMALE,
UNSPECIFIED
}
7.4. Generating an Anonymous Inner Class
7.4.生成一个匿名的内部类
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类的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();
This will generate the following Java code:
这将产生以下Java代码。
new Comparator<String>() {
public int compare(String a, String b) {
return a.length() - b.length();
}
});
8. Annotation Specification
8.注释规范
To add an annotation to generated code, we can call the addAnnotation() method in a MethodSpec or FieldSpec builder class:
为了向生成的代码添加注释,我们可以在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();
Which generates:
这就产生了。
@Override
public static void sumOfTen(int number, List<String> 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:
如果我们需要指定成员值,我们可以调用addMember()类的AnnotationSpec方法。
AnnotationSpec toString = AnnotationSpec
.builder(ToString.class)
.addMember("exclude", "\"name\"")
.build();
This will generate the following annotation:
这将产生以下注释。
@ToString(
exclude = "name"
)
9. Generating Javadocs
9.生成Javadocs
Javadoc can be generated using CodeBlock, or by specifying the value directly:
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();
This will generate the following Java code:
这将产生以下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. Formatting
10.格式化
Let’s recheck the example of the FieldSpec initializer in Section 5 which contains an escape char used to escape the “Alice” String value:
让我们重新检查一下第5节中的FieldSpec初始化器的例子,它包含一个用于转义 “Alice”String值的转义符。
initializer("\"Alice\"")
There is also a similar example in Section 8 when we define the excluded member of an annotation:
在第8节中也有一个类似的例子,当我们定义一个注释的排除成员时。
addMember("exclude", "\"name\"")
It becomes unwieldy when our JavaPoet code grows and has a lot of similar String escape or String concatenation statements.
当我们的JavaPoet代码增长并有很多类似的String转义或String连接语句时,它就变得不方便了。
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.
JavaPoet中的字符串格式化功能使String在beginControlFlow()、addStatement()或initializer()方法中的格式化更容易。该语法类似于Java中的String.format()功能。它可以帮助格式化字面符号、字符串、类型和名称。
10.1. Literal Formatting
10.1.字面格式化
JavaPoet replaces $L with a literal value in the output. We can specify any primitive type and String values in the argument(s):
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();
}
In case we call the generateSumMethod() with the following values specified:
如果我们调用generateSumMethod(),并指定以下值。
generateSumMethod("sumOfOneHundred", 0, 100, "+");
JavaPoet will generate the following output:
JavaPoet将产生以下输出。
int sumOfOneHundred() {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
10.2. String Formatting
10.2.字符串格式化
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:
String格式化生成的值带有引号,这在Java中专指String类型。JavaPoet在输出中用String值替换了$S。
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:
如果我们调用generateGetter()方法并提供这些值。
generateStringSupplier("getDefaultName", "Bob");
We will get the following generated Java code:
我们将得到以下生成的Java代码。
String getDefaultName() {
return "Bob";
}
10.3. Type Formatting
10.3.类型格式化
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.
JavaPoet将$T替换为生成的Java代码中的一个类型。JavaPoet会自动处理导入语句中的类型。如果我们以字面形式提供类型,JavaPoet就不会处理导入的问题。
MethodSpec getCurrentDateMethod = MethodSpec
.methodBuilder("getCurrentDate")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
JavaPoet will generate the following output:
JavaPoet将产生以下输出。
Date getCurrentDate() {
return new Date();
}
10.4. Name Formatting
10.4.名称格式化
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.
如果我们需要引用一个变量/参数、字段或方法的名称,我们可以在JavaPoet的String格式化中使用$N/strong>。
We can add the previous getCurrentDateMethod() to the new referencing method:
我们可以将之前的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();
Which generates:
这就产生了。
String getCurrentDateAsString() {
DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
return formatter.format(getCurrentDate());
}
11. Generating Lambda Expressions
11.生成Lambda表达式
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:
我们可以利用我们已经探索过的功能来生成一个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();
That logic generates the following output:
该逻辑产生了以下输出。
List<String> names = new ArrayList<>();
IntStream.range(0, 10).forEach(i -> names.add(name));
names.forEach(System.out::println);
12. Producing the Output Using JavaFile
12.使用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.
JavaFile类有助于配置和产生生成代码的输出。为了生成Java代码,我们只需构建JavaFile,提供包的名称和TypeSpec对象的实例。
12.1. Code Indentation
12.1.代码缩进
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:
默认情况下,JavaPoet使用两个空格来缩进。为了保持一致性,本教程中的所有例子都使用4个空格缩进,这可以通过indent()方法进行配置。
JavaFile javaFile = JavaFile
.builder("com.baeldung.javapoet.person", person)
.indent(" ")
.build();
12.2. Static Imports
12.2.静态进口
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:
如果我们需要添加一个静态导入,我们可以通过调用addStaticImport()方法,在JavaFile中定义类型和具体的方法名称。
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
12.3.输出
The writeTo() method provides functionality to write the code into multiple targets, such as standard output stream (System.out) and File.
writeTo()方法提供了将代码写入多个目标的功能,如标准输出流(System.out)和File。
To write Java code to a standard output stream, we simply call the writeTo() method, and provide the System.out as the argument:
要将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);
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:
writeTo()方法也接受java.nio.file.Path和java.io.File。我们可以提供相应的Path或File对象,以便将Java源代码文件生成到目标文件夹/路径。
Path path = Paths.get(destinationPath);
javaFile.writeTo(path);
For more detailed information regarding JavaFile, please refer to the Javadoc.
有关JavaFile的更多详细信息,请参考Javadoc>。
13. Conclusion
13.结论
This article has been an introduction to JavaPoet functionalities, like generating methods, fields, parameters, types, annotations, and Javadocs.
本文是对JavaPoet功能的介绍,如生成方法、字段、参数、类型、注释和Javadocs。
JavaPoet is designed for code generation only. In case we would like to do metaprogramming with Java, JavaPoet as of version 1.10.0 doesn’t support code compilation and running.
JavaPoet只为代码生成而设计。如果我们想用Java做元编程,JavaPoet在1.10.0版本中不支持代码编译和运行。
As always, the examples and code snippets are available over on GitHub.
像往常一样,例子和代码片段可在GitHub上获得。