Java | Фишки и трюки
6.93K subscribers
181 photos
33 videos
6 files
42 links
Java: примеры кода, интересные фишки и полезные трюки

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

✍️По всем вопросам: @Pascal4eg

Менеджер по рекламе: @shmyzna
Download Telegram
⌨️ Какими значениями инициализируются переменные по умолчанию?


byte — (byte)0;
short — (short)0;
int — 0;
long — 0L;
float — 0f;
double — 0d;
char — \u0000;
boolean — false;
Объекты (в том числе String) — null.


#java #initialization
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍10🥴21
⌨️ Срезы в стримах. Метод 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
8🔥3👍2
🖥 Блоки инициализации в Java

Блоки инициализации позволяют гибко управлять созданием объектов. Вот как их правильно использовать:

💠 Нестатические блоки

Выполняются перед конструктором:
public class Logger {
private String prefix;

{ // Блок инициализации экземпляра (выполняется перед конструктором)
prefix = "[LOG] " + LocalDateTime.now(); // Инициализация префикса с текущей датой/временем
System.out.println("Логгер готов!"); // Сообщение о готовности логгера
}

public Logger() {
System.out.println(prefix + " | Новый объект"); // Вывод информации о создании объекта
}
}

Для чего: предварительная настройка полей, валидация, логирование.

💠 Статические блоки

Срабатывают один раз при загрузке класса:
public class ConfigLoader {
static {
System.out.println("Загружаем конфиги...");
// Здесь можно читать файлы, подключать БД и т.д.
}
}

Для чего: инициализация кэшей, регистрация драйверов, загрузка ресурсов.

📝 Когда что использовать

- Нестатические блоки → простая инициализация полей
- Статические блоки → настройка системных ресурсов

🧬Дополнение:

Для улучшения читаемости кода используйте блоки инициализации для простых операций. Избегайте сложной логики — это может затруднить отладку и понимание приложения.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍61
🔗 В чем разница между == и .equals()?

При сравнении объектов в Java важно понимать различие между == и .equals().

✔️ == сравнивает ссылки на объекты, проверяя, указывают ли они на одну и ту же область памяти.

✔️ .equals() используется для сравнения содержимого объектов, если метод переопределен.

Пример:
String a = new String("Java");
String b = new String("Java");

System.out.println(a == b); // false (разные ссылки)
System.out.println(a.equals(b)); // true (сравнение содержимого)


💡 Совет: для корректного сравнения объектов всегда переопределяйте метод equals() в вашем классе.

#java #equals #comparison
Please open Telegram to view this post
VIEW IN TELEGRAM
👍71
🌱 Spring Cloud Config — это проект в экосистеме Spring, который предоставляет сервер и клиент для централизованного управления конфигурацией в распределённых системах. Основная идея Spring Cloud Config — это вынесение конфигурационных файлов из приложений в единое место (например, репозиторий Git), чтобы облегчить управление конфигурацией для различных сервисов в микросервисной архитектуре.

Основные компоненты

✔️ Config Server — сервер, который хранит конфигурации (обычно в Git) и раздает их микросервисам.

✔️ Config Client — клиент в микросервисах, который получает конфигурации от Config Server.


Возможности

✔️ Централизация конфигураций всех сервисов.

✔️ Поддержка версионирования конфигураций (например, через Git).

✔️ Динамическое обновление конфигураций без перезапуска приложений с помощью Spring Cloud Bus.

✔️ Поддержка различных сред и профилей (dev, prod и т.д.).


Пример настройки Config Server:

spring:
cloud:
config:
server:
git:
uri: https://github.com/your-repo/config-repo


Пример настройки Config Client:

spring:
cloud:
config:
uri: http://localhost:8888
profile: dev


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

Недостатки: важен контроль за доступом и стабильностью Config Server.

#java #Spring #Cloud #Config
Please open Telegram to view this post
VIEW IN TELEGRAM
👍51
⌨️ Ленивая инициализация через `Supplier` - мощнее, чем кажется

В Java есть недооценённая фишка - ленивые вычисления через Supplier. Это может сильно упростить код и улучшить производительность.

📦 Что такое `Supplier`?
Это функциональный интерфейс из java.util.function, который просто возвращает значение:

Supplier<String> supplier = () -> "Hello";
System.out.println(supplier.get());


Но магия начинается, когда используешь его правильно 👇

🔥 Кейс: дорогое вычисление

public String getData() {
System.out.println("Loading...");
return "data";
}


Теперь сравни:

Обычный вызов:

String result = getData(); // всегда выполняется


Ленивый вызов:

Supplier<String> supplier = this::getData;

// вызов произойдёт только здесь
String result = supplier.get();


👉 Код выполняется только когда реально нужен результат.

Практическое применение: логирование

logger.debug("Result: " + expensiveOperation());


Даже если debug выключен — expensiveOperation() всё равно выполнится.

Правильно:

logger.debug(() -> "Result: " + expensiveOperation());


👉 Вычисление произойдёт только если лог реально пишется.

🧠 Комбинация с `Optional`

