Java Talks
Иконка канала Java Talks

Java Talks

1 подписчик

2просмотра

Деление на ноль в Java ведёт себя совершенно по-разному для целых и вещественных чисел. Этот нюанс часто упускают, пока не столкнутся на собеседовании или в реальном баге. В видео — короткий пример и четыре варианта ответа. Выбери свой, а правильный разбор выйдет завтра. Сохрани пост — пригодится, когда меньше всего ждёшь. Напиши в комментариях, какой вариант выбрал — интересно посмотреть, кто попадётся.

1просмотр

Джун пишет logger.info("value: " + expensiveOp()) и не понимает, почему приложение тормозит, даже когда уровень INFO выключен. Сеньор знает, что конкатенация происходит до вызова метода, поэтому дорогая операция выполняется в любом случае. Он использует ленивое логирование. Почему конкатенация вычисляется всегда? В Java аргументы метода вычисляются до того, как метод будет вызван. expensiveOp() выполнится, чтобы получить строку, которая передаётся в logger.info(). Даже если логгер настроен на WARN и INFO-сообщения не пишутся, операция уже совершена. ⚠️ Нюанс: Плейсхолдеры {} в SLF4J (logger.debug("value: {}", expensiveOp())) не ленивы — аргумент всё равно вычисляется до вызова. Для ленивого вычисления нужно использовать проверку isEnabled или лямбду-супплера.

2просмотра

Перегрузка методов — базовая тема, но передача null способна запутать даже опытных. В видео — короткий код и четыре варианта ответа. Выбери тот, который считаешь верным, а правильный разбор появится завтра. Сохрани пост, чтобы вспомнить этот нюанс перед собеседованием. Напиши в комментариях свой вариант — интересно сравнить догадки
Почему try-with-resources? Ресурсы (потоки, соединения, блокировки) должны закрываться в любом случае, иначе возможны утечки памяти и файловых дескрипторов. Ручное закрытие требует finally и проверок на null, а исключение при close() может перезаписать оригинальную ошибку. try-with-resources решает эти проблемы: он гарантированно вызывает close() у всех объявленных в скобках объектов (реализующих AutoCloseable), даже если в блоке try произошло исключение. Более того, подавленные исключения от close() не теряются и доступны через getSuppressed(). ⚠️ Нюанс: Ресурсы должны быть final или effectively final в блоке try. Если нужно управлять закрытием вручную, можно использовать try-с-ресурсами с уже существующей переменной, но лучше объявлять их прямо в заголовке.

9просмотров

Блок finally выполняется всегда, но влияет ли он на уже готовый return? Этот пример встречается на собеседованиях и ставит в тупик даже опытных. Выбери один из четырёх вариантов, а завтра я разберу правильный ответ. Сохрани пост, чтобы не забыть эту тонкость. Напиши в комментариях свой вариант — интересно, сколько человек попадётся.
Почему EnumMap быстрее и компактнее? EnumMap специально спроектирован для ключей типа enum. Внутри он использует массив, размер которого равен количеству значений enum. Индекс в этом массиве определяется ординалом (ordinal()) enum-константы, поэтому доступ O(1), и не нужно вычислять хеш и разрешать коллизии. Это экономит память и увеличивает скорость по сравнению с HashMap, который хранит узлы с хешами и указателями. ⚠️ Нюанс: EnumMap не может использовать null в качестве ключа — при попытке добавить null выбрасывается NullPointerException. Это плата за компактность.
Что такое интерфейс? Интерфейс в Java — это ссылочный тип, который определяет «контракт»: набор методов (сигнатур), которые класс обязан реализовать. До Java 8 интерфейсы не могли содержать код, но сейчас могут иметь default-методы с реализацией и статические методы. Интерфейс позволяет достичь абстракции и слабой связанности: код зависит от контракта, а не от конкретной реализации. ✅ Ключевые идеи: • Контракт: интерфейс говорит «что нужно сделать», класс — «как это сделать». • Множественная реализация: класс может реализовать несколько интерфейсов, обходя ограничение на множественное наследование классов. • Функциональные интерфейсы: если в интерфейсе один абстрактный метод, его можно использовать с лямбда-выражениями (Runnable, Comparator, Function). • Default-методы: позволяют добавлять новую функциональность в интерфейсы без поломки существующих реализаций (Java 8+). ⚠️ Нюанс: Интерфейс не может иметь состояние (поля, кроме статических констант) и конструкторы. Для общего состояния и частичной реализации используй абстрактный класс.
Казалось бы, простой пример с ++, но на собеседованиях на нём спотыкаются даже опытные разработчики. В видео — короткий сниппет и четыре варианта ответа. Выбери тот, который считаешь верным, а правильный разбор появится завтра. Сохрани пост, чтобы не забыть этот нюанс. Напиши в комментариях свой вариант — интересно сравнить ответы.
Джун вызывает save(), идёт проверять данные в базе — и удивляется, что их там нет. Он думает, что save() немедленно отправляет SQL в базу, но это не так. Данные теряются для других запросов, пока транзакция не завершится. Почему так происходит? save() не выполняет немедленную вставку. Он только переводит сущность в управляемое состояние (persist) внутри текущего persistence context. Фактический SQL-запрос (INSERT или UPDATE) выполняется либо при вызове flush(), либо автоматически при коммите транзакции. Пока транзакция открыта, другие транзакции (в зависимости от уровня изоляции) могут не видеть изменения. ✅ Как правильно? Сеньор знает, когда нужны данные в БД немедленно. Если действительно необходимо отправить SQL прямо сейчас (например, чтобы получить сгенерированный ID или чтобы другие транзакции увидели изменения), он вызывает flush(). ⚠️ Нюанс: flush() не коммитит транзакцию! Данные станут видны другим транзакциям только после коммита. Исключение — если в БД настроен уровень изоляции READ_UNCOMMITTED.

