Java | Фишки и трюки
7.21K subscribers
182 photos
29 videos
6 files
40 links
Java: примеры кода, интересные фишки и полезные трюки

Купить рекламу: https://telega.in/c/java_tips_and_tricks

✍️По всем вопросам: @Pascal4eg
Download Telegram
💎Enum как полноценные классы с методами и интерфейсами

Enum в Java — это не просто перечисление констант. Это полноценные классы с конструкторами, методами и поддержкой ООП.

💠 1. Enum с абстрактными методами

public enum FileFormat {
    CSV {
        public String parse(String data) {
            return data.replace(",", ";");
        }
    },
    JSON {
        public String parse(String data) {
            return "{" + data + "}";
        }
    };
   
    public abstract String parse(String data);
}



💠 2. Реализация интерфейсов 

public interface Formatter {
    String format(String text);
}

public enum TextFormat implements Formatter {
    UPPERCASE {
        public String format(String text) {
            return text.toUpperCase();
        }
    },
    LOWERCASE {
        public String format(String text) {
            return text.toLowerCase();
        }
    };
}

// Пример вызова
String result = TextFormat.UPPERCASE.format("Hello"); // "HELLO"



💠 3. Состояния и полиморфизм 

public enum OrderStatus {
    NEW(1) {
        public OrderStatus next() {
            return PROCESSING;
        }
    },
    PROCESSING(2) {
        public OrderStatus next() {
            return SHIPPED;
        }
    },
    SHIPPED(3) {
        public OrderStatus next() {
            return COMPLETED;
        }
    };

    private final int code;

    OrderStatus(int code) {
        this.code = code;
    }

    public abstract OrderStatus next();
}



💠 4. Enum в JPA-сущностях 

@Entity
public class Order {
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
}

// Сохраняется как строка "NEW", "PROCESSING" в БД



💠 5. Поиск по значению 

public static OrderStatus findByCode(int code) {
    return Arrays.stream(OrderStatus.values())
        .filter(status -> status.code == code)
        .findFirst()
        .orElseThrow();
}



⚠️ Опасности
▸ Перегруженность логикой нарушает принцип SRP 
▸ Сложность тестирования из-за статической природы 
▸ Ограниченная расширяемость после компиляции 

Практические кейсы
▸ Фабрики объектов с предопределёнными настройками 
▸ State Machine для управления состоянием приложения 
▸ Конфигурация параметров системы 

💠Пример интеграции с Stream API

public enum MathOperation {
    PLUS("+", (a, b) -> a + b),
    MINUS("-", (a, b) -> a - b);

    private final String symbol;
    private final IntBinaryOperator operation;

    MathOperation(String symbol, IntBinaryOperator operation) {
        this.symbol = symbol;
        this.operation = operation;
    }

    public int apply(int a, int b) {
        return operation.applyAsInt(a, b);
    }
}

// Использование
int result = MathOperation.PLUS.apply(5, 3); // 8



💠P.S. enum в switch-выражениях

String description = switch (status) {
    case NEW -> "Новый заказ";
    case PROCESSING -> "В обработке";
    case SHIPPED -> "Отправлен";
};



💠Дополнение
Используйте EnumSet для эффективной работы с группами констант: 