String name = Optional.ofNullable(getName())
.orElseGet(() -> generateDefaultName());


👉 generateDefaultName() вызовется только если значение отсутствует
(в отличие от orElse, который выполняется всегда)

🚀 Паттерн: кэширование (lazy cache)

Supplier<String> cached = new Supplier<>() {
private String value;

@Override
public String get() {
if (value == null) {
value = loadExpensiveData();
}
return value;
}
};


👉 Получаешь простую ленивую инициализацию без лишних библиотек

📌 Вывод
Supplier — это не просто "лямбда ради лямбды", а инструмент для:
* ленивых вычислений
* оптимизации
* более чистого API
Please open Telegram to view this post
VIEW IN TELEGRAM
10👍1
⌨️ equals() и hashCode() - почему они ломают коллекции (и как это исправить)

Одна из самых коварных тем в Java — это контракт между equals() и hashCode(). Ошибся и HashMap или HashSet начинают вести себя "магически" (читай: ломаются).

📦 Проблема на практике

Set<User> users = new HashSet<>();

users.add(new User("Alex"));
System.out.println(users.contains(new User("Alex"))); // false


🤨 Почему false, если значения одинаковые?

⚠️ Причина
По умолчанию equals() сравнивает ссылки (==), а не содержимое.

Правильная реализация

public class User {
private String name;

// equals
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(name, user.name);
}

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


🔥 Почему важны ОБА метода?

Если переопределить только equals():

users.add(new User("Alex"));
users.contains(new User("Alex")); // всё ещё может быть false


👉 Потому что HashSet сначала смотрит на hashCode(), а потом уже на equals().

Контракт, который нельзя нарушать

1. Если equals() возвращает truehashCode() обязан быть одинаковым
2. Если hashCode() одинаковый → equals() может быть как true, так и false

💣 Классическая ошибка

Изменяем объект после добавления в HashSet:


User user = new User("Alex");
users.add(user);

user.setName("Bob");

users.contains(user); // может вернуть false


👉 Объект "потерялся" в коллекции

🧠 Как избежать проблем

* Делай объекты immutable (final поля)
* Используй record (Java 16+):


public record User(String name) {}


👉 equals() и hashCode() генерируются автоматически и корректно

📌 Вывод
Коллекции в Java работают быстро благодаря hashCode(),
но требуют строгого соблюдения контракта.

Нарушишь - получишь баги, которые очень сложно отловить 🐛
Please open Telegram to view this post
VIEW IN TELEGRAM
👍71
⌨️ Неограниченным wildcard <?>

В Java <?> называется неограниченным wildcard. Он обозначает, что параметр типа может быть любым, то есть неизвестным на этапе компиляции. Это удобно, когда метод или класс работают с обобщёнными типами, но конкретный тип элемента не важен.

Например, выражение List<?> означает список, элементы которого могут быть любого типа. Такой подход позволяет писать более универсальный и гибкий код, но при этом накладывает ограничения: мы не можем добавлять новые элементы в коллекцию, поскольку компилятор не знает конкретный тип элементов, чтобы обеспечить безопасность типов.

✔️ Прием метода, работающего с любым типом списка


import java.util.List;

public class WildcardExample {
public static void printList(List<?> list) {
for (Object element : list) { // Элементы можно читать как Object
System.out.println(element);
}
}

public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3);
List<String> strList = List.of("A", "B", "C");

printList(intList); // Вывод: 1 2 3
printList(strList); // Вывод: A B C
}
}


Здесь List<?> позволяет передавать любой тип списка, но мы можем безопасно читать только как Object.

Ограничение на добавление элементов


public static void addElement(List<?> list) {
// list.add("Hello"); // Ошибка компиляции!
}


#java #wildcard
Please open Telegram to view this post
VIEW IN TELEGRAM
👍61
☕️ Возвращение пустых коллекций вместо null

Возвращение пустых коллекций вместо null-это рекомендуемый подход для методов, возвращающих коллекции. Он упрощает обработку данных, предотвращает ошибки и делает код более предсказуемым.

Преимущества

1. Улучшение читаемости: Код становится проще, так как не нужно проверять результат на null.
2. Предотвращение NullPointerException: Исключается вероятность ошибок, связанных с доступом к null.
3.Соответствие принципу наименьшего удивления: Методы всегда возвращают коллекцию, даже если она пуста.
4. Эффективность: Пустые коллекции создаются один раз и переиспользуются благодаря реализации через паттерн Singleton.
5. Совместимость с функциональным программированием: Пустые коллекции легко интегрируются в стримы и другие функциональные конструкции.

Пример:

public class CacheService {
    private final Map<String, List<Object>> cache = new ConcurrentHashMap<>();

    public List<Object> getCachedValues(String key) {
        return cache.getOrDefault(key, Collections.emptyList());
    }

    public void addToCache(String key, Object value) {
        cache.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
    }
}


❗️ Метод Collections.emptyList() является частью стандартной библиотеки Java (java.util.Collections). Он реализован через паттерн Singleton для повышения эффективности и безопасности.

