Как создать классы данных на Java

kak sozdat klassy dannyh na java

Kotlin имеет сжатый синтаксис для объявления классов данных:

data class User(val name: String, val age: Int)

Эквивалентный синтаксис Java многословен. Вам нужно создать класс Java с приватными полями. И getter и setter методы для полей И дополнительные методы типа equals(), hashCode() и toString().

Но кто сказал, что код Java нужно создавать вручную?

В этой статье я покажу вам, как создать исходные файлы Java из YAML.

Вот пример файла YAML:

User:
    name: Name
    age: Integer

Name:
    firstName: String
    lastName: String

Примером выхода генератора кода являются два исходных файла Java, User.java и Name.java.

public class User{
    private Name name;
    private Integer age;
    
    public User(){
    }
    public Name getName(){
        return name;
    }
    public void setName(Name name){
        this.name = name;
    }
    public Integer getAge(){
        return age;
    }
    public void setAge(Integer age){
        this.age = age;
    }
}

Name.java похож.

Суть настоящей статьи: вы узнаете, как запрограммировать генератор кода с нуля. И его легко адаптировать под свои нужды.

Основной метод

The main() метод делает две вещи:

  • Шаг 1: Прочтите в файле YAML спецификации класса
  • Шаг 2: Сгенерируйте исходные файлы Java по спецификациям класса

Он разъединяет чтение и сочинение. Вы можете изменить формат ввода в будущем или поддерживать другие форматы ввода.

Вот main() метод:

public static void main(String[] args) throws Exception {
    // Make sure there is exactly one command line argument, 
    // the path to the YAML file
    if (args.length != 1) {
        System.out.println("Please supply exactly one argument, the path to the YAML file.");
        return;
    }
  
    // Get the YAML file's handle & the directory it's contained in
    // (generated files will be placed there)
    final String yamlFilePath = args[0];
    final File yamlFile = new File(yamlFilePath);
    final File outputDirectory = yamlFile.getParentFile();
  
    // Step 1: Read in the YAML file, into class specifications
    YamlClassSpecificationReader yamlReader = new YamlClassSpecificationReader();
    List<ClassSpecification> classSpecifications = yamlReader.read(yamlFile);
    
    // Step 2: Generate Java source files from class specifications
    JavaDataClassGenerator javaDataClassGenerator = new JavaDataClassGenerator();
    javaDataClassGenerator.generateJavaSourceFiles(classSpecifications, outputDirectory);
    
    System.out.println("Successfully generated files to: " + outputDirectory.getAbsolutePath());
}

Шаг 1: Прочтите файл YAML в спецификациях класса

Позвольте мне объяснить, что происходит в этой строке:

List<ClassSpecification> classSpecifications =  yamlReader.read(yamlFile);

Спецификация класса — это определение созданного класса и его полей.
Помните о User в примере файла YAML?

User:
    name: Name
    age: Integer

Когда читатель YAML прочтет это, он создаст его ClassSpecification объекта с названием User. И эта спецификация класса ссылается на два FieldSpecification предметы, наз name и age.

Код для ClassSpecification класс и FieldSpecification класс прост.

Содержание ClassSpecification.java показано ниже:

public class ClassSpecification {
    private String name;
    private List<FieldSpecification> fieldSpecifications;
  
    public ClassSpecification(String className, List<FieldSpecification> fieldSpecifications) {
        this.name = className;
        this.fieldSpecifications = fieldSpecifications;
    }
  
    public String getName() {
        return name;
    }
  
    public List<FieldSpecification> getFieldSpecifications() {
        return Collections.unmodifiableList(fieldSpecifications);
    }
}

Содержание FieldSpecification.java есть:

public class FieldSpecification {
    private String name;
    private String type;
  
    public FieldSpecification(String fieldName, String fieldType) {
        this.name = fieldName;
        this.type = fieldType;
    }
  
    public String getName() {
        return name;
    }
  
    public String getType() {
        return type;
    }
}

Единственный вопрос, оставшийся для шага 1: как перейти от файла YAML к объектам этих классов?

Считыватель YAML использует библиотеку SnakeYAML для анализа файлов YAML.
SnakeYAML делает содержимое файла YAML доступным в структурах данных, таких как карты и списки.

Для этой статьи вам нужно только разобраться в картах – потому что это то, что мы используем в файлах YAML.

Посмотрите еще раз на пример:

User:
    name: Name
    age: Integer

Name:
    firstName: String
    lastName: String

Здесь вы видите две вложенные карты.

Ключом внешней карты является имя класса (например User).

Когда вы получите значение для User ключ, вы получите карту полей класса:

name: Name
age: Integer

Ключ этой внутренней карты — имя поля, а значение — тип поля.

Это карта строк в карту строк в строке. Это важно для понимания кода читателя YAML.

Вот метод, читающий полное содержимое файла YAML:

