Skip to the content.

Технологии программирования

Назад на главную

Аннотации и аспектно-ориентированное программирование. Lombok. Record. Работа с JSON.

Аннотации в java

Аннотации в Java — это специальные конструкции, которые предоставляют дополнительную информацию для компилятора, инструментов разработки и во время выполнения программы. Они не влияют на выполнение кода напрямую, но могут изменять процесс компиляции, генерации кода или поведение приложения в runtime.

Что такое аннотации?

Аннотации — это своего рода «метки» или «теги», которые можно применять к классам, методам, полям и другим элементам программы. Они начинаются с символа @ и могут содержать атрибуты, которые определяют их поведение.

Пример:

@Override
public void someMethod() {
    // тело метода
}

Зачем нужны аннотации?

  1. Упрощение разработки: аннотации помогают автоматизировать некоторые задачи, такие как генерация кода, проверка типов и т. д.
  2. Улучшение читаемости кода: они делают код более понятным, указывая на его назначение или особенности.
  3. Интеграция с инструментами разработки: многие IDE и инструменты сборки используют аннотации для предоставления дополнительных функций, таких как рефакторинг, анализ кода и т. п.
  4. Поддержка фреймворков и библиотек: аннотации часто используются в фреймворках и библиотеках для конфигурации и управления поведением компонентов.

Как написать свою аннотацию?

Для создания собственной аннотации необходимо использовать ключевое слово interface вместе с @interface. Внутри аннотации можно определить атрибуты, которые будут хранить дополнительную информацию.

Пример определения аннотации:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "default value";
    int id();
}

Здесь:

С какими концепциями программирования связаны аннотации?

Аннотации в Java — это мощный инструмент для улучшения процесса разработки, повышения читаемости кода и интеграции с различными инструментами и фреймворками. Понимание того, как работают аннотации и как их использовать, позволяет разработчикам более эффективно создавать и поддерживать свои приложения.

Аспектно-ориентированное программирование (АОП)

Аспектно-ориентированное программирование (АОП) — это подход к написанию программ, который позволяет выделить и отдельно описать так называемые «поперечные concerns» или «аспекты». Это могут быть функции, которые пересекаются с основной логикой программы и применяются в разных её частях, например, логирование, обработка ошибок, управление транзакциями или безопасность.

Как работает АОП?

В АОП используются специальные конструкции — аннотации, которые указывают, где и как должен быть применён аспект. Например, вы можете определить аспект для логирования и указать, что он должен применяться ко всем методам, помеченным определённой аннотацией.

Это позволяет:

Пример

Рассмотрим простой пример, который иллюстрирует основные идеи аспектно-ориентированного программирования (АОП). Предположим, у нас есть программа, которая выполняет простые арифметические операции: сложение и умножение. Мы хотим добавить логирование времени выполнения каждой операции без изменения исходного кода этих операций.

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public int multiply(int a, int b) {
        return a * b;
    }
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@interface LogExecutionTime {
}

public class Calculator {

    @LogExecutionTime
    public int add(int a, int b) {
        return a + b;
    }

    @LogExecutionTime
    public int multiply(int a, int b) {
        return a * b;
    }
}

Сам аспект мы не будем реализовывать - его можно реализовать с помощью AspectJ. AspectJ — это расширение языка Java, которое позволяет реализовывать аспектно-ориентированное программирование (АОП).

Библиотека Lombok в Java

Библиотека Lombok — это инструмент, который упрощает разработку на Java, автоматизируя создание шаблонного кода. Она позволяет сократить количество строк кода и сделать его более читаемым, устраняя необходимость вручную реализовывать стандартные методы, такие как геттеры, сеттеры, конструкторы и т. д.

Что такое Lombok?

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

Зачем нужна Lombok?

Примеры использования Lombok

Генерация геттеров и сеттеров без Lombok:

public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

С lombok

import lombok.Getter;
import lombok.Setter;

public class Person {
    @Getter @Setter
    private String name;
    @Getter @Setter
    private int age;
}

Как добавить в проект:

plugins {
    id 'io.franzbecker.gradle-lombok' version '5.0.0'
    id 'java'
}

lombok {
    version = '1.18.26'
    sha256 = ""
}

Возможности

@Getter @Setter