EnumSet activeStatuses = EnumSet.of(NEW, PROCESSING);
if (activeStatuses.contains(currentStatus)) {
    // Логика для активных статусов
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥2❤‍🔥11
💠Gson — это библиотека от Google, предназначенная для работы с JSON в Java-приложениях. Она позволяет легко преобразовывать объекты Java в JSON и обратно, избавляя разработчиков от необходимости писать шаблонный код для парсинга данных. Gson поддерживает сложные структуры данных, такие как коллекции, вложенные объекты и даже объекты без исходного кода.

🟣Почему стоит использовать Gson?

1.🧬 Простота интеграции: Для использования достаточно добавить зависимость в build.gradle:
  


   implementation 'com.google.code.gson:gson:2.12.1'
  

2.🧬Мощные возможности сериализации и десериализации: Gson поддерживает преобразование объектов любой сложности.
3.🧬Интеграция с Retrofit: Gson часто используется в качестве конвертера для обработки данных из API.
4.🧬 Настраиваемость: С помощью GsonBuilder можно задать форматирование JSON, исключение полей и другие настройки.

⚡️Пример 1: Сериализация объекта.

➡️Сериализация — это процесс преобразования объекта Java в строку JSON. Рассмотрим простой пример:

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

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

➡️Создадим объект User и преобразуем его в JSON:

Gson gson = new Gson();
User user = new User("John", 25);
String json = gson.toJson(user);
System.out.println(json);

➡️Результат:

{"name":"John","age":25}


⚡️Пример 2: Десериализация JSON

➡️Десериализация — это обратный процесс, преобразующий строку JSON в объект Java:

String json = "{\"name\":\"Jane\",\"age\":30}";
User user = gson.fromJson(json, User.class);
System.out.println("Имя: " + user.getName() + ", Возраст: " + user.getAge());

➡️Результат:
Имя: Jane, Возраст: 30

⚡️Пример 3: Работа с коллекциями

➡️Если данные представлены в виде массива JSON, Gson может преобразовать их в список объектов:

String jsonArray = "[{\"name\":\"John\",\"age\":25},{\"name\":\"Jane\",\"age\":30}]";
Type userListType = new TypeToken<List<User>>() {}.getType();
List<User> users = gson.fromJson(jsonArray, userListType);

for (User user : users) {
    System.out.println("Имя: " + user.getName() + ", Возраст: " + user.getAge());
}

➡️Результат:
Имя: John, Возраст: 25 
Имя: Jane, Возраст: 30


⚡️Пример 4: Настройка через GsonBuilder.

➡️С помощью GsonBuilder можно настроить форматирование JSON или исключение определённых полей:

Gson gson = new GsonBuilder()
    .setPrettyPrinting()
    .excludeFieldsWithoutExposeAnnotation()
    .create();
String json = gson.toJson(user);
System.out.println(json);

➡️Результат будет отформатирован для удобного чтения:

{
  "name": "John",
  "age": 25
}


💎Обработка ошибок при десериализации

➡️Если формат JSON некорректен или не соответствует ожидаемому, может возникнуть ошибка JsonSyntaxException. Чтобы избежать краша приложения, используйте блок try-catch:

try {
    User user = gson.fromJson("{invalid_json}", User.class);
} catch (JsonSyntaxException e) {
    e.printStackTrace();
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍112👾1
🟡 Retrofit это библиотека для работы с REST API, которая значительно упрощает процесс выполнения HTTP-запросов в Android-приложениях. Она позволяет разработчикам сосредоточиться на логике приложения, избавляя от необходимости вручную обрабатывать сетевые запросы. Retrofit поддерживает методы HTTP (GET, POST, PUT, DELETE) и интеграцию с другими библиотеками, такими как Gson для обработки JSON-данных.

🔵Для начала работы с Retrofit необходимо добавить зависимости в файл build.gradle вашего проекта:

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

✔️Эти зависимости обеспечивают работу Retrofit и конвертацию JSON-ответов в объекты Java.

🔵После добавления зависимостей нужно настроить экземпляр Retrofit. Это делается с помощью класса Retrofit.Builder, где указывается базовый URL API и конвертер для обработки данных:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();


🔵Следующий шаг — создание интерфейса для описания методов API. Например:

public interface ApiService {
    @GET("users")
    Call<List<User>> getUsers();
}

✔️Метод @GET("users") указывает конечную точку API.

🔵Чтобы выполнить запрос, нужно создать экземпляр интерфейса через объект Retrofit:

ApiService apiService = retrofit.create(ApiService.class);
apiService.getUsers().enqueue(new Callback>() {
    @Override
    public void onResponse(Call<List<User>> call, Response<List<User>> response) {
        if (response.isSuccessful()) {
            List<User> users = response.body();
            // Обработка данных
        } else {
            // Обработка ошибки
        }
    }

    @Override
    public void onFailure(Call<List<User>> call, Throwable t) {
        // Обработка ошибки сети
    }
});

🔵Этот код выполняет асинхронный запрос к серверу. Если запрос успешен, данные из ответа преобразуются в список объектов User.

✔️Пример класса модели данных:

public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}


🔵Retrofit также поддерживает передачу параметров в запросы. Например, для получения данных о конкретном пользователе можно использовать аннотацию @Path:

public interface ApiService {
    @GET("users/{id}")
    Call<User> getUser(@Path("id") int userId);
}

🔵Вызов метода будет выглядеть так:

apiService.getUser(1).enqueue(new Callback() {
    @Override
    public void onResponse(Call<User> call, Response response) {
        if (response.isSuccessful()) {
            User user = response.body();
            // Обработка данных пользователя
        }
    }

    @Override
    public void onFailure(Call<User> call, Throwable t) {
        // Обработка ошибки сети
    }
});


🔵Retrofit позволяет легко работать с заголовками HTTP через аннотацию @Header:

public interface ApiService {
    @GET("users")
    Call<List<User>> getUsers(@Header("Authorization") String token);
}


🔵Библиотека также поддерживает обработку сложных сценариев взаимодействия с сервером, таких как отправка тела запроса (@Body) или параметров запроса (@Query). Например:

public interface ApiService {
    @POST("users")
    Call<User> createUser(@Body User user);

    @GET("users")
    Call<List<User>> getUsers(@Query("page") int page);
}


💠Retrofit значительно упрощает работу с сетью в Android-приложениях (и не только). Он делает код более чистым и поддерживаемым, а также позволяет легко интегрировать другие инструменты для обработки данных.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍61
⌨️ Приведение типов. Понижение и повышение типа

Java является строго типизированным языком программирования, а это означает, то что каждое выражение и каждая переменная имеет строго определенный тип уже на момент компиляции. Однако определен механизм приведения типов (casting) - способ преобразования значения переменной одного типа в значение другого типа.

В Java существуют несколько разновидностей приведения:

✔️ Тождественное (identity). Преобразование выражения любого типа к точно такому же типу всегда допустимо и происходит автоматически.

✔️ Расширение (повышение, upcasting) примитивного типа (widening primitive). Означает, что осуществляется переход от менее емкого типа к более ёмкому. Например, от типа byte (длина 1 байт) к типу int (длина 4 байта). Такие преобразование безопасны в том смысле, что новый тип всегда гарантировано вмещает в себя все данные, которые хранились в старом типе и таким образом не происходит потери данных. Этот тип приведения всегда допустим и происходит автоматически.

✔️ Сужение (понижение, downcasting) примитивного типа (narrowing primitive). Означает, что переход осуществляется от более емкого типа к менее емкому. При таком преобразовании есть риск потерять данные. Например, если число типа int было больше 127, то при приведении его к byte значения битов старше восьмого будут потеряны. В Java такое преобразование должно совершаться явным образом, при этом все старшие биты, не умещающиеся в новом типе, просто отбрасываются - никакого округления или других действий для получения более корректного результата не производится.

✔️ Расширение объектного типа (widening reference). Означает неявное восходящее приведение типов или переход от более конкретного типа к менее конкретному, т.е. переход от потомка к предку. Разрешено всегда и происходит автоматически.

✔️ Сужение объектного типа (narrowing reference). Означает нисходящее приведение, то есть приведение от предка к потомку (подтипу). Возможно только если исходная переменная является подтипом приводимого типа. При несоответствии типов в момент выполнения выбрасывается исключение ClassCastException. Требует явного указания типа.

✔️ Преобразование к строке (to String). Любой тип может быть приведен к строке, т.е. к экземпляру класса String.

✔️ Запрещенные преобразования (forbidden). Не все приведения между произвольными типами допустимы. Например, к запрещенным преобразованиям относятся приведения от любого ссылочного типа к примитивному и наоборот (кроме преобразования к строке). Кроме того, невозможно привести друг к другу классы, находящиеся на разных ветвях дерева наследования и т.п.

При приведении ссылочных типов с самим объектом ничего не происходит, - меняется лишь тип ссылки, через которую происходит обращение к объекту.

Для проверки возможности приведения нужно воспользоваться оператором instanceof:

Parent parent = new Child();
if (parent instanceof Child) {
Child child = (Child) parent;
}


#java #casting #upcasting #downcasting
Please open Telegram to view this post
VIEW IN TELEGRAM
👍152
⌨️ Generics (Безопасность типов)

Использование "сырых" типов коллекций (вроде List вместо List<String>) опасно! Generics (<>) обеспечивают безопасность типов на этапе компиляции.


// Опасно - можно добавить что угодно, ошибка будет при извлечении
List unsafeList = new ArrayList();
unsafeList.add("Привет");
unsafeList.add(123); // Компилятор не ругается!

// String s = (String) unsafeList.get(1); // ClassCastException во время выполнения!

// Безопасно - только строки!
List<String> safeList = new ArrayList<>();
safeList.add("Привет");
// safeList.add(123); // Ошибка компиляции! Нельзя добавить int в List<String>

String s = safeList.get(0); // Никакого приведения типов не нужно!


#java #generics #typesafety #bestpractice
Please open Telegram to view this post
VIEW IN TELEGRAM
👍121
⌨️ java.time API (Современные Дата и Время)

Старый API даты/времени был неудобным и изменяемым (mutable). Новый пакет java.time (LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Duration, Period) решает эти проблемы!


import java.time.LocalDate;
import java.time.Month;
import java.time.temporal.ChronoUnit;

LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(2024, Month.DECEMBER, 31);
LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); // Неизменяемый! Возвращает новый объект

