Java Geek
2.45K subscribers
272 photos
1 file
24 links
Практичные советы, лайфхаки и код для Java-разработчиков. Каждый пост — реальная польза. Учим Java на примерах.

По всем вопросам @evgenycarter
Download Telegram
Что вернет выражение 1.0/0.0? Приведет ли оно к генерации исключения или ошибке при компиляции?

Еще один каверзный вопрос насчет класса Double. Хотя разработчики Java знают о существовании простого типа данных double и класса Double, при выполнении операций с плавающей точкой они не уделяют достаточного внимания Double.INFINITY, NaN, -0.0 и правилам, которым подчиняются связанные с ними арифметические вычисления.

Ответ на этот вопрос прост: генерации исключения ArithmeticException не произойдет, будет возвращено значение Double.INFINITY.

👉 @java_geek
👍31
Что такое каскадность? Как она используется в Hibernate?

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

Например, если у нас есть отношение между сущностями Person и Address, и без сущности Person сущность Address не имеет смысла. Когда мы удаляем сущность Person, наша сущность Address также должна быть удалена. Каскадность — это способ достижения этого.

В JPA/Hibernate имеются различные типы каскадности, которые определяют, какие операции должны распространяться на связанные сущности. Например, CascadeType.ALL распространяет все операции от родительской сущности на дочернюю.

👉 @java_geek
2👍1
Почему метод clone объявлен как protected?

Метод clone() по умолчанию объявлен как protected, чтобы ограничить возможность клонирования объектов.
Если бы метод был public, то любой код мог бы клонировать объекты класса. А это не всегда желательно по соображениям безопасности и корректности программы.

С protected доступом клонирование разрешено только внутри класса и его наследников. Таким образом разработчик класса может сам решить, будет ли класс поддерживать клонирование и как именно оно будет реализовано в методе clone().

Делая клонирование protected по умолчанию, Java принуждает разработчика сознательно разрешить клонирование класса путем переопределения метода clone() как public в самом классе.
Это позволяет более гибко контролировать возможности клонирования в каждом конкретном классе.

👉 @java_geek
👍42
Расскажите о различиях вложенных классов: простых и статических?

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

Основные различия:

- Простой вложенный класс имеет доступ к членам внешнего класса, статический - нет.

- Для создания экземпляра простого вложенного класса нужен экземпляр внешнего класса, для статического - нет.

- Простой вложенный класс зависит от экземпляра внешнего класса, статический - нет.

👉 @java_geek
3👍1
Зачем нужен HashMap, если есть Hashtable?

Методы класса Hashtable синхронизированы, что приводит к снижению производительности, а HashMap - нет;
HashTable не может содержать элементы null, тогда как HashMap может содержать один ключ null и любое количество значений null;
Iterator у HashMap, в отличие от Enumeration у HashTable, работает по принципу «fail-fast» (выдает исключение при любой несогласованности данных).

Hashtable это устаревший класс и его использование не рекомендовано.

👉 @java_geek
👍31
Что такое Local Variable?

Популярный вопрос на собеседовании Java-разработчика. Local variable — это переменная, которая определена внутри метода и существует вплоть до того момента, пока выполняется этот метод. Как только выполнение закончится, локальная переменная перестанет существовать.

Вот программа, которая использует локальную переменную helloMessage в методе main().

👉 @java_geek
👍1
Что такое «сессия»?

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

👉 @java_geek
👍41
This media is not supported in your browser
VIEW IN TELEGRAM
Блиц-опрос Scala-разработчиковсамые полезные и бесполезные вещи в работе

Смотрите подкаст «Криптонит говорит» о Scala! В нём айтишники обсуждают:
🔹Scala, Java и их перспективы;
🔹как успешно пройти собеседование в айти;
🔹что нужно делать, чтобы стать хорошим программистом;
🔹и когда нейросети смогут делать code review на уровне старшего разработчика и многое другое.

📺 VK Видео
📺
YouTube
📺 Rutube
💬 Подкаст в телеграме
🎵 Яндекс.Музыка

