Java for Beginner
680 subscribers
573 photos
158 videos
12 files
869 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Множественное наследование

Классы в Java не могут наследовать более одного класса, но могут реализовать несколько интерфейсов:
public interface Flyable {
void fly();
}

public interface Swimmable {
void swim();
}

public class Duck implements Flyable, Swimmable {
public void fly() { ... }
public void swim() { ... }
}
Это решает проблему ограничений одиночного наследования и позволяет композировать поведение.



Трудности и подводные камни

1. Конфликт default-методов

Если класс реализует несколько интерфейсов, в которых есть default-методы с одинаковыми сигнатурами, возникает конфликт:

interface A {
default void show() { System.out.println("A"); }
}
interface B {
default void show() { System.out.println("B"); }
}
class C implements A, B {
public void show() {
A.super.show(); // Явное разрешение конфликта
}
}


2. Отсутствие состояния
Интерфейсы не могут содержать нестатические поля, что ограничивает их в сравнении с абстрактными классами. Всё, что можно объявить — это public static final константы.

3. Непредсказуемое поведение equals/hashCode

Если объект используется через интерфейс, его поведение equals() и hashCode() может зависеть от реализации, что важно при использовании в коллекциях.

4. Ошибки при переопределении
Ошибки в сигнатурах при реализации интерфейса не всегда очевидны. Например, случайное переопределение метода с другой сигнатурой может вызвать баг.

5. Подмена реализации
Использование интерфейса как типа может скрывать специфичное поведение конкретной реализации:

List<String> list = new LinkedList<>();
list.get(0); // Поведение может отличаться от ArrayList



Дополнительные нюансы

1. Функциональные интерфейсы
С Java 8 появились интерфейсы с единственным абстрактным методом — функциональные интерфейсы. Они могут быть использованы с лямбда-выражениями:
Runnable r = () -> System.out.println("Run");


2. Интерфейсы и обобщения
Интерфейсы часто комбинируются с generics для повышения универсальности:
public interface Repository<T> {
void save(T entity);
}


3. Marker-интерфейсы
Некоторые интерфейсы, такие как Serializable или Cloneable, не содержат методов. Они используются как маркеры для определения поведения JVM или библиотек.

4. Интерфейсы и наследование
Интерфейс может наследовать другие интерфейсы:
interface A { void a(); }
interface B extends A { void b(); }
Класс, реализующий B, обязан реализовать методы a() и b().


5. Интерфейсы и динамические прокси
Через интерфейсы создаются динамические прокси, например, в java.lang.reflect.Proxy, что активно используется в Spring, Hibernate и других фреймворках.

#Java #для_новичков #beginner #reference_types #Interface
Интеграция Flyway с инструментами

1. Maven: плагин flyway-maven-plugin
Настройка в pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>9.22.0</version>
<configuration>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<user>postgres</user>
<password>password</password>
<locations>
<location>classpath:db/migration</location>
</locations>
</configuration>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>


Ключевые параметры:

url — JDBC-строка подключения.
locations — пути к папкам с миграциями (classpath: или filesystem:).
baselineVersion — версия для инициализации существующей БД (если не пустая).


Команды:
mvn flyway:migrate  # Применить миграции
mvn flyway:info # Показать статус
mvn flyway:clean # Очистить БД (удалить все объекты)


2. Gradle: плагин org.flywaydb.flyway
Настройка в build.gradle:
plugins {
id "org.flywaydb.flyway" version "9.22.0"
}

flyway {
url = 'jdbc:postgresql://localhost:5432/mydb'
user = 'postgres'
password = 'password'
locations = ['classpath:db/migration']
}

dependencies {
flywayRuntime 'org.postgresql:postgresql:42.6.0'
}


Команды:
gradle flywayMigrate  # Применить миграции
gradle flywayInfo # Показать статус


3. Интеграция с Spring Boot
Автоконфигурация через application.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: postgres
password: password
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true # Игнорировать существующую БД
validate-on-migrate: false # Отключить валидацию (для dev)


Принцип работы:

При старте приложения Spring Boot автоматически:
Проверяет наличие таблицы flyway_schema_history.
Применяет невыполненные миграции из указанных в locations.
Если baseline-on-migrate: true, Flyway инициализирует БД без ошибок, даже если она не пуста.


Важные свойства:
spring.flyway.clean-disabled — запрет на случайную очистку БД (по умолчанию true в prod).
spring.flyway.table — кастомное имя для таблицы истории (по умолчанию flyway_schema_history).


4. Callbacks (SQL-хуки)

SQL-скрипты, выполняемые до/после основных операций Flyway (миграции, очистки и т.д.).

Поддерживаемые события:
beforeMigrate, afterMigrate
beforeClean, afterClean
beforeInfo, afterInfo


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


Создайте файл db/migration/beforeMigrate.sql:
-- beforeMigrate.sql
CREATE TABLE IF NOT EXISTS _flyway_audit (
operation VARCHAR(20),
timestamp TIMESTAMP
);
INSERT INTO _flyway_audit VALUES ('migration_started', NOW());


Flyway выполнит этот скрипт перед применением миграций.

Правила:
Имена файлов должны соответствовать шаблону: <event>.sql (например, afterMigrate.sql).
Размещаются в папке locations (рядом с миграциями).


5. Мультимодульные проекты (Maven/Gradle)

Если миграции находятся в отдельном модуле:
Укажите путь к ресурсам через filesystem::
<!-- Maven -->
<location>filesystem:${project.basedir}/../migrations/src/main/resources/db/migration</location>


// Gradle
locations = ['filesystem:../migrations/src/main/resources/db/migration']


6. Пропуск миграций в тестах

В application-test.yml:
spring:
flyway:
enabled: false


7. Кастомные места хранения миграций

S3-хранилище (через Flyway Teams):
spring:
flyway:
locations: s3://my-bucket/db/migration


Классовый путь + файловая система:
locations: classpath:db/migration, filesystem:/opt/migrations


#Java #middle #Flyway
Ссылочные типы в Java var

Ключевое слово var, добавленное в Java 10, представляет собой локальную типизированную переменную с выводом типа (local variable type inference). Хотя var сам по себе не является типом (в отличие от String, List, и т.п.), он оперирует ссылочными (и примитивными) типами — именно поэтому мы рассматриваем его как часть экосистемы ссылочных типов.

var позволяет сократить синтаксис, делая код лаконичнее, при этом тип переменной всё равно строго определяется на этапе компиляции.

Синтаксис и пример использования

var list = new ArrayList<String>();
var name = "John";
var age = 25;


Это полностью эквивалентно:
ArrayList<String> list = new ArrayList<>();
String name = "John";
int age = 25;


Тип переменной вычисляется во время компиляции, и далее не может быть изменён. var нельзя использовать без инициализации:
var name; // ошибка компиляции


Что происходит на уровне компиляции и памяти

На уровне байткода и JVM var не существует — это просто синтаксический сахар. Компилятор javac анализирует выражение справа от знака =, определяет точный тип и подставляет его вместо var.

Пример:
var user = new User("Alice");


Компилируется в:
User user = new User("Alice");


На уровне памяти всё ведёт себя так же, как если бы вы явно указали тип: объект User создаётся в куче (heap), а ссылка user — в стеке (если переменная локальная). То есть var никак не влияет на runtime-поведение, управление памятью или структуру объектов.

Где можно использовать var

Только внутри методов (локальные переменные)
В циклах for-each
В try-with-resources


Примеры:
for (var entry : map.entrySet()) {
...
}

try (var stream = Files.lines(path)) {
...
}


Нельзя использовать:
Для параметров методов
Для полей классов
В сигнатурах методов


Преимущества использования var

1. Ускорение читаемости

Map<String, List<User>> usersByRole = new HashMap<>();


var usersByRole = new HashMap<String, List<User>>();


Меньше дублирования, особенно при использовании дженериков.

2. Быстрая прототипизация


При тестировании и прототипах позволяет писать код быстрее.

3. Минимизация шаблонного кода

Особенно полезно в циклах:
for (var entry : map.entrySet()) { ... }


Недостатки и подводные камни

1. Потеря явности

var может скрыть реальный тип переменной, что ухудшает читаемость, особенно в больших проектах или при чтении чужого кода:
var x = process(); // Что возвращает process()? Map? List? String?


2. Не подходит для API и контрактов

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

3. Опасности с null

Пример:
var data = null; // ошибка компиляции: невозможно вывести тип


Тип должен быть определён по выражению. null без контекста не имеет типа.


4. Может скрыть ошибки


В случае приведения типов или неочевидных фабрик:
var list = Arrays.asList(1, 2, 3); // list: List<Integer>, но неизменяемый!


Программист может подумать, что это ArrayList.