private Map<String, Map<String, String>> readYamlClassSpecifications(Reader reader) {
	Yaml yaml = new Yaml();

	// Read in the complete YAML file to a map of strings to a map of strings to strings
	Map<String, Map<String, String>> yamlClassSpecifications = 
		(Map<String, Map<String, String>>) yaml.load(reader);

	return yamlClassSpecifications;
}

С yamlClassSpecifications как входные данные, считыватель YAML создает файл ClassSpecification объекты:

private List<ClassSpecification> createClassSpecificationsFrom(Map<String, Map<String, String>> yamlClassSpecifications) {
	final Map<String, List<FieldSpecification>> classNameToFieldSpecificationsMap 
		= createClassNameToFieldSpecificationsMap(yamlClassSpecifications);

	List<ClassSpecification> classSpecifications = 
		classNameToFieldSpecificationsMap.entrySet().stream()
			.map(e -> new ClassSpecification(e.getKey(), e.getValue()))
			.collect(toList());

	return classSpecifications;
}

The createClassNameToFieldSpecificationsMap() метод создает

  • полевые спецификации для каждого класса и на их основе
  • сопоставление названия каждого класса со спецификациями его поля.

Затем средство чтения YAML создает файл a ClassSpecification объект для каждой записи на карте.

Содержимое файла YAML теперь доступно для шага 2 независимо от YAML. Мы закончили шаг 1.

Шаг 2: Сгенерируйте исходные файлы Java по спецификациям класса

Apache FreeMarker – это движок шаблонов Java, создающий текстовый выход. Шаблоны написаны на языке шаблонов FreeMarker (FTL). Это позволяет статическому тексту смешиваться с содержимым объектов Java.

Вот шаблон для создания исходных файлов Java, javadataclass.ftl:

public class ${classSpecification.name}{
<#list classSpecification.fieldSpecifications as field>
    private ${field.type} ${field.name};
</#list>
    public ${classSpecification.name}(){
    }
<#list classSpecification.fieldSpecifications as field>
    public ${field.type} get${field.name?cap_first}(){
        return ${field.name};
    }
    public void set${field.name?cap_first}(${field.type} ${field.name}){
        this.${field.name} = ${field.name};
    }
</#list>    
}

Давайте посмотрим на первую строчку:

public class ${classSpecification.name}{

Вы можете увидеть, что он начинается со статического текста объявления класса: public class. Интересный фрагмент находится посередине: ${classSpecification.name}.

Когда Freemarker обрабатывает шаблон, он получает доступ к classSpecification объект в своей модели. Это называет getName() метод на нем.

А как насчет этой части шаблона?

<#list classSpecification.fieldSpecifications as field>
    private ${field.type} ${field.name};
</#list>

Сначала звонит по телефону Freemarker classSpecification.getFieldSpecifications(). Затем он берет на себя спецификации поля.

И последнее. Эта строка немного странна:

public ${field.type} get${field.name?cap_first}(){

Скажем, пример поля age: Integer (в ЯМЛ). Freemarker переводит это на:

public Integer getAge(){

Поэтому ?cap_first означает: писать первую букву с прописной, как содержится в файле YAML age строчными буквами.

Достаточно шаблонов. Как вы создаете исходные файлы Java?

Сначала вам нужно настроить FreeMarker, создав файл Configuration экземпляр. Это происходит в конструкторе JavaDataClassGenerator:

Чтобы создать исходные файлы, файл JavaDataClassGenerator перебирает спецификации класса и создает исходный файл для каждого:

И все это.

Вывод

Я показал вам, как создать генератор исходного кода Java на основе файлов YAML. Я выбрал YAML, потому что его легко обрабатывать и, следовательно, легко учить. Вы можете заменить его другим форматом, если хотите.

Полный код можно найти на Github.

Чтобы сделать код максимально понятным, я использовал несколько ярлыков:

  • таких методов нет equals(), hashCode() и toString()
  • отсутствие наследования классов данных
  • сгенерированные классы Java находятся в пакете по умолчанию
  • выходной каталог такой же, как и входной
  • обработка ошибок не была моим вниманием

Решить эти проблемы нужно готовое для производства решение. Кроме того, для классов данных Project Lombok является альтернативой без генерации кода.

Поэтому воспринимайте эту статью как начало, а не конец. Представьте, что это возможно. Несколько примеров:

  • scaffold JPA классы сущностей или репозитории Spring
  • создать несколько классов из одной спецификации на основе шаблонов в вашем приложении
  • генерировать код на разных языках программирования
  • изготовить документацию

Сейчас я использую этот подход для перевода требований природного языка
непосредственно в коде для исследовательских целей. Что ты будешь делать?

Если вы хотите знать, что я хватаю, посетите мой проект GitHub.

Вы можете связаться со мной через Twitter или LinkedIn.

Оригинальная версия этой статьи была размещена на dev.to

Добавить комментарий

Ваш адрес email не будет опубликован.