Смотрите и подписывайтесь на подкаст «Криптонит говорит» — обсуждаем айти, искусственный интеллект, языки программирования и криптографию.

Реклама АО НПК «Криптонит» ИНН 9701115253, erid: 2VtzqveAsgd
О чем говорит ключевое слово throws?

Ответ:
Модификатор throws прописывается в заголовке метода и указывает на то, что метод потенциально может выбросить исключение с указанным типом.

👉 @java_geek
👍21
Что не так с кодом?

Он не скомпилируется. Это вопрос на знание иерархии исключений: FileNotFoundException унаследован от IOException, первый catch будет перехватывать все исключения, а в следующий блок catch управление передано не будет. Поэтому возникнет ошибка: exception FileNotFoundException has already been caught.

👉 @java_geek
👍1
Что такое пул строк? Это набор строк, хранящийся в Heap.

Пул строк возможен благодаря неизменяемости строк в Java и реализации идеи интернирования строк;
Пул строк помогает экономить память, но по этой же причине создание строки занимает больше времени;
Когда для создания строки используются ", то сначала ищется строка в пуле с таким же значением, если находится, то просто возвращается ссылка, иначе создается новая строка в пуле, а затем возвращается ссылка на неё;
При использовании оператора new создаётся новый объект String. Затем при помощи метода intern() эту строку можно поместить в пул или же получить из пула ссылку на другой объект String с таким же значением;
Пул строк является примером паттерна «Приспособленец» (Flyweight).

👉 @java_geek
👍32
🚀 Hibernate: Эффективное обновление с @DynamicUpdate

Часто ли вы задумывались, почему Hibernate при обновлении одного поля в сущности генерирует SQL-запрос, который включает все поля? Это может быть неэффективно, особенно для сущностей с десятками колонок.

Именно здесь на помощь приходит аннотация @DynamicUpdate!

💡 В чем проблема? (Без @DynamicUpdate)

По умолчанию, когда вы вызываете repository.save(entity) для существующей сущности, Hibernate генерирует UPDATE запрос, который устанавливает значения для всех полей, кроме первичного ключа.


// Without @DynamicUpdate
update employee
set
age=?, // даже если только age изменился
first_name=?, //... и это тоже
last_name=?
where
id=?


Если изменилось только age, остальные поля обновляются на те же значения, что были.

Решение: @DynamicUpdate(true)

Если вы добавите @DynamicUpdate к вашей сущности, Hibernate будет генерировать SQL-запрос UPDATE, который включает только те поля, которые были изменены (dirty-checking) с момента загрузки или создания сущности.

Смотрим на код:


@Entity
@DynamicUpdate // Добавляем эту аннотацию!
public class Employee extends AbstractPersistable<Long> {
@Column
String firstName;

@Column
String lastName;

@Column
Integer age;
// ...
}

// В EmployeeService:
// var entity = employeeRepository.findById(1L).get();
// entity.setAge(22); // Изменили только возраст
// employeeRepository.save(entity);


Сгенерированный SQL:


// With @DynamicUpdate
update employee
set
age=? // Обновляется ТОЛЬКО измененное поле
where
id=?


⚖️ Когда использовать?

- Используйте, если у вас большие сущности (много колонок) и вы часто обновляете только небольшую часть полей. Это сэкономит трафик и немного ускорит базу данных.
- Имейте в виду, что Hibernate придется динамически строить SQL-запрос при каждом обновлении, что может добавить небольшой оверхед, но обычно выгода от оптимизации SQL его перевешивает.

Сделайте ваш код более чистым и эффективным! 🛠️


А вы используете @DynamicUpdate в своих проектах? Поделитесь в комментариях! 👇

P.S. Не путайте с @DynamicInsert, который делает то же самое для INSERT запросов (включает только не-null поля).

👉 @java_geek
🔥82
☕️ Лямбды и Функциональные интерфейсы: Как это работает под капотом?

Мы все любим лямбды в Java за краткость. Но задумывались ли вы, как строго типизированная Java понимает запись x -> x * 2, не зная типа переменной x?