1просмотр

Одна из первых ловушек, которую ловят Java-джуны на String Pool. Пиши свой ответ в комментах, чуть позже я опубликую пояснение Отметь друга-джависта #java #javadeveloper #javacode
В чём разница? String — неизменяемый (immutable). Каждая операция конкатенации через + или concat() создаёт новый объект в памяти. В цикле это превращается в тысячи ненужных объектов и падение производительности. StringBuilder — изменяемый (mutable). Он работает с одним внутренним буфером, который расширяется при необходимости. Не синхронизирован, поэтому быстрее в однопоточной среде. StringBuffer — полный аналог StringBuilder, но все его методы синхронизированы. Это даёт потокобезопасность ценой скорости. ✅ Правила: • Одиночная конкатенация ("a" + "b") — используй +. • Циклы и однопоточка — только StringBuilder. • Многопоточка — StringBuffer, но лучше рассмотреть StringBuilder с внешней синхронизацией. ⚠️ Нюанс: Компилятор Java оптимизирует простые конкатенации через +, заменяя их на StringBuilder под капотом. Но в цикле эта оптимизация не спасает.

1просмотр

Джун запускает CompletableFuture.runAsync() с блокирующей операцией (например, Thread.sleep()), не указывая исполнитель. Вся JVM начинает тормозить, потому что общий ForkJoinPool перегружен и не может обслуживать другие задачи. Почему так происходит? CompletableFuture без явного Executor использует общий ForkJoinPool.commonPool(). Он рассчитан на неблокирующие CPU-bound задачи. Блокирующая операция (сон, сетевые вызовы, работа с БД) занимает поток из этого пула, и другим задачам (включая внутренние операции JVM) может не хватить ресурсов. ⚠️ Нюанс: Для I/O-интенсивных операций лучше использовать Executors.newCachedThreadPool() или настроить пул динамически. Главное правило: никогда не блокируйте общий ForkJoinPool — это может привести к deadlock'ам. #джава #javaprogramming
Джун хранит даты в Date или LocalDateTime и теряет часовой пояс. Сеньор использует ZonedDateTime — класс, который знает всё о временных зонах и летнем времени. Зачем нужен ZonedDateTime? LocalDateTime хранит только дату и время без привязки к поясу — это как циферблат, у которого не указано, в каком городе он висит. ZonedDateTime добавляет к этому зону (ZoneId), а значит, понимает, что такое UTC+3, переход на летнее время и как корректно сравнивать моменты в разных точках мира. Без него невозможно правильно посчитать время стыковки рейсов или отправить уведомление в нужный час. ✅ Правило от сеньора: • Встречи, рейсы, события с временем и поясом → ZonedDateTime. • Только дата (день рождения) → LocalDate. • Дата и время без привязки к поясу (локальный будильник) → LocalDateTime. • Момент в UTC для логов и меток → Instant. ⚠️ Нюанс: ZonedDateTime.now() без аргументов использует системный часовой пояс сервера. В распределённых системах лучше всегда явно указывать ZoneId, чтобы избежать сюрпризов. #джава #javatutorial
Джун использует Collectors.toMap без merge-функции. Если в стриме встречаются два элемента с одинаковым ключом, приложение падает с IllegalStateException: Duplicate key. На первый взгляд всё работает, пока данные не подкинут сюрприз. Почему так? Collectors.toMap без третьего аргумента внутри использует HashMap::put, который при дубликате ключа выбрасывает исключение. Это защита от случайной потери данных. Но если дубликаты для бизнес-логики допустимы, нужно явно указать, как разрешать конфликт. ⚠️ Нюанс: Collectors.toMap по умолчанию возвращает HashMap (без гарантии порядка). Если нужен порядок вставки — используйте четвёртый аргумент. #джава #javatutorial

1просмотр