Использование пустых коллекций вместо null делает код более устойчивым и предсказуемым. Это особенно важно в сложных многопоточных приложениях или системах с большим количеством взаимосвязанных компонентов.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍41
⌨️ Почему `try-with-resources` это больше, чем "синтаксический сахар"

Многие воспринимают try-with-resources как просто удобную замену finally. Но под капотом есть важная деталь, о которой часто забывают - подавленные исключения (suppressed exceptions).

📦 Классический код

try {
InputStream is = new FileInputStream("file.txt");
// работа с потоком
} finally {
is.close();
}


Проблема: если в try и в close() произойдут исключения - одно из них потеряется.

try-with-resources

try (InputStream is = new FileInputStream("file.txt")) {
// работа с потоком
}


👉 Java сама закроет ресурс и корректно обработает исключения

🔥 Вот где магия

try (MyResource r = new MyResource()) {
throw new RuntimeException("Ошибка в try");
}


И допустим, close() тоже кидает исключение:

@Override
public void close() {
throw new RuntimeException("Ошибка при закрытии");
}


🤯 Что произойдёт?
* Основное исключение: "Ошибка в try"
* Исключение из close() НЕ потеряется
* Оно попадёт в suppressed exceptions

📌 Как их получить

try (MyResource r = new MyResource()) {
...
} catch (Exception e) {
for (Throwable t : e.getSuppressed()) {
t.printStackTrace();
}
}


⚠️ Почему это важно

Если не знаешь про suppressed exceptions:
* можно потерять важную информацию об ошибках
* сложнее дебажить ресурсы (файлы, сокеты, DB)

🧠 Кастомные ресурсы

Чтобы использовать try-with-resources, класс должен реализовывать AutoCloseable:

class MyResource implements AutoCloseable {
@Override
public void close() {
// cleanup
}
}


👉 Даже свои классы можно встроить в этот механизм

🚀 Маленький лайфхак (Java 9+)
Можно объявить ресурс заранее:

InputStream is = new FileInputStream("file.txt");

try (is) {
// используем
}


📌 Вывод
try-with-resources это не только удобство, а ещё и правильная модель обработки исключений, которую вручную реализовать сложно.
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1
📌 Что такое volatile и как оно работает?

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

✔️ Основные особенности:

1️⃣ Обеспечивает чтение переменной из основной памяти, а не из кэша потока.
2️⃣ Гарантирует видимость изменений между потоками.

Пример:
class SharedObject {
volatile int count = 0;
}


💡 Ограничение: volatile не гарантирует атомарность операций. Для этого используйте synchronized или классы из java.util.concurrent.

#java #volatile #multithreading
Please open Telegram to view this post
VIEW IN TELEGRAM
👍42
⌨️ Arrays.deepToString()

Метод deepToString из класса Arrays используется для создания строкового представления многомерных массивов (например, массивов массивов). Он обходит каждый уровень вложенности массива и выводит его элементы в виде строки. Это удобно для работы с многомерными массивами, так как стандартный метод toString не раскрывает их структуру.

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

int[][] array = {{1, 2, 3}, {4, 5, 6}};
System.out.println(Arrays.deepToString(array));
// [[1, 2, 3], [4, 5, 6]]

Этот метод работает рекурсивно, обеспечивая полное отображение структуры массива любой вложенности.

#java #Arrays #deepToString
Please open Telegram to view this post
VIEW IN TELEGRAM
👍72
🛠 Разница между StringBuilder и StringBuffer

И StringBuilder, и StringBuffer используются для работы со строками, но между ними есть важные отличия.

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

✔️ StringBuilder быстрее, но не потокобезопасен.
✔️ StringBuffer потокобезопасен, но медленнее из-за синхронизации.

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

StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
System.out.println(sb); // Hello World


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

StringBuffer sb = new StringBuffer("Hello");
sb.append(" World");
System.out.println(sb); // Hello World


💡 Совет: Если ваш код выполняется в одном потоке, используйте StringBuilder для лучшей производительности.

#java #stringbuilder #stringbuffer
Please open Telegram to view this post
VIEW IN TELEGRAM
3👍1🆒1
⌨️ Чем абстрактный класс отличается от интерфейса? В каких случаях следует использовать абстрактный класс, а в каких интерфейс?

✔️ В Java класс может одновременно реализовать несколько интерфейсов, но наследоваться только от одного класса.

✔️ Абстрактные классы используются только тогда, когда присутствует тип отношений «is a» (является). Интерфейсы могут реализоваться классами, которые не связаны друг с другом.

✔️ Абстрактный класс - средство, позволяющее избежать написания повторяющегося кода, инструмент для частичной реализации поведения. Интерфейс - это средство выражения семантики класса, контракт, описывающий возможности. Все методы интерфейса неявно объявляются как public abstract или (начиная с Java 8) default - методами с реализацией по-умолчанию, а поля - public static final.

✔️ Интерфейсы позволяют создавать структуры типов без иерархии.

✔️ Наследуясь от абстрактного, класс «растворяет» собственную индивидуальность. Реализуя интерфейс, он расширяет собственную функциональность.

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

#java #interface #abstract
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1🥴1