Все дело в Функциональных интерфейсах. Давайте разберем механику, которая превращает "бойлерплейт" в элегантный код.

🧩 Что такое SAM?
В основе лежит концепция SAM (Single Abstract Method). Функциональный интерфейс - это интерфейс, у которого есть ровно один абстрактный метод.

Лямбда-выражение - это не самостоятельный объект. Это ленивая реализация этого самого единственного метода "на лету".

🔥 До vs После
Взгляните, как функциональный интерфейс Predicate<T> убивает лишний шум:


// До Java 8 (Анонимный класс)
// Куча лишнего кода ради одной проверки
Predicate<String> isLong = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() > 5;
}
};

// С Лямбдой
// Компилятор видит Predicate -> ищет метод test -> подставляет логику
Predicate<String> isLong = s -> s.length() > 5;



⚙️ Магия invokedynamic
Многие думают, что лямбда - это просто "сахар" для анонимных классов. Это не так!

💜 Анонимный класс создает реальный .class файл на диске при компиляции.
💜 Лямбда использует инструкцию байт-кода invokedynamic. Она связывает метод динамически только когда это нужно.
👉 Итог: Меньше мусора в памяти и быстрее загрузка приложения.

📚 Шпаргалка: "Великолепная четверка"
В java.util.function есть интерфейсы на все случаи жизни. Запомните базу:

1. Predicate<T>boolean
💜 Суть: Проверка условия (фильтры).
💜 Пример: stream().filter(x -> x > 0)


2. Consumer<T>void
💜 Суть: Потребитель. Что-то делает с объектом, ничего не возвращая.
💜 Пример: list.forEach(System.out::println)


3. Function<T, R>R
💜 Суть: Преобразователь. Берет T, возвращает R.
💜 Пример: stream().map(User::getName)


4. Supplier<T>T
💜 Суть: Поставщик. Ничего не принимает, отдает объект.
💜 Пример: Optional.orElseGet(() -> new User())


💡Всегда ставьте аннотацию @FunctionalInterface над своими интерфейсами. Это защитит от случайного добавления второго абстрактного метода, который сломает все лямбды в проекте.

👉 @java_geek
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥63
📌 Декоратор в Java: Как добавить логику без изменения кода?

🔹 Когда использовать?
- Когда нужно добавить поведение к объекту динамически.
- Когда нельзя или не хочется менять исходный код класса.
- Когда необходимо сохранить принцип открытости/закрытости (OCP из SOLID).

🔹 Как это работает?
Декоратор — это обёртка вокруг базового объекта. Он реализует тот же интерфейс, но внутри может добавлять новую логику.

Пример использования
Допустим, у нас есть базовый интерфейс Notifier, который отправляет уведомления:


public interface Notifier {
void send(String message);
}


И его простая реализация:


public class BasicNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("Отправка сообщения: " + message);
}
}


Теперь добавим декораторы, которые расширяют функциональность:
1️⃣ Декоратор для отправки в Slack:

public class SlackNotifierDecorator implements Notifier {
private final Notifier wrapped;

public SlackNotifierDecorator(Notifier wrapped) {
this.wrapped = wrapped;
}

@Override
public void send(String message) {
wrapped.send(message); // вызываем базовый метод
System.out.println("Дополнительно отправляем в Slack: " + message);
}
}


2️⃣ Декоратор для отправки в Email:

public class EmailNotifierDecorator implements Notifier {
private final Notifier wrapped;

public EmailNotifierDecorator(Notifier wrapped) {
this.wrapped = wrapped;
}

@Override
public void send(String message) {
wrapped.send(message);
System.out.println("Дополнительно отправляем Email: " + message);
}
}


🚀 Использование:

public class Main {
public static void main(String[] args) {
Notifier notifier = new BasicNotifier();
notifier = new SlackNotifierDecorator(notifier);
notifier = new EmailNotifierDecorator(notifier);

notifier.send("Привет, мир!");
}
}


🔥 Что произойдет?