System.out.println("Сегодня: " + today);
System.out.println("День рождения: " + birthday);
System.out.println("Через неделю: " + nextWeek);
// Вывод:
// Сегодня: 2023-10-27 (пример)
// День рождения: 2024-12-31
// Через неделю: 2023-11-03 (пример)


Используйте современный, потокобезопасный и интуитивно понятный API!
#java #datetime #java8 #javatime #api #bestpractice
Please open Telegram to view this post
VIEW IN TELEGRAM
👍121🔥1👏1
⌨️ Дайте определение понятию «интерфейс». Какие модификаторы по умолчанию имеют поля и методы интерфейсов?

Ключевое слово interface используется для создания полностью абстрактных классов. Основное предназначение интерфейса - определять каким образом мы можем использовать класс, который его реализует. Создатель интерфейса определяет имена методов, списки аргументов и типы возвращаемых значений, но не реализует их поведение. Все методы неявно объявляются как public.

Начиная с Java 8 в интерфейсах разрешается размещать реализацию методов по умолчанию default и статических static методов.

Интерфейс также может содержать и поля. В этом случае они автоматически являются публичными public, статическими static и неизменяемыми final.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍142
💎Unicode-хаки и комментарии-невидимки в Java 
Java поддерживает Unicode во всех частях кода, включая комментарии и имена переменных. Это открывает неочевидные возможности для экспериментов, но требует осторожности в production-коде.

