⌨️ Неограниченным 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
Java | Фишки и трюки
@java_tips_and_tricks
Java: примеры кода, интересные фишки и полезные трюкиКупить рекламу: https://telega.in/c/java_tips_and_tricks✍️По всем вопросам: @Pascal4egМенеджер по рекламе: @shmyzna
Похожие каналы
Все →Последние посты
⌨️ 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() возвращает true → hashCode() обязан быть одинаковым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(),но требуют строгого соблюдения контракта.Нарушишь - получишь баги, которые очень сложно отловить 🐛
⌨️ Ленивая инициализация через `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
🌱 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
🔗 В чем разница между == и .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
🖥 Блоки инициализации в 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("Загружаем конфиги..."); // Здесь можно читать файлы, подключать БД и т.д. }}▸ Для чего: инициализация кэшей, регистрация драйверов, загрузка ресурсов.📝 Когда что использовать- Нестатические блоки → простая инициализация полей - Статические блоки → настройка системных ресурсов 🧬Дополнение: Для улучшения читаемости кода используйте блоки инициализации для простых операций. Избегайте сложной логики — это может затруднить отладку и понимание приложения.
⌨️ Срезы в стримах. Метод 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
⌨️ Какими значениями инициализируются переменные по умолчанию?byte — (byte)0;short — (short)0;int — 0;long — 0L;float — 0f;double — 0d;char — \u0000;boolean — false;Объекты (в том числе String) — null.#java #initialization
⌨️ Чтение и запись файла с помощью класса FilesЧтение всего файла в список строк (readAllLines)import java.nio.file.*;import java.io.IOException;import java.util.List;public class Main { public static void main(String[] args) throws IOException { Path path = Paths.get("example.txt"); List<String> lines = Files.readAllLines(path); lines.forEach(System.out::println); }}📌 Читает весь файл в List<String>, удобно для небольших файлов.Запись в файл (write)List<String> lines = List.of("Первая строка", "Вторая строка");Files.write(Paths.get("output.txt"), lines);📌 Если файла нет — создаст, если есть — перезапишет.Добавление в файл (без перезаписи):Files.write(Paths.get("output.txt"), lines, StandardOpenOption.APPEND);#java #Files #readAllLines #write
⌨️ JVM Escape Analysis: Миф о том, что все объекты живут в Куче (Heap)Вспомните свои первые уроки по Java. Нам всем говорили: "Примитивы (int, double) живут в быстром Стеке (Stack), а вот объекты (new Object()) всегда создаются в медленной Куче (Heap), и потом за ними приходит Garbage Collector".Спойлер: В некоторых случаях JVM может вообще не создавать объект в heapСоздание объекта в Heap не бесплатно. Нужно выделить память, синхронизировать потоки, а потом еще и напрягать GC, чтобы этот мусор собрать. Поэтому JIT-компилятор научился магии под названием Escape Analysis (Анализ утечки).Как это работает?Допустим, у вас есть метод:public int calculateArea(int x, int y) { // Создаем временный объект Point p = new Point(x, y); return p.getX() * p.getY();}Казалось бы, при каждом вызове метода мы засоряем Heap новым объектом Point. Но JIT-компилятор во время работы программы анализирует код и задает себе вопрос: "А убегает ли этот объект за пределы метода?"В нашем случае p никуда не передается, не сохраняется в глобальную коллекцию и не возвращается наружу (возвращается только int). Объект не убегает (No Escape).✨ Магия Скалярной замены (Scalar Replacement)Поняв это, JVM принимает гениальное решение: она вообще не создает объект Point в Куче!Вместо этого она "расщепляет" объект на его составляющие примитивы (поля x и y) и держит их в регистрах или других внутренних структурах JIT.Для процессора этот код превращается во что-то такое:public int calculateArea(int x, int y) { int p_x = x; int p_y = y; return p_x * p_y;}Что это дает?1. 0 байт выделено в Heap.2. 0 миллисекунд потрачено на работу Garbage Collector.3. Скорость выполнения может быть очень близка к нативной производительности.Итог:В большинстве обычных случаев можно не бояться создавать короткоживущие объекты внутри методов (DTO, обертки, Optional), если это делает код чище. JVM достаточно умна, чтобы "схлопнуть" их и не нагружать память. Экономить на спичках и переиспользовать один объект через