Отправка сообщения: Привет, мир!
Дополнительно отправляем в Slack: Привет, мир!
Дополнительно отправляем Email: Привет, мир!


🎯 Итог:
Мы не изменяли код BasicNotifier, но добавили новую функциональность.
Гибкость: можем легко комбинировать декораторы в любом порядке.
Код остаётся чистым и расширяемым.

👉 @java_geek
4👍2👏2🔥1
☕️ Java Tips: Инициализация карты в одну строку с Map.of()

Помните, как раньше приходилось создавать Map с заранее известными значениями? Куча вызовов .put(), статические блоки или (не дай бог) двойные фигурные скобки. Начиная с Java 9, у нас есть красивый и лаконичный способ - Map.of().

Разберем, как это работает и, главное, чего нельзя делать.

🆚 До и После

Как было раньше (The Old Way):


Map<String, Integer> map = new HashMap<>();
map.put("One", 1);
map.put("Two", 2);
map.put("Three", 3);
// Результат: много строк кода ради простых данных



Как сейчас (The Modern Way):


Map<String, Integer> map = Map.of(
"One", 1,
"Two", 2,
"Three", 3
);
// Результат: чисто, читаемо, одна инструкция



⚠️ Важные нюансы (Gotchas)

Использование Map.of() - это не просто синтаксический сахар для HashMap. Это создание неизменяемой (Immutable) структуры.

1. Нельзя менять данные
Попытка добавить или удалить элемент приведет к ошибке.


var colors = Map.of("Red", "#FF0000");
colors.put("Blue", "#0000FF");
// Ошибка: UnsupportedOperationException



2. Никаких null
В отличие от HashMap, здесь нельзя использовать null ни в ключах, ни в значениях.


Map.of("Key", null);
// Ошибка: NullPointerException



3. Лимит в 10 пар
Метод Map.of() перегружен для приема до 10 пар ключ-значение. Если нужно больше, используйте Map.ofEntries():


Map.ofEntries(
Map.entry("k1", "v1"),
Map.entry("k2", "v2"),
// ... хоть 100 пар
Map.entry("k100", "v100")
);



🚀 Когда использовать?

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

#Java #CodeTips #Programming #Java9

👉 @java_geek
👍41🤡1
⚔️ Java Battle: List.of() vs Arrays.asList() - в чем разница?

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

Давайте разберем, почему замена одного на другой может сломать ваш код.

1. Иллюзия неизменяемости (The Mutability Trap)

Это самое важное различие.

🔴List.of() (Java 9+): Создает по-настоящему неизменяемый список. Вы не можете ни добавить, ни удалить, ни изменить элемент.
🔴Arrays.asList() (Java 1.2+): Создает список фиксированного размера, который "обернут" вокруг массива. Вы не можете менять размер (add/remove), но можете заменять элементы!


var legacyList = Arrays.asList("A", "B");
var modernList = List.of("A", "B");

legacyList.set(0, "C"); // РАБОТАЕТ! Список теперь ["C", "B"]
modernList.set(0, "C"); // Ошибка: UnsupportedOperationException

legacyList.add("D"); // Ошибка (размер фиксирован)
modernList.add("D"); // Ошибка (полная иммутабельность)



2. Отношение к null

🔴Arrays.asList: Разрешает null элементы.
🔴List.of: Враждебен к null. Если попытаетесь передать null, мгновенно получите NullPointerException. Это сделано специально, чтобы избежать ошибок в логике.

3. Связь с исходным массивом (Reference vs Copy)

Если вы создаете список из существующего массива:


String[] arr = {"One", "Two"};
var list1 = Arrays.asList(arr);
var list2 = List.of(arr);

arr[0] = "Zero"; // Меняем исходный массив

System.out.println(list1); // ["Zero", "Two"] <- Изменился вслед за массивом!
System.out.println(list2); // ["One", "Two"] <- Остался прежним



Arrays.asList работает как "окно" (view) в массив. List.of создает защитную копию данных.

🏁 Итог