💠1. Исполняемые комментарии через Unicode 

// \u000d System.out.println("Этот код выполнится!");

После компиляции символ \u000d превращается в перенос строки, и код становится: 

//
System.out.println("Этот код выполнится!");

Применение: 
▸ Демонстрация скрытых уязвимостей в презентациях 
▸ Образовательные эксперименты с компиляцией 
▸ Технические розыгрыши (только для небоевых проектов!) 

💠2. Кириллические идентификаторы 

int размер = 10; // переменная "размер"
System.out.println(размер); // 10

💠Плюсы: 
▸ Локализация кода для образовательных проектов 
▸ Поддержка специфических терминов на национальных языках 

💠3. Графика через Unicode-символы 

System.out.println("\u2591\u2592\u2593"); // Вывод символов псевдографики: ░ ▒ ▓ (разные уровни заливки)
System.out.println("\u265A \u265B"); // Вывод юникод-символов: ♚ ♛ (шахматные фигуры короля и королевы)

💠Практическое применение: 
▸ Консольные интерфейсы с псевдографикой 
▸ Визуализация данных в текстовом режиме 
▸ Генерация ASCII-арта 

💠4. Экранирование спецсимволов 

String regex = "\\p{So}"; // Шаблон для эмодзи и символов
String text = "Alert! ⚠️";
System.out.println(text.replaceAll(regex, "[символ]")); // Alert! [символ]


⚠️ Важные предупреждения: 
▸ Код с Unicode-трюками не проходит код-ревью в серьезных проектах 
▸ Может вызывать проблемы с линтерами и статическими анализаторами 
▸ Затрудняет поиск в кодовой базе (например, \u000d

💠Альтернативы для безопасного использования: 

// Локализация через ResourceBundle
ResourceBundle bundle = ResourceBundle.getBundle("Messages_ru"); // Загрузка ресурсов для локали ru
String message = bundle.getString("welcome"); // Получение локализованной строки по ключу
Please open Telegram to view this post
VIEW IN TELEGRAM
👍81
💎ThreadLocal— потокобезопасное хранилище данных 

ThreadLocal позволяет хранить данные, уникальные для каждого потока, без использования синхронизации. Это особенно полезно в многопоточных приложениях, где необходимо сохранять состояние для конкретного потока.

💠1. Пример

private static final ThreadLocal<DateFormat> dateFormat =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

void formatDate(Date date) {
    System.out.println(dateFormat.get().format(date)); // У каждого потока свой экземпляр
}

💠Преимущества: 
▸ Избегает создания нового объекта для каждого вызова 
▸ Гарантирует потокобезопасность без блокировок 

💠2. Контекст безопасности в веб-приложениях 

public class SecurityContext {
    private static final ThreadLocal<User> currentUser = new ThreadLocal<>();

    public static void login(User user) {
        currentUser.set(user);
    }

    public static User getCurrentUser() {
        return currentUser.get();
    }

    public static void logout() {
        currentUser.remove();
    }
}

// В фильтре сервлета
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    SecurityContext.login((User) req.getAttribute("user"));
    try {
        chain.doFilter(req, res);
    } finally {
        SecurityContext.logout();
    }
}


💠3. Локальный кэш для тяжелых вычислений 

private static final ThreadLocal<Map<String, String>> cache =
    ThreadLocal.withInitial(HashMap::new);

void processData(String key) {
    if (!cache.get().containsKey(key)) {
        cache.get().put(key, expensiveOperation(key));
    }
    System.out.println(cache.get().get(key));
}


⚠️ Опасности: 
▸ Утечки памяти в пулах потоков (например, Tomcat) 
▸ Неожиданное поведение при повторном использовании потоков 
▸ Сложность отладки из-за неявной передачи данных 

💠Практические кейсы: 
▸ Контекст пользователя в веб-приложениях 
▸ Локальный кэш для тяжелых вычислений 
▸ Параметры локали и форматирования дат
Please open Telegram to view this post
VIEW IN TELEGRAM
👍82🎉1
⌨️ Суть лямбда-выражений

