Рубрики
Без рубрики

Введение в JavaPoet

Узнайте, как можно создать Java-код с помощью библиотеки JavaPoet

Автор оригинала: baeldung.

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, List strings) {
    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, List strings) {
    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, List strings) {
    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();

Эта логика генерирует следующий вывод:

List names = 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 .