🔴Используйте List.of() в 99% случаев. Это безопаснее, быстрее и потребляет меньше памяти.
🔴Используйте Arrays.asList(), только если вам нужны null-ы или вы намеренно хотите, чтобы изменения в исходном массиве отражались в списке (редкий кейс).

#Java #CodeTips #InterviewQuestions #JavaCore

👉 @java_geek
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍2
🕵️ Java var: Удобный сахар или скрытая угроза?

С выходом Java 10 ключевое слово var позволило нам писать меньше кода. var - это Local Variable Type Inference. Это значит, что Java осталась строго типизированным языком, просто теперь компилятор сам догадывается о типе переменной, глядя на то, что находится справа от знака равно (=).

Но иногда его догадки могут вас удивить. Вот 3 примера, где var работает неочевидно.

1. Ловушка "Diamond Operator" (<>)

Самая частая ошибка новичков.


// Без var (Классика)
List<String> list = new ArrayList<>();
// Компилятор видит слева List<String> и понимает, что справа тоже String.

// С var (Ошибка)
var list = new ArrayList<>();



В чем проблема?
У компилятора нет информации о типе. Он видит пустые скобки <> и решает, что это ArrayList<Object>.
В итоге вы теряете типизацию:


list.add("Hello");
list.add(123); // Это сработает, хотя вы, вероятно, хотели только строки!



Как исправить:
Если используете var с конструктором, всегда указывайте тип справа:


var list = new ArrayList<String>();



2. Магия "Пересечения типов" (Intersection Types)

А вот это уже высший пилотаж, который часто встречается при использовании List.of() или Map.of().

Что будет, если сложить в список данные разных типов?


var magicList = List.of(10, 20.5, "30");
// Какой тут тип списка? List<Object>?



На самом деле компилятор выведет наиболее специфичный общий тип.
Тип переменной magicList будет выглядеть примерно так:
List<? extends Serializable & Comparable<? extends Serializable & Comparable<?>>>

Java находит общие интерфейсы для Integer, Double и String (все они реализуют Serializable и Comparable) и создает этот ужасный тип-франкенштейн. Это работает, но может свести с ума вашу IDE или методы, принимающие конкретные типы.

3. Анонимные классы на стероидах

var позволяет делать трюк, невозможный ранее: сохранять тип анонимного класса.


var user = new Object() {
String name = "Alex";
int age = 25;
};

// Это работает!
System.out.println(user.name);
System.out.println(user.age);



Если бы мы написали Object user = ..., поля name и age были бы недоступны. А var "видит" реальную структуру анонимного объекта. Это иногда полезно для локальных промежуточных вычислений, заменяя DTO или кортежи.

🧠 Золотое правило использования

var хорош тогда, когда тип очевиден из правой части:

🔴👍 var users = Map.of("id", 1); (Понятно, что это Map)

🔴👍 var stream = list.stream(); (Понятно, что Stream)

🔴👎 var result = service.process(); (Что вернулось? boolean? User? null?)

Читаемость кода важнее краткости!

#Java #CleanCode #Var #Java10

👉 @java_geek
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍21
💿 Java Records: Конец эпохи Lombok?

Долгие годы Project Lombok был нашим единственным спасением от бесконечных геттеров, сеттеров, equals() и hashCode(). Но начиная с Java 14 (и официально в Java 16), в языке появилась нативная альтернатива - Records.

Многие поспешили удалить Lombok и переписать всё на рекорды. И... столкнулись с проблемами. Давайте разберем, почему record - это не просто "короткий класс".

🏎 Скорость написания

Lombok (@Value для неизменяемости):


@Value
public class User {
String name;
int age;
}



Record:


public record User(String name, int age) {}



Тут победа за рекордами. Синтаксис максимально лаконичен. Мы объявляем состояние, а не поля.

🔍 Что под капотом? (Главные отличия)

Вот здесь начинаются нюансы, из-за которых Records нельзя назвать полной заменой Lombok.

1. Жесткая неизменяемость (Immutability)
Record - это всегда final класс с final полями. Вы не можете сделать "сеттер" в рекорде. Если вам нужен изменяемый DTO (например, для заполнения формы по шагам) - Record не подойдет. Lombok @Data всё еще нужен.