@Getter
@Setter
public class Author {
    private int id;
    private String name;
    @Setter(AccessLevel.PROTECTED)
    private String surname;
}

public class Author {
    private int id;
    private String name;
    private String surname;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    protected void setSurname(String surname) {
        this.surname = surname;
    }
}

@NoArgsConstructor - создает дефолтный конструктор @AllArgsConstructor - создает конструктор, где все поля класса - аргументы

@NoArgsConstructor
@AllArgsConstructor
public class Author {
    private int id;
    private String name;
    private String surname;
    private final String birthPlace;
}

public class Author {
    private int id;
    private String name;
    private String surname;
    private final String birthPlace;

    // @NoArgsConstructor
    public Author() {}

    // @AllArgsConstructor
    public Author(int id, String name, String surname, String birthPlace) {
        this.id = id
        this.name = name
        this.surname = surname
        this.birthPlace = birthPlace
    }

    // @RequiredArgsConstructor
    public Author(String birthPlace) {
        this.birthPlace = birthPlace
    }
}

@ToString - создает метод ToString

@ToString(includeFieldNames=true)
public class Author {
    private int id;
    private String name;
    private String surname;
}

public class Author {
    private int id;
    private String name;
    private String surname;

    @Override
    public String toString() {
        return "Author(id=" + this.id + ", name=" + this.name + ", surnname=" + this.surname + ")";
    }
}
@Getter
@Setter
@EqualsAndHashCode
public class Author {
    private int id;
    private String name;
    private String surname;
}

public class Author {

    // геттеры и сеттеры ...

    @Override
    public int hashCode() {
        final int PRIME = 31;
        int result = 1;
        result = prime * result + id;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((surname == null) ? 0 : surname.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Author)) return false;
        Author other = (Author) o;
        if (!other.canEqual((Object)this)) return false;
        if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false;
        if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
        if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false;
        return true;
    }
}

@Data - сокращенная аннотация сочетающая @ToString, @EqualsAndHashCode, @Getter @Setter и @RequiredArgsConstructor.

@Getter
@Builder
public class Student {
    private String firstName;
    private String lastName;
    private Long studentId;
    private String email;
    private String phoneNumber;
    private String address;
    private String country;
    private int age;
}

class Temp {
    Student john = Student.builder()
            .firstName("John")
            .lastName("Doe")
            .email("john@doe.com")
            .country("England")
            .age(20)
            .build();
}

Плюсы Lombok

Недостатки Lombok

Полезные ссылки

Статья Lombok. Полное руководство

Статья Lombok возвращает величие Java

Статья Lombok: хорошее и плохое применение

Record

Record — это относительно новая конструкция в языке Java, которая упрощает создание неизменяемых данных (immutable data). Она появилась в Java 16 как часть усилий по упрощению и улучшению языка.

Зачем нужны Record?

Когда появились Record?

Record были введены в Java 16 в марте 2021 года как часть проекта Amber, направленного на улучшение и упрощение языка Java.

Как раскрываются Record после компиляции?

После компиляции Record раскрывается в обычный класс с некоторыми автоматически сгенерированными методами:

Пример записи:

public record Person(String name, int age) {
    // автоматически генерируются геттеры, equals, hashCode и toString
}

Компилятор преобразует этот код в полноценный класс с соответствующими методами, что позволяет разработчикам сосредоточиться на логике приложения, а не на реализации базовых функциональностей.

Пример того, как будет выглядеть класс Person, созданный с использованием record, после компиляции в обычный класс Java:

import java.util.Objects;

public final class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String name() {
        return name;
    }

    public int age() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" + "name=" + name + ", age=" + age + '}';
    }
}

Наследоваться от Record в Java нельзя, поскольку Record по умолчанию является final классом. Это сделано для обеспечения неизменяемости и простоты использования, что является одной из ключевых особенностей Record. Запрет на наследование помогает предотвратить потенциальные проблемы, связанные с нарушением инвариантов и изменением состояния объекта, что противоречит концепции неизменяемых данных.

Работа с JSON.

JSON (JavaScript Object Notation) — это формат обмена данными, который используется для хранения и передачи структурированной информации. Он основан на синтаксисе объектов JavaScript и представляет данные в виде текстовых объектов, что делает его лёгким для чтения как для человека, так и для машины. JSON широко применяется в веб-разработке для обмена данными между сервером и клиентом, а также между различными приложениями.

