1. Обзор
В этом учебнике мы изумим основные функциональные возможности JavaPoet библиотека.
JavaPoet разработан компанией Квадратные , который предоставляет API для генерации исходных кодов Java . Он может генерировать примитивные типы, типы ссылок и их варианты (такие как классы, интерфейсы, перечисленные типы, анонимные внутренние классы), поля, методы, параметры, аннотации и Javadocs.
JavaPoet управляет импортом зависимых классов автоматически. Он также использует шаблон Builder для указать логику для создания Java-кода.
2. Зависимость от Maven
Для того, чтобы использовать JavaPoet, мы можем непосредственно скачать последние Файл JAR , или определить следующую зависимость в нашем пом.xml:
com.squareup javapoet 1.10.0
3. Спецификация метода
Во-первых, давайте рассмотрим спецификацию метода. Чтобы создать метод, мы просто называем методСтроитель () метод МетодСпец класс. Мы указать имя сгенерированного метода в качестве Струнные аргумент методСтроитель () метод.
Мы можем генерировать любое логическое заявление, заканчивающийся полукоголовой с помощью добавитьГосударство () метод. Между тем, мы можем определить один поток управления, связанный с фигурными скобками, такими как если-другой блок, или для петли, в потоке управления.
Вот быстрый пример – генерация 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();Это позволит вывести следующие результаты:
void sumOfTen() {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}4. Код блок
Мы также можем оберните один или несколько потоков управления и логических операторов в один блок кода :
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 sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addCode(sumOfTenImpl)
.build();Блок кода также применим к другим спецификациям, таким как типы и Javadocs.
5. Поле Спецификация
Далее – давайте рассмотрим логику спецификации поля.
Для создания поля мы используем строитель () метод FieldSpec класс:
FieldSpec name = FieldSpec .builder(String.class, "name") .addModifiers(Modifier.PRIVATE) .build();
Это позволит создать следующее поле:
private String name;
Мы также можем инициализировать значение поля по умолчанию, позвонив в инициализатор () метод:
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. Параметр спецификации
Давайте теперь рассмотрим логику спецификации параметров.
В случае, если мы хотим добавить параметр к методу, мы можем назвать добавитьПараметр () в цепочке функции вызывает в строитель.
В случае более сложных типов параметров, мы можем использовать ПараметрСпец строитель:
ParameterSpec strings = ParameterSpec
.builder(
ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)),
"strings")
.build();Мы также можем добавить модификатор метода, например, общественные и/или статический:
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, Liststrings) { int sum = 0; for (int i = 0; i <= 10; i++) { sum += i; } }
7. Спецификация типа
После изучения способов генерации методов, полей и параметров, теперь давайте посмотрим на спецификации типа.
Чтобы объявить тип, мы можем использовать ТипСпец которые могут создавать классы, интерфейсы и перечисленные типы .
7.1. Создание класса
Для создания класса мы можем использовать classBuilder () метод ТипСпец класс.
Мы также можем указать его модификаторы, например, общественные и окончательный модификаторы доступа. В дополнение к модификаторам классов, мы также можем указать поля и методы, используя уже упомянутые FieldSpec и МетодСпец Классы.
Обратите внимание, что addField () и добавитьМетод () методы также доступны при генерации интерфейсов или анонимных внутренних классов.
Рассмотрим следующий пример строителя классов:
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 мы используем интерфейсСтроитель () метод ТипСпец.
Мы также можем определить метод по умолчанию, указав ПО умолчанию значение модификатора в 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();Он будет генерировать следующий java-код:
public interface Person {
private static final String DEFAULT_NAME = "Alice";
void getName();
default void getDefaultName() {
return DEFAULT_NAME;
}
}7.3. Создание enum
Для создания перечисленного типа мы можем использовать 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 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. Спецификация аннотации
Чтобы добавить аннотацию к генерируемому коду, мы можем назвать addAnnotation () метод в МетодСпец или FieldSpec класс строитель:
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, Liststrings) { int sum = 0; for (int i = 0; i <= 10; i++) { sum += i; } }
В случае, если нам нужно указать значение участника, мы можем позвонить в addMember () метод АннотацияСпец класс:
AnnotationSpec toString = AnnotationSpec
.builder(ToString.class)
.addMember("exclude", "\"name\"")
.build();Это приведет к следующему аннотации:
@ToString(
exclude = "name"
)9. Создание Джавадоков
Javadoc может быть сгенерирован с помощью КодБлок, или путем указания значения напрямую:
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, Liststrings) { int sum = 0; for (int i = 0; i <= 10; i++) { sum += i; } }
10. Форматирование
Давайте перепроверим пример FieldSpec инициализатор в Раздел 5 который содержит побег символ используется для побега “Алиса” Струнные ценность:
initializer("\"Alice\"")Аналогичный пример имеется и в разделе 8, когда мы определяем исключенный член аннотации:
addMember("exclude", "\"name\"")Он становится громоздким, когда наш код JavaPoet растет и имеет много подобных Струнные бежать или Струнные конкатации заявления.
Функция форматирования строки в JavaPoet делает Струнные форматирование в начатьКонтрольПоток () , добавитьГосударство () или инициализатор () методы проще. Синтаксис похож на String.format () функциональности в Java. Это может помочь форматировать буквы, строки, типы и имена .
10.1. Буквальное форматирование
JavaPoet заменяет $L с буквальным значением в выходе. Мы можем указать любой примитивный тип и Струнные значения в аргументе (ы):
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. Форматирование струн
Струнные форматирование генерирует значение с кавычками, что относится исключительно к Струнные типа в Java. JavaPoet заменяет $S с Струнные значение в выходных :
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 обрабатывает тип в выписке по импорту автоматически. Если бы мы предоставили тип в качестве буквального вместо этого, JavaPoet не будет обрабатывать импорт.
MethodSpec getCurrentDateMethod = MethodSpec
.methodBuilder("getCurrentDate")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();JavaPoet будет генерировать следующий выход:
Date getCurrentDate() {
return new Date();
}10.4. Форматирование имен
На случай, если нам нужно ссылаться на имя переменной/параметр, поле или метод, мы можем использовать $N в JavaPoet Струнные форматер.
Мы можем добавить предыдущий 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. Например, код-блок, который печатает имя поле или переменная несколько раз:
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();Эта логика генерирует следующий вывод:
Listnames = new ArrayList<>(); IntStream.range(0, 10).forEach(i -> names.add(name)); names.forEach(System.out::println);
12. Производство вывода с использованием JavaFile
JavaFile класс помогает настроить и произвести выход сгенерированного кода . Для создания Java-кода мы просто создаем JavaFile, предоставить название пакета и экземпляр ТипСпец объект.
12.1. Отступ кода
По умолчанию JavaPoet использует два пространства для отступов. Чтобы сохранить согласованность, все примеры в этом учебнике были представлены с 4 пробелов отступ, который настроен с помощью отступ () метод:
JavaFile javaFile = JavaFile
.builder("com.baeldung.javapoet.person", person)
.indent(" ")
.build();12.2. Статический импорт
В случае, если нам нужно добавить статический импорт, мы можем определить тип и конкретное название метода в JavaFile позвонив в addStaticImport () метод:
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 ) и Файл .
Чтобы записать 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 path = Paths.get(destinationPath); javaFile.writeTo(path);
Для получения более подробной информации о JavaFile , пожалуйста, обратитесь к Джавадок .
13. Заключение
Эта статья была введением в функции JavaPoet, такие как генерация методов, полей, параметров, типов, аннотаций и Javadocs.
JavaPoet предназначен только для генерации кода. В случае, если мы хотели бы сделать метапрограммирование с Java, JavaPoet по версии 1.10.0 не поддерживает компиляцию кода и работает.
Как всегда, примеры и фрагменты кода доступны более на GitHub .