2. Запрет на наследование
Рекорды не могут наследоваться от других классов (они уже неявно наследуются от java.lang.Record).

• Если у вас есть BaseEntity с полем ID - вы не сможете унаследовать рекорд от него.

3. Имена геттеров
Это ломает многие старые библиотеки.

• Lombok/JavaBean: getName(), getAge()
• Record: name(), age()
Совет: Jackson и современные JSON-библиотеки уже умеют с этим работать, но легаси-фреймворки могут "не увидеть" ваши поля.

Killer Feature: Компактные конструкторы

То, чего нет в Lombok. Валидация данных в рекордах выглядит потрясающе чисто. Вам не нужно дублировать аргументы:


public record User(String name, int age) {
// Обратите внимание: нет скобок с аргументами!
public User {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
name = name.toUpperCase(); // Можно даже менять данные перед записью
}
}



🏆 Вердикт: Кто победил?

Никто. Это инструменты для разных целей.

Используйте record: Для DTO, ключей в Map, ответов API, записей в Event Log и промежуточных данных в Stream API. Это "именованные кортежи" данных.

Используйте Lombok / POJO: Для JPA сущностей (Hibernate не очень любит рекорды из-за прокси и отсутствия сеттеров), для классов с наследованием и там, где нужна мутабельность.

#Java #Lombok #CleanCode #Java17

👉 @java_geek
👍21🔥1
🔒 Java Sealed Classes: Диктатура в вашей иерархии

Раньше в Java у нас было всего два стула для классов:

1. Public: Наследуйся кто хочет (открытый проходной двор).
2. Final: Никто не пройдет (полная изоляция).

Но что, если я хочу разрешить наследование только моим классам Circle и Square, но запретить Васе из соседнего отдела создавать свой кривой Triangle?
Начиная с Java 17, у нас есть Sealed Classes.

🚧 Как это работает?

Вы используете ключевое слово sealed и permits, чтобы явно перечислить, кому дозволено быть вашим наследником.


public sealed interface PaymentResult
permits Success, Failure, Pending {
}



Теперь компилятор гарантирует: в мире существуют только три вида PaymentResult. Четвертого не дано.

🤝 Идеальная пара: Sealed + Records

Чаще всего наследниками делают record, потому что они идеально подходят для хранения данных.


public record Success(String txId) implements PaymentResult {}
public record Failure(String error) implements PaymentResult {}
public record Pending(long timestamp) implements PaymentResult {}



🧠 Главная фишка: Умный Switch (Pattern Matching)

Зачем нам эти ограничения? Ради исчерпываемости (Exhaustiveness).
Когда вы используете sealed классы в новых switch (Java 21+), компилятор знает все возможные варианты.

Вам не нужно писать default ветку!


String message = switch (result) {
case Success s -> "Paid! ID: " + s.txId();
case Failure f -> "Error: " + f.error();
case Pending p -> "Wait...";
// Нет default! И это безопасно.
};



В чем магия? Если через полгода вы добавите в permits новый вариант Cancelled, ваш код перестанет компилироваться везде, где используется этот switch. Компилятор ткнет вас носом: "Ты забыл обработать новый статус!". Это спасает от сотен багов в сложной бизнес-логике.

📜 Три правила для наследников

Наследник sealed класса обязан выбрать одну из трех стратегий:

1. final: На мне иерархия заканчивается (как в Records).
2. sealed: Я продолжаю жесткий контроль, вот мои наследники.
3. non-sealed: Я открываю шлюзы - от меня может наследоваться кто угодно (возврат к старому поведению).

🚀 Итог: Используйте Sealed Classes, когда ваша модель данных представляет собой конечное множество вариантов:

🔴Статусы заказа
🔴Типы пользователей (Admin, User, Guest)
🔴Результаты операций (Success, Error)

Это делает код предсказуемым и безопасным на уровне компилятора.

#Java17 #Architecture #CleanCode #PatternMatching

👉 @java_geek
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍2