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

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Трудности и подводные камни

1. Неизменяемость
Изменение строки всегда приводит к созданию нового объекта. Это важно учитывать при работе в циклах:

String result = "";
for (int i = 0; i < 1000; i++) {
result += "a"; // создается 1000 новых строк
}


Лучше использовать StringBuilder:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("a");
}
String result = sb.toString();


2. Сравнение с null

Обращение к методам строки без проверки может привести к NullPointerException:
String name = null;
if (name.equals("admin")) { // ошибка
}


Правильнее писать:
if ("admin".equals(name)) { // безопасно
}


3. Пул строк и производительность
Пул строк позволяет уменьшить использование памяти, но злоупотребление вручную созданными строками через new String(...) может привести к увеличению нагрузки на сборщик мусора.

4. Конкатенация и производительность
Строки, объединённые с помощью + в цикле, могут ухудшать производительность. Лучше использовать StringBuilder или StringBuffer (если требуется потокобезопасность).

5. Использование в Map и Set

Поскольку строки неизменяемы, их можно безопасно использовать в качестве ключей в HashMap, HashSet и других коллекциях. Однако важно корректно переопределять equals() и hashCode() для классов, в которых строки используются в качестве полей для сравнения.



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

1. String vs StringBuilder vs StringBuffer
String — неизменяемый, потокобезопасный.
StringBuilder — изменяемый, не потокобезопасный, но самый быстрый.
StringBuffer — изменяемый, потокобезопасный, но медленнее.


2. Методы класса String

Строки обладают большим набором методов:
substring()
trim()
split()
replace()
matches() (регулярные выражения)
format() и другие


3. Регулярные выражения

Методы matches(), replaceAll() и split() поддерживают регулярные выражения, что делает String мощным инструментом для разбора и обработки текста.

4. Юникод и кодировка

Java строки используют UTF-16, где каждый символ — это один или два 16-битных элемента. Это важно при работе с Unicode-символами, особенно при подсчёте длины строки или извлечении символов.

5. Объекты String в коллекциях

Если строка используется как ключ в Map, важно помнить, что разные ссылки на строки с одинаковым содержимым будут считаться одинаковыми, если equals() и hashCode() совпадают — что работает корректно для String.

#Java #для_новичков #beginner #reference_types #String
Типы миграций и версионирование в Flyway

1. Структура SQL-миграций

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

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

Версионные миграции (Versioned)

Префикс V + версия + описание:
V<Версия>__<Описание>.sql


Пример:
-- V1__Create_users_table.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL
);


Повторяемые миграции (Repeatable)

Префикс R + описание:
R__<Описание>.sql


Пример:
-- R__Update_user_view.sql
CREATE OR REPLACE VIEW user_view AS
SELECT id, username FROM users;


2. Java-миграции

Позволяют описывать миграции на Java для сложной логики, недоступной в SQL.

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


JdbcMigration (устаревший):
public class V2__InsertTestData implements JdbcMigration {
@Override
public void migrate(Connection connection) throws Exception {
try (Statement stmt = connection.createStatement()) {
stmt.execute("INSERT INTO users (username) VALUES ('admin')");
}
}
}


JavaMigration (рекомендуемый):
public class V3__AddAdminUser implements JavaMigration {
@Override
public void migrate(Context context) throws Exception {
try (Statement stmt = context.getConnection().createStatement()) {
stmt.execute("INSERT INTO users (username) VALUES ('superuser')");
}
}
}


2.2. Правила

Классы должны находиться в classpath (обычно src/main/java/db/migration).
Имя класса соответствует имени файла SQL-миграции (например, V2__InsertTestData.java).

3. Правила версионирования

3.1. Семантическое версионирование

Рекомендуемый формат версии:
V<Мажор>.<Минор>.<Патч>__<Описание>.sql


Пример:
-- V1.2.3__Fix_email_constraint.sql
ALTER TABLE users ALTER COLUMN email SET NOT NULL;


Преимущества:
Понятная история изменений.
Совместимость с Semantic Versioning.


3.2. Откат изменений

Flyway не поддерживает автоматический откат. Альтернативы:

Новая миграция для отмены изменений:
-- V3__Drop_users_table.sql
DROP TABLE users;


Callbacks (SQL-хуки):
Скрипты beforeMigrate/afterMigrate для резервного копирования.

Важно: Не изменяйте уже примененные миграции — это нарушит целостность flyway_schema_history.

4. Примеры

4.1. SQL-миграции
-- V1__Initial_schema.sql
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100)
);

-- R__Update_view.sql
CREATE OR REPLACE VIEW active_users AS
SELECT * FROM users WHERE deleted_at IS NULL;


4.2.
Java-миграция
public class V4__EncryptPasswords implements JavaMigration {
@Override
public void migrate(Context context) throws Exception {
try (PreparedStatement ps = context.getConnection()
.prepareStatement("UPDATE users SET password = encrypt(password)")) {
ps.executeUpdate();
}
}
}


#Java #middle #Flyway
Ссылочные типы в Java — интерфейсы (interfaces)

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

Понимание интерфейсов необходимо для эффективного взаимодействия с фреймворками, библиотеками и построения легко расширяемого кода.

Создание интерфейсов

Интерфейсы объявляются с помощью ключевого слова interface. Они содержат объявления методов без реализации (до Java 8) или с ограниченной реализацией (начиная с Java 8):

public interface Animal {
void makeSound();
}


Класс, реализующий интерфейс, должен предоставить реализацию всех его методов:
public class Dog implements Animal {
public void makeSound() {
System.out.println("Woof");
}
}


Начиная с Java 8, интерфейсы могут содержать:
default-методы с реализацией
static-методы
private-методы (с
Java 9)

public interface Logger {
default void log(String msg) {
System.out.println("Log: " + msg);
}
}


Ключевые особенности:
Интерфейс не может содержать поля с реализацией — только public static final константы.
Все методы по умолчанию public abstract, если не указано иное.
Интерфейс — это тип, и его можно использовать как ссылку.


Использование интерфейсов

Интерфейсы позволяют описывать поведение без привязки к конкретной реализации:
public void processAnimal(Animal animal) {
animal.makeSound(); // работает с любым классом, реализующим Animal
}


Это даёт возможность:
использовать полиморфизм,
отделять абстракции от реализаций,
писать гибкий и расширяемый код.

Интерфейсы можно использовать как:
параметры методов
типы переменных
результаты возвращаемых значений
обобщенные типы


Также интерфейсы — неотъемлемая часть функционального программирования в Java 8+:
@FunctionalInterface
public interface Converter<F, T> {
T convert(F from);
}



Существование и удаление

Поскольку интерфейсы — это ссылочные типы, они работают как любые другие объекты. Переменная типа интерфейса — это ссылка на объект, реализующий этот интерфейс.
Animal animal = new Dog();


Здесь animal — это ссылка на объект типа Dog, но доступ к нему осуществляется через интерфейс Animal. Удаление объекта происходит по общим правилам: когда все ссылки исчезают, он становится доступным для сборки мусора.


Полиморфизм через интерфейсы

Интерфейсы — один из основных инструментов реализации полиморфизма. Код, написанный против интерфейсов, легко расширяем:
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();
Здесь List и Map — интерфейсы, а ArrayList и HashMap — конкретные реализации. Это позволяет заменить реализацию без изменения остального кода.


#Java #для_новичков #beginner #reference_types #Interface
Множественное наследование

Классы в 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