5. Не подходит для примитивов, если важна производительность
var x = 42; // int
var y = 42L; // long

// Может быть неожиданным, если x и y пойдут в разные ветви перегрузки


Лучшие практики использования var

Использовать var, когда тип очевиден из правой части.
Не использовать в публичных API и библиотечном коде.
Избегать var, если тип сложный или поведение неочевидно.
Можно комбинировать с IDE: использовать var в коде, но просматривать тип через наведение мыши.


Пример хорошего использования:
var list = new ArrayList<String>(); // тип легко читается


Пример плохого использования:
var result = service.process(); // неясно, что такое result


#Java #для_новичков #beginner #reference_types #var
Best Practices и расширенные сценарии работы с Flyway

1. Организация миграций

1.1. Разделение миграций по окружениям

Проблема:
Разные БД для dev/test/prod могут требовать специфичных скриптов (например, тестовые данные только для dev).

Решение:
Структура папок:

db/
migration/
dev/
V1__Dev_only_data.sql
prod/
V1__Prod_indexes.sql
common/
V1__Base_schema.sql


Настройка application.yml для dev:
spring:
flyway:
locations: classpath:db/migration/common, classpath:db/migration/dev


1.2. Работа с существующей БД (baseline)

Проблема:
Flyway ожидает пустую БД или таблицу flyway_schema_history. Если БД уже используется, требуется инициализация.

Решение:
Включить baseline-on-migrate:

spring:
flyway:
baseline-on-migrate: true
baseline-version: 1.0 # Версия, с которой начнется контроль


Вручную через CLI:

flyway baseline -baselineVersion="1.0"


Важно:
После baseline миграции с версией ≤ baselineVersion игнорируются.


2. Тестирование миграций

2.1. Интеграция с Testcontainers + JUnit

Цель:
Проверить, что миграции применяются без ошибок в изолированной БД.

Пример теста (Java + JUnit 5):
import org.flywaydb.core.Flyway;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
public class FlywayMigrationTest {

@Container
private static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15");

@Test
void testMigrations() {
Flyway flyway = Flyway.configure()
.dataSource(
postgres.getJdbcUrl(),
postgres.getUsername(),
postgres.getPassword()
)
.load();
flyway.migrate(); // Упадет, если есть ошибки
}
}


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



3. Работа в команде

3.1. Именование файлов

Правила:
Уникальность версий:
Используйте дату + порядковый номер: V20240315-1__Add_users_table.sql.

Описательные имена:
Плохо: V1__Changes.sql.
Хорошо: V20240315-2__Alter_users_add_email.sql.


3.2. Запрет изменения примененных скриптов

Проблема:
Изменение уже выполненного скрипта приводит к ошибке:

Validate failed: Migration checksum mismatch for V1__Create_table.sql


Решение:
Никогда не изменять содержимое V*-файлов после их применения.

Для исправлений:
Создать новую миграцию с корректировкой:
-- V20240315-3__Fix_users_table.sql
ALTER TABLE users ALTER COLUMN email SET NOT NULL;

Если критично, использовать flyway repair (пересчитывает контрольные суммы).

Исключение:
R__-миграции (повторяемые) можно изменять — они переприменяются при изменениях.


4. Расширенные сценарии

4.1. Миграции для нескольких схем БД

Настройка:
spring:
flyway:
schemas: public, audit
default-schema: public
В SQL-файлах укажите схему явно:

sql
-- V1__Create_audit_table.sql
CREATE TABLE audit.logs (
id SERIAL PRIMARY KEY,
message TEXT
);


4.2. Шаблонизация SQL

Проблема:
Разные SQL-синтаксисы для PostgreSQL/Oracle.

Решение (через Maven/Gradle):
Используйте placeholders в pom.xml:
<configuration>
<placeholders>
<table.prefix>${env}</table.prefix>
</placeholders>
</configuration>


В SQL:
CREATE TABLE ${table.prefix}_users (...);


4.3. Откаты (безопасная альтернатива)


Тактика:
Для деструктивных изменений (DROP, DELETE) создавать "обратные" миграции:
-- V5__Drop_users.sql
DROP TABLE users;

-- V6__Restore_users.sql (если нужно откатить)
CREATE TABLE users (...); -- Повторяем структуру из V1
INSERT INTO users (...) SELECT ... FROM backup_users;
Использовать резервные копии перед рискованными операциями.


#Java #middle #Flyway