Лямбда-выражения на первый взгляд могут показаться чем-то сложным и загадочным, но на самом деле они просты и интуитивно понятны.

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

Если говорить ещё проще, лямбда-выражение — это просто другой способ создания и реализации объекта определённого типа. Рассмотрим это на примере создания нового потока.

У класса Thread есть конструктор:

public Thread(Runnable target) {
...
}


То есть в конструктор нужно передать объект типа Runnable. До лямбда-выражений мы сделали бы так:

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
}).start();

Здесь мы создаём анонимный класс, реализующий интерфейс Runnable, с определённым методом run.
Если использовать лямбда-выражение, тот же код будет выглядеть следующим образом:

Runnable r = () -> System.out.println("Hello World");
new Thread(r).start();

Или проще:

new Thread(() -> System.out.println("Hello World")).start();


Лямбда-выражение заменяет собой анонимный класс, который раньше был бы необходим для реализации Runnable. Лямбда-выражение может использоваться только там, где ожидается реализация функционального интерфейса — интерфейса с единственным абстрактным методом.
А интерфейс Runnable именно такой:

@FunctionalInterface
public interface Runnable {
public abstract void run();
}


Функциональный интерфейс должен содержать только один абстрактный метод, чтобы компилятор мог точно определить, какой метод реализует лямбда-выражение. В противном случае возникли бы неоднозначности и ошибки.

#java #lambda #Runnable
Please open Telegram to view this post
VIEW IN TELEGRAM
👍145🔥2🤣2
☕️Checked vs Unchecked исключения

🔹Исключения делятся на проверяемые (checked) и непроверяемые (unchecked).

🔹Checked исключения требуют обязательной обработки или объявления через throws. Обычно связаны с внешними ресурсами: файлами, сетью.

🔎Пример:

public void readConfigFile() {
try (FileInputStream file = new FileInputStream("config.txt")) {
int data = file.read();
System.out.println("Данные: " + data);
} catch (FileNotFoundException e) {
System.err.println("Файл не найден!");
} catch (IOException e) {
System.err.println("Ошибка ввода-вывода: " + e.getMessage());
}
}


🔹Unchecked исключения — наследники RuntimeException. Не требуют обработки, сигнализируют о логических ошибках.

🔻 Пример:

public void printLength(String text) {
if (text == null) throw new IllegalArgumentException("Строка не может быть null!");
System.out.println("Длина: " + text.length());
}



public int divide(int a, int b) {
if (b == 0) throw new ArithmeticException("Деление на ноль!");
return a / b;
}


📈Советы:
1. Для checked используйте try или try-with-resources.
2. Для unchecked проверяйте входные данные.
3. Документируйте исключения через
@throws.

🔺Checked — для внешних ошибок, Unchecked — для внутренних багов.
Please open Telegram to view this post
VIEW IN TELEGRAM
13🤝3👍1
☕️GraphQL в Java позволяет клиентам запрашивать только нужные данные, избегая избыточности REST. Пример на Spring Boot:

🔹Схема (schema.graphqls):
type Vehicle {
id: ID!
type: String!
modelCode: String!
}

type Query {
vehicles: [Vehicle!]!
}


🔻Контроллер:
@Controller
public class VehicleController {
@QueryMapping
public List vehicles() {
return List.of(new Vehicle("1", "bus", "XYZ123"));
}
}


💠Пример запроса:
{
vehicles {
id
type
}
}


🔸Ответ:
{
"data": {
"vehicles": [{ "id": "1", "type": "bus" }]
}
}


🔺Для интеграции с БД используйте Spring Data JPA и Querydsl:
public interface VehicleRepo extends JpaRepository, QuerydslPredicateExecutor {}


🔹Maven зависимости:

org.springframework.boot
spring-boot-starter-graphql


com.querydsl
querydsl-jpa


📈Советы:
1. Включите GraphiQL для интерактивных запросов.
2. Используйте
@MutationMapping для мутаций.
3. Настройте кеширование через CacheControl.

🔺GraphQL позволяет запрашивать только нужные поля, снижая нагрузку на сеть.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍141
☕️Spring Data Envers помогает автоматически отслеживать изменения сущностей в базе данных с помощью Hibernate Envers.

🔺Пример сущности с аудитом:
@Entity  
@Audited
public class Post {
@Id @GeneratedValue
private Long id;
private String title;
private String slug;
}


🔺Репозиторий с поддержкой ревизий:
public interface PostRepository extends JpaRepository, RevisionRepository { }  


💠Доступны методы findRevisions() и findLastChangeRevision() для получения истории изменений.
🔻Envers создает таблицы _AUD и REVINFO для хранения версий и метаданных. При изменениях данные копируются в _AUD с типом операции (INSERT, UPDATE, DELETE).


