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, Liststrings) { 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 .