Джун использует обычный int с synchronized для простого счётчика, создавая лишние блокировки и замедляя код. Сеньор знает про AtomicInteger — быстрый и потокобезопасный инструмент, работающий на CAS (compare-and-swap). Почему это круто? AtomicInteger обеспечивает атомарность операций без блокировок, используя низкоуровневые инструкции процессора. Потоки не блокируются в ожидании доступа к переменной, а пробуют снова, если другой поток уже изменил значение. Такой подход часто быстрее synchronized, особенно при низкой конкуренции. Но важно помнить: одна операция атомарна, а цепочка операций — нет. Для составных действий нужен compareAndSet в цикле или синхронизация. ⚠️ Нюанс: AtomicInteger гарантирует атомарность только одной операции. Если нужно атомарно выполнить последовательность (проверить + изменить), используй compareAndSet в цикле или синхронизацию. И помни, что updateAndGet — это более краткий способ атомарного обновления. #джава #javatutorial

1просмотр

❌ Джун задаёт значение в ThreadLocal и забывает его очистить. В пуле потоков один и тот же поток может быть переиспользован для следующей задачи, и данные «протекают» — новый запрос видит чужие конфиденциальные данные или состояние. Почему так? ThreadLocal хранит переменные в карте, привязанной к текущему потоку. В серверных приложениях (Tomcat, пул потоков ExecutorService) потоки не умирают после задачи, а возвращаются в пул. Если перед возвращением не вызвать remove(), следующая задача, выполняющаяся на этом же потоке, унаследует все оставленные значения. ⚠️ Нюанс: InheritableThreadLocal в пулах потоков не работает как ожидается — дочерние потоки редко создаются напрямую из задач. #джава #javatutorial

2просмотра

Джун переходит на var и начинает писать его везде. Код становится короче, но тип переменной теперь спрятан — чтобы понять, что именно возвращает метод, нужно прыгать в его реализацию. Читаемость страдает, особенно при код-ревью. Почему это проблема? var был введён в Java 10 для сокращения ceremony-кода, но его создатели сразу предупредили: используйте var только там, где тип очевиден из контекста. Если справа стоит конструктор (new HashMap()), литерал (List.of(...)) или статический фабричный метод с говорящим именем — var оправдан. Если вызывается произвольный метод без ясного возвращаемого типа — лучше объявить тип явно. Это ускоряет понимание кода и уменьшает когнитивную нагрузку. ✅ Как правильно? Писать явно, когда тип неочевиден. ⚠️ Нюанс: var нельзя использовать для полей класса, параметров методов и возвращаемых типов — только для локальных переменных с инициализацией. #java #джава #javadeveloper #javatutorial #javaprogramming

1просмотр

Код выглядит как гарантированный NullPointerException, но выполняется без ошибок. Метод вызывается, программа продолжает работу, а джун недоумевает, почему не упало. Почему так? Статические методы в Java принадлежат классу, а не экземпляру. Компилятор разрешает вызов по типу ссылки (Foo), а не по объекту, на который она указывает. На этапе компиляции вызов obj.staticMethod() заменяется на Foo.staticMethod(), поэтому ссылка obj вообще не используется во время выполнения. Это контрастирует с экземплярными методами, где попытка вызова на null действительно приводит к NPE. ✅ Как правильно? Всегда вызывай статические методы через имя класса: Foo.staticMethod(); Это делает код предсказуемым и сразу даёт понять, что метод статический. ⚠️ Нюанс: Даже если метод объявлен как synchronized, вызов через null-ссылку сработает — монитор захватывается у объекта класса, а не у экземпляра. #java #джава #javadeveloper #javatutorial #javaprogramming
Джун помечает приватный метод аннотацией @Transactional и ожидает, что сохранение данных обернётся транзакцией. Но Spring молча игнорирует аннотацию — транзакция не открывается, откаты не работают, а разработчик даже не подозревает об этом. Почему так? Spring управляет транзакциями через AOP-прокси. Когда ты вызываешь метод бина, на самом деле вызывается прокси, который оборачивает вызов в транзакцию. Прокси — это подкласс твоего класса, и он может перехватить только public методы. Private методы недоступны для перехвата ни у JDK-прокси, ни у CGLIB. Поэтому аннотация на них просто не срабатывает. ✅ Как правильно? Сеньор всегда вешает @Transactional на public методы. И помнит, что даже public метод должен вызываться через прокси (из другого бина), а не через this внутри того же класса. ⚠️ Нюанс: Self-invocation (вызов @Transactional метода из другого метода того же класса) тоже не сработает — прокси не увидит вызов. Решение: внедрить self-ссылку (@Autowired private MyService self) или вынести метод в отдельный сервис. #java #джава #javadeveloper #javatutorial #javaprogramming
Двусторонняя очередь, которая заменяет и Queue, и Stack. За 30 секунд объясняю, как она работает и почему ArrayDeque — лучший выбор. #java #джава #javadeveloper #javatutorial #javaprogramming