Пример использования:
for (Revision rev : postRepository.findRevisions(postId)) {  
System.out.println("Версия " + rev.getRevisionNumber() + ": " + rev.getEntity());
}


📈Советы:
1. Добавьте зависимости hibernate-envers и spring-data-envers.
2. Включите
@EnableJpaAuditing в конфигурации.
3. Для расширенного аудита используйте
@CreatedBy и @LastModifiedBy.

🔸Метод findRevisions() возвращает все версии сущности — от создания до удаления.
Please open Telegram to view this post
VIEW IN TELEGRAM
3👌2
☕️Spring WebFlux — реактивный фреймворк для асинхронных веб-приложений в Spring 5. Он работает на серверах Netty, Undertow или Servlet 3.1+ и использует Project Reactor для обработки потоков данных без блокировок. Это позволяет обрабатывать тысячи соединений с минимальным числом потоков, что важно для высоконагруженных систем.

🎯Пример контроллера:
@RestController  
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public Mono getUser(@PathVariable Long id) {
return userRepository.findById(id);
}
@GetMapping("/{id}/orders")
public Flux getUserOrders(@PathVariable Long id) {
return userRepository.findById(id)
.flatMapMany(orderRepository::findByUser);
}
}


📇WebClient для неблокирующих HTTP-запросов:
WebClient.create("http://service.com")  
.get()
.uri("/data")
.retrieve()
.bodyToFlux(Data.class)
.subscribe(System.out::println);


📈Советы:
1. Используйте WebFlux для микросервисов с высокой нагрузкой.
2. Комбинируйте с R2DBC для реактивного доступа к БД.
3. Избегайте блокирующих вызовов (JDBC, JPA) в реактивных цепочках.


💠«WebFlux обрабатывает больше запросов с меньшим числом потоков за счет асинхронного планирования».
Please open Telegram to view this post
VIEW IN TELEGRAM
💯63👍3
💠Room — это библиотека для работы с локальными базами данных SQLite в Android, предоставляющая удобный интерфейс для взаимодействия с данными. Она является частью Android Jetpack и значительно упрощает управление базами данных, устраняя необходимость писать сложный SQL-код вручную. Room обеспечивает безопасность и упрощает поддержку приложения благодаря проверке запросов на этапе компиляции и автоматической сериализации объектов.

⁉️Почему стоит использовать Room?

1. Меньше шаблонного кода: Room автоматически обрабатывает многие задачи, такие как создание таблиц, выполнение запросов и преобразование данных.
2. Проверка запросов на этапе компиляции: Если запрос SQL содержит ошибку, она будет обнаружена до запуска приложения.
3. Интеграция с современными инструментами: Room поддерживает работу с LiveData, RxJava, Coroutines и Paging, что делает её идеальной для современных приложений.
4. Удобная миграция данных: Room предоставляет встроенные инструменты для обновления структуры базы данных.

Как подключить Room к проекту?

implementation 'androidx.room:room-runtime:2.6.1'
annotationProcessor 'androidx.room:room-compiler:2.6.1'

✔️Для использования Kotlin Coroutines добавьте:
implementation 'androidx.room:room-ktx:2.6.1'


➡️Пример 1: Создание сущности.

Сущность представляет таблицу в базе данных. Например:
@Entity(tableName = "users")
public class User {
    @PrimaryKey(autoGenerate = true)
    public int id;

    @ColumnInfo(name = "name")
    public String name;

    @ColumnInfo(name = "age")
    public int age;

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

✔️Аннотация @Entity указывает, что класс является таблицей в базе данных.

➡️Пример 2: Создание DAO.

DAO (Data Access Object) определяет методы для взаимодействия с базой данных:
@Dao
public interface UserDao {
    @Insert
    void insert(User user);

    @Query("SELECT * FROM users")
    List getAllUsers();

    @Delete
    void delete(User user);
}

Методы DAO используют аннотации (@Insert, @Query, @Delete) для выполнения операций.

➡️Пример 3: Инициализация базы данных.

✔️Для доступа к базе данных создаётся класс, наследующий RoomDatabase:
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

✔️Инициализация базы данных:
AppDatabase db = Room.databaseBuilder(context, AppDatabase.class, "database-name").build();
UserDao userDao = db.userDao();


➡️Пример 4: Работа с LiveData.

✔️Room легко интегрируется с LiveData для наблюдения за изменениями данных:
// Получение LiveData-списка всех пользователей
@Query("SELECT * FROM users")
LiveData<List<User>> getAllUsers(); // LiveData<List<User>> - тип возвращаемого значения

✔️В UI можно наблюдать за изменениями данных:
userDao.getAllUsers().observe(this, users -> {
    // Обновление интерфейса при изменении данных
});


➡️Пример 5: Миграция базы данных.

✔️Если структура базы данных изменяется (например, добавляется новое поле), необходимо настроить миграцию:
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE users ADD COLUMN email TEXT");
    }
};
AppDatabase db = Room.databaseBuilder(context, AppDatabase.class, "database-name")
    .addMigrations(MIGRATION_1_2)
    .build();