С помощью JSON можно отправлять данные с сервера на веб-страницу.

Пример:

{
  "name": "yandex",
  "id": 0
}

JSON обладает несколькими ключевыми особенностями:

В Java существует несколько подходов к работе с JSON:

Парсинг JSON:

Сериализация / десериализация объектов:

Json Simple

Json Simple — это библиотека для работы с JSON в Java, которая предоставляет простой и удобный способ парсить JSON-данные, а также создавать и манипулировать JSON-объектами.

Основные возможности Json Simple:

Конечно нам потребуется зависимость в gradle-файле (maven)

implementation 'com.googlecode.json-simple:json-simple:1.1'

Мы будем работать с 2-мя основным классами:

class Temp {
    public static String buildWeatherJson() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", "Yandex");
        jsonObject.put("id", "0");

        return jsonObject.toJSONString();
    }

    public static void parseCurrentWeatherJson(String resultJson) {
        try {
            // конвертируем строку с Json в JSONObject для дальнейшего его парсинга
            JSONObject companyJsonObject = (JSONObject) JSONValue.parseWithException(resultJson);

            // получаем название города, для которого смотрим погоду
            System.out.println("Название компании: " + companyJsonObject.get("name"));

            // получаем массив элементов для поля weather
            /* ... "weather": [
            {
                "id": 500,
                    "main": "Rain",
                    "description": "light rain",
                    "icon": "10d"
            }
            ], ... */
            JSONArray weatherArray = (JSONArray) weatherJsonObject.get("weather");
            // достаем из массива первый элемент
            JSONObject weatherData = (JSONObject) weatherArray.get(0);

            // печатаем текущую погоду в консоль
            System.out.println("Погода на данный момент: " + weatherData.get("main"));
            // и описание к ней
            System.out.println("Более детальное описание погоды: " + weatherData.get("description"));

        } catch (org.json.simple.parser.ParseException e) {
            e.printStackTrace();
        }
    }
}

Jackson

Jackson — это популярная библиотека для работы с JSON в Java, которая предоставляет мощные возможности для сериализации и десериализации объектов. Она позволяет преобразовывать Java-объекты в JSON-формат и обратно, что упрощает обмен данными между приложениями и хранение информации.

Основные возможности Jackson:

Подключение в gradle

implementation 'com.fasterxml.jackson.core:jackson-core:2.10.1'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.10.1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.1'
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
    private String firstName;
    private String lastName;
    private int age;
}

public class JacksonExample {
    static ObjectMapper objectMapper = new ObjectMapper();

    public static void main(String[] args) {
        Employee employee = new Employee("Mark", "James", 20);

        try {
            String json = objectMapper.writeValueAsString(employee);
            System.out.println(json);
        } catch (JsonProcessingException e) {
            throw new RuntimeException();
        }

        try {
            Employee employee1 = objectMapper.readValue(employeeJson, Employee.class);
            System.out.println(employee.getFirstName());
            System.out.println(employee.getLastName());
            System.out.println(employee.getAge());
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}

Иногда документ JSON представляет собой не объект, а список объектов. Давайте посмотрим, как можно его прочитать.

class Temp {
    public static void main(String[] args) {
        File file = new File("src/test/resources/employeeList.json");
        List<Employee> employeeList = objectMapper.readValue(file, new TypeReference<>(){});

        assertThat(employeeList).hasSize(2);
        assertThat(employeeList.get(0).getAge()).isEqualTo(33);
        assertThat(employeeList.get(0).getLastName()).isEqualTo("Simpson");
        assertThat(employeeList.get(0).getFirstName()).isEqualTo("Marge");
    }
}

У Jackson есть свои аннотации: @JsonSetter @JsonGetter @JsonIgnore

@JsonAnySetter

public class Car {  
    @JsonSetter("car_brand")  
    private String carBrand;  
    private Map<String, String> unrecognizedFields = new HashMap<>();  
  
    @JsonAnySetter  
    public void allSetter(String fieldName, String fieldValue) {  
        unrecognizedFields.put(fieldName, fieldValue);  
    }  
}

Полезные ссылки

Парсинг JSON с помощью Jackson