⚡️Советы по использованию Room:

1. Используйте Flow или LiveData для реактивного обновления UI.
2.
Оптимизируйте запросы, выбирая только необходимые поля из таблицы.
3.
Настройте UPSERT операции, используя стратегию OnConflictStrategy.REPLACE:
  

   @Insert(onConflict = OnConflictStrategy.REPLACE)
   void insertOrUpdate(User user);
  


☄️Room — это мощный инструмент для работы с локальными данными в Android-приложениях. Она упрощает взаимодействие с SQLite, делает код более организованным и поддерживаемым, а также интегрируется с современными архитектурными компонентами Android.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍43
⌨️ Срезы в стримах. Метод takeWhile

В Java 9 появилось два новых метода, полезных для выбора элементов потока с хорошей производительностью: takeWhile и dropWhile.

Допустим, у нас есть следующий список блюд:

List<Dish> specialMenu = Arrays.asList(
new Dish("seasonal fruit", 120),
new Dish("prawns", 300),
new Dish("rice", 350),
new Dish("chicken", 400),
new Dish("french fries", 530));


Для получения блюд с калорийностью меньше 320, можно воспользоваться операцией filter. Недостаток операции filter в том, что она требует прохода в цикле по всему потоку данных с применением предиката ко всем элементам.

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

Поможет нам в этом операция takeWhile! Она позволяет выполнить срез любого потока данных (даже бесконечного) с помощью предиката. И, к счастью, она прекращает работу сразу же по обнаружении неподходящего элемента. Вот как ее следует использовать:

List<Dish> sliceMenu1
= specialMenu.stream()
.takeWhile(dish -> dish.getCalories() < 320)
.collect(toList());


#java #stream #takeWhile
Please open Telegram to view this post
VIEW IN TELEGRAM
👍202
☕️GraalVM ускоряет Java с помощью JIT-компиляции и поддерживает мультиязычность и нативную компиляцию.

🔹Пример JIT-ускорения:

public class Demo {
public static void workload(int a) {
System.out.println(a + 1); // Простая операция
}
public static void main(String[] args) {
for (int i = 0; i < 100_000; i++) workload(i);
}
}

🔺Запуск:

java -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler Demo


🚀Вызов JavaScript из Java:

import org.graalvm.polyglot.*;
public class Polyglot {
public static void main(String[] args) {
try (Context context = Context.create()) {
int result = context.eval("js", "Math.pow(2, 10)").asInt();
System.out.println(result); // 1024
}
}
}


🔸Нативная компиляция:

native-image -jar app.jar
./app

🔭Работает без JVM!

📈Советы:
1. Для мультиязычности добавьте org.graalvm.sdk.
2. Используйте
@CEntryPoint для нативных методов.
3. Тестируйте производительность через jmh.


GraalVM позволяет запускать Python и Ruby внутри Java через Polyglot API.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
⌨️ Типы классов

В Java существует несколько «типов» (форм) классов, каждый из которых решает свои задачи и обладает своими особенностями. Рассмотрим основные из них:

1. Топ-level класс
Класс, объявленный в собственном файле .java (или несколько в одном файле, но только один может быть public).
Когда используется: для большинства объектов предметной области.

2. Статический вложенный класс (static nested class)
Объявляется внутри другого класса с модификатором static.
Когда используется: чтобы логически сгруппировать вспомогательный класс и избежать загрязнения пакета; не требует ссылки на экземпляр внешнего класса.

3. Нестатический внутренний класс (inner class)
Нестатический класс внутри другого класса. Имеет неявную ссылку на экземпляр внешнего.
Когда используется: когда класс не имеет смысла отдельно от конкретного экземпляра внешнего класса.

4. Локальный класс (local class)
Объявляется внутри метода. Видим только внутри этого метода.
Когда используется: для единичной вспомогательной логики, зависящей от локальных переменных.

5. Анонимный класс (anonymous class)
Определяется прямо при создании объекта, без имени. Часто для реализации интерфейсов/абстрактных классов «на месте».
Когда используется: когда нужна «быстрая» одноразовая реализация интерфейса или абстрактного класса.

6. Абстрактный класс (abstract class)
Может содержать как реализованные, так и абстрактные (без реализации) методы; сам не может быть инстанцирован.
Когда используется: когда есть общая часть реализации для набора подклассов, но нужна возможность «заставить» их реализовать конкретные методы.

7. Финальный класс (final class)
Нельзя унаследовать.
Когда используется: чтобы зафиксировать поведение и запретить расширение (например, для безопасности).

8. Перечисление (enum)
Специальный вид класса для фиксированного набора констант; может содержать поля и методы.
Когда используется: для ограничения значений до заранее известных.

9. Запись (record) [Java 16+]
Лаконичная форма класса-носителя данных: автоматом генерируются конструктор, equals(), hashCode(), toString().
Когда используется: для неизменяемых DTO/Value-объектов.

10. Запечатанный класс (sealed class) [Java 17+]
Позволяет закрыть иерархию наследования: указать, какие классы могут расширять/реализовывать.
Когда используется: когда важно контролировать все возможные подклассы (например, при реализации «альгебраического типа»).

11. Аннотации (annotation types)
Специальный «интерфейс» с префиксом @interface для метаданных.
Когда используется: для декларации информации, обрабатываемой компилятором или фреймворками (например, @Override, @Entity).

Каждый из этих типов классов/типов в Java помогает структурировать код, задавая чёткие контракты, ограничивая использование и сокращая «бесполезный» шаблонный код.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍107🔥2
⌨️ Игра "Камень, ножницы, бумага"


import java.util.Random;
import java.util.Scanner;

public class RockPaperScissors {
public static void main(String[] args) {
String[] options = { "Камень", "Ножницы", "Бумага" };
Random rnd = new Random();
Scanner sc = new Scanner(System.in);
int userScore = 0, compScore = 0;

System.out.println("Игра «Камень, ножницы, бумага». Введите 0-Камень, 1-Ножницы, 2-Бумага, q-выход.");

while (true) {
System.out.print("Ваш выбор: ");
String inp = sc.nextLine();
if (inp.equalsIgnoreCase("q")) break;

int user, comp = rnd.nextInt(3);
try {
user = Integer.parseInt(inp);
if (user < 0 || user > 2) throw new NumberFormatException();
} catch (NumberFormatException e) {
System.out.println("Некорректно, попробуйте снова.");
continue;
}

System.out.printf("Вы: %s, Компьютер: %s → ", options[user], options[comp]);
int outcome = (user - comp + 3) % 3;
// 0=ничья, 2=победа пользователя, 1=проигрыш
if (outcome == 0) {
System.out.println("Ничья");
} else if (outcome == 2) {
System.out.println("Вы выигрываете");
userScore++;
} else {
System.out.println("Проигрываете");
compScore++;
}

System.out.printf("Счёт %d:%d%n", userScore, compScore);
}

System.out.println("Игра окончена. Финальный счёт " + userScore + ":" + compScore);
sc.close();
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
😁14👍3😍3
⌨️ Stream.reduce() vs Collectors.joining()

Когда требуется конкатенировать строки с использованием Stream, можно выбрать один из двух методов: Stream.reduce() или Stream.collect(Collectors.joining()).

Пример с Stream.reduce():
List<String> list = List.of("Str1", "Str2", "Str3");
String result = list.stream().reduce("", (a, b) -> a + b);
System.out.println(result); // Str1Str2Str3


Пример с Collectors.joining():
List<String> list = List.of("Str1", "Str2", "Str3");
String result = list.stream().collect(Collectors.joining());
System.out.println(result); // Str1Str2Str3


Использование reduce() для конкатенации строк не является оптимальным с точки зрения производительности. При каждом вызове операции +, создается новая строка, так как строки в Java неизменяемы. Это приводит к увеличению нагрузки на память из-за создания множества промежуточных объектов.

В свою очередь, метод Collectors.joining() использует StringBuilder для сборки строк, что значительно эффективнее. Он избегает создания лишних объектов и снижает потребление памяти.

#java #Stream #reduce #joining
Please open Telegram to view this post
VIEW IN TELEGRAM
👍141
⌨️ В Java static import (статический импорт) позволяет импортировать статические члены (методы и поля) из класса, чтобы использовать их напрямую без указания имени класса. Это может сделать код более читабельным и компактным.


// Для импорта конкретного статического члена
import static package.Clazz.member;

// Для импорта всех статических членов класса
import static package.Clazz.*;


Пример:

public class MathUtils {
public static final double PI = 3.141592653589793;
public static int add(int a, int b) {
return a + b;
}
public static int subtract(int a, int b) {
return a - b;
}
}



import static MathUtils.PI;
import static MathUtils.add;

public class Main {
public static void main(String[] args) {
System.out.println("Value of PI: " + PI);
System.out.println("Addition result: " + add(5, 3));
}
}



import static MathUtils.*;

public class Main {
public static void main(String[] args) {
System.out.println("Value of PI: " + PI);
System.out.println("Addition result: " + add(5, 3));
System.out.println("Subtraction result: " + subtract(5, 3));
}
}


Плюсы: читаемость, компактность кода.

Минусы:
- не очевидно откуда берется поле или метод
- импорт всех статических членов может привести к конфликтам имен, если разные классы содержат статические члены с одинаковыми именами

#java #static #import
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥41