Что происходит если не обработать исключение?
Если не было предпринято дополнительных действий, в этой ситуации нет никаких хитростей. Всё приложение, и даже метод main(), выполняется в потоках. Поток, в котором было выброшено и не обработано исключение, остановится, и распечатает стектрейс в вывод System.err. Если это был последний пользовательский поток, приложение начнет завершение работы.
Для изменения логики обработки непойманных исключений в Java существует функциональный интерфейс Thread.UncaughtExceptionHandler.
Обработчик упущенных исключений может быть установлен (в порядке возрастания приоритета):
• глобально на всё приложение, статическим методом Thread.setDefaultUncaughtExceptionHandler();
• для группы потоков, переопределением метода uncaughtException() в реализации объекта подкласса ThreadGroup (т.к. ThreadGroup сам является наследником UncaughtExceptionHandler);
• для отдельного потока, методом setUncaughtExceptionHandler().
Естественно, установка нестандартного обработчика не имеет обратной силы. Используя его, нужно убедиться, что он установлен достаточно рано, до выброса какого-либо исключения.
Хорошей практикой считается обрабатывать исключение настолько близко к месту его выброса, насколько возможно. Следовательно, использование глобальных обработчиков – самый плохой вариант.
Так же как в случае различных финализаций, несмотря на все её недостатки, глобальная обработка иногда лучше, чем ничего. Она может, например, дать последний шанс освободить внешние ресурсы, или уведомить о некорректной работе программы более эффективно, чем через логи.
Когда код с исключением выполняет ExecutorService, мы не имеем прямого доступа к объектам потока. Но в этом случае результатом выполнения будет объект типа Future. Такой отложенный объект при попытке прочитать значение перевыбросит полученное исключение, завернув его в ExecutionException. Новое исключение-обертка уже пойдет по обычному пути обработки текущего потока. Исключение как бы перекочует из внутреннего потока пулла во внешний, который использует этот пулл.
Если же пользовательский код не станет дожидаться результатов, исключение будет потеряно, не оставив даже стектрейса в потоке вывода. Для предотвращения такой ситуации стоит снабдить поток обработчиком сразу после создания, определив для сервиса собственную ThreadFactory.
Обычно, если фреймворк скрывает от пользователя детали работы с потоками, он также скрывает и детали работы с исключениями, оставляя свой специальный способ назначить обработчик. И этот специальный обработчик – более специфичный, а значит более правильный подход, чем стандартная глобальная обработка исключений Java. Так, например, в Spring MVC применяется аннотация @ExceptionHandler.
Подписывайся на наш канал в Max🟪
Если не было предпринято дополнительных действий, в этой ситуации нет никаких хитростей. Всё приложение, и даже метод main(), выполняется в потоках. Поток, в котором было выброшено и не обработано исключение, остановится, и распечатает стектрейс в вывод System.err. Если это был последний пользовательский поток, приложение начнет завершение работы.
Для изменения логики обработки непойманных исключений в Java существует функциональный интерфейс Thread.UncaughtExceptionHandler.
Обработчик упущенных исключений может быть установлен (в порядке возрастания приоритета):
• глобально на всё приложение, статическим методом Thread.setDefaultUncaughtExceptionHandler();
• для группы потоков, переопределением метода uncaughtException() в реализации объекта подкласса ThreadGroup (т.к. ThreadGroup сам является наследником UncaughtExceptionHandler);
• для отдельного потока, методом setUncaughtExceptionHandler().
Естественно, установка нестандартного обработчика не имеет обратной силы. Используя его, нужно убедиться, что он установлен достаточно рано, до выброса какого-либо исключения.
Хорошей практикой считается обрабатывать исключение настолько близко к месту его выброса, насколько возможно. Следовательно, использование глобальных обработчиков – самый плохой вариант.
Так же как в случае различных финализаций, несмотря на все её недостатки, глобальная обработка иногда лучше, чем ничего. Она может, например, дать последний шанс освободить внешние ресурсы, или уведомить о некорректной работе программы более эффективно, чем через логи.
Когда код с исключением выполняет ExecutorService, мы не имеем прямого доступа к объектам потока. Но в этом случае результатом выполнения будет объект типа Future. Такой отложенный объект при попытке прочитать значение перевыбросит полученное исключение, завернув его в ExecutionException. Новое исключение-обертка уже пойдет по обычному пути обработки текущего потока. Исключение как бы перекочует из внутреннего потока пулла во внешний, который использует этот пулл.
Если же пользовательский код не станет дожидаться результатов, исключение будет потеряно, не оставив даже стектрейса в потоке вывода. Для предотвращения такой ситуации стоит снабдить поток обработчиком сразу после создания, определив для сервиса собственную ThreadFactory.
Обычно, если фреймворк скрывает от пользователя детали работы с потоками, он также скрывает и детали работы с исключениями, оставляя свой специальный способ назначить обработчик. И этот специальный обработчик – более специфичный, а значит более правильный подход, чем стандартная глобальная обработка исключений Java. Так, например, в Spring MVC применяется аннотация @ExceptionHandler.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7
Как реализовать паттерн producer/consumer?
Шаблон producer/consumer (производитель/потребитель) – простая и базовая реализация обмена данными между несколькими потоками. Поток-производитель отправляет объекты на условную обработку, потоки-потребители асинхронно принимают и обрабатывают их.
Общий вид решения выглядит так. Продюсер отправляет объекты в специальную коллекцию – буфер. Когда потребитель освобождается, он отправляет запрос на извлечение одного объекта из буфера. Если буфер пуст, потребитель блокируется и ждет, если буфер переполнен – ждет производитель.
На практике реализовать этот паттерн можно множеством способов. Самый правильный способ для применения в бою – использовать готовую реализацию из стандартной библиотеки, объект типа BlockingQueue.
На собеседовании обычно просят реализовать паттерн с нуля. Реализация представлена на изображении. Модификатор synchronized делает так, чтобы в каждый момент времени мог выполняться только один из методов, и только одним потоком. Этого достаточно для корректной работы пока буфер не пуст и не полон. При пустом или полном буфере управление явно перебрасывается на производителя или потребителя соответственно, с помощью методов notify() и wait().
Шаблону producer/consumer посвящена глава 5.3 книги Java Concurrency in Practice.
Сильно упрощая, на основе этого паттерна работают сервисы-брокеры сообщений: Rabbit MQ, Apache ActiveMQ и другие.
Подписывайся на наш канал в Max🟪
Шаблон producer/consumer (производитель/потребитель) – простая и базовая реализация обмена данными между несколькими потоками. Поток-производитель отправляет объекты на условную обработку, потоки-потребители асинхронно принимают и обрабатывают их.
Общий вид решения выглядит так. Продюсер отправляет объекты в специальную коллекцию – буфер. Когда потребитель освобождается, он отправляет запрос на извлечение одного объекта из буфера. Если буфер пуст, потребитель блокируется и ждет, если буфер переполнен – ждет производитель.
На практике реализовать этот паттерн можно множеством способов. Самый правильный способ для применения в бою – использовать готовую реализацию из стандартной библиотеки, объект типа BlockingQueue.
На собеседовании обычно просят реализовать паттерн с нуля. Реализация представлена на изображении. Модификатор synchronized делает так, чтобы в каждый момент времени мог выполняться только один из методов, и только одним потоком. Этого достаточно для корректной работы пока буфер не пуст и не полон. При пустом или полном буфере управление явно перебрасывается на производителя или потребителя соответственно, с помощью методов notify() и wait().
Шаблону producer/consumer посвящена глава 5.3 книги Java Concurrency in Practice.
Сильно упрощая, на основе этого паттерна работают сервисы-брокеры сообщений: Rabbit MQ, Apache ActiveMQ и другие.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥6❤1🤔1
Какая NoSQL база данных чаще всего используется для хранения документов?
Anonymous Quiz
14%
Cassandra
2%
Neo4j
74%
MongoDB
1%
InfluxDB
9%
Redis
👍5🔥3😐2
👩💻 Открытый урок «JDBC — ваш швейцарский нож для работы с данными»
🗓 26 февраля в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java Developer. Professional» от Otus.
Мастерство работы с базами данных: эффективные инструменты и лучшие практики для разработчиков.
О чём поговорим:
✔️ Основы JDBC: что это такое, зачем нужно и как работает
✔️ Практические примеры выполнения сложных запросов
✔️ Работа с транзакциями и обработка ошибок в JDBC
✔️ Оптимизация производительности при работе с данными через JDBC
Кому будет интересно:
Вебинар будет полезен разработчикам, инженерам по базам данных и архитекторам ПО, стремящимся улучшить навыки работы с базами данных и оптимизировать взаимодействие с данными.
🔗 Ссылка на регистрацию: https://vk.cc/cUAqAW
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🗓 26 февраля в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java Developer. Professional» от Otus.
Мастерство работы с базами данных: эффективные инструменты и лучшие практики для разработчиков.
О чём поговорим:
✔️ Основы JDBC: что это такое, зачем нужно и как работает
✔️ Практические примеры выполнения сложных запросов
✔️ Работа с транзакциями и обработка ошибок в JDBC
✔️ Оптимизация производительности при работе с данными через JDBC
Кому будет интересно:
Вебинар будет полезен разработчикам, инженерам по базам данных и архитекторам ПО, стремящимся улучшить навыки работы с базами данных и оптимизировать взаимодействие с данными.
🔗 Ссылка на регистрацию: https://vk.cc/cUAqAW
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
❤3👍2🔥2
Что такое ForkJoinPool?
ForkJoinPool – специальный вид ExecutorService (пулла потоков), который появился в Java с версии 7. Предназначен для выполнения рекурсивных задач.
Задача для сервиса представляется экземпляром класса ForkJoinTask. В основном используются подклассы RecursiveTask и RecursiveAction, для задач с результатом и без соответственно. Аналогично интерфейсам Callable и Runnable обычного ExecutorService.
Тело рекурсивной операции задается в реализации метода compute() задачи ForkJoinTask. Здесь же создаются новые подзадачи, и запускаются параллельно методом fork(). Чтобы дождаться завершения выполнения задачи, на каждой форкнутой подзадаче вызывается блокирующий метод join(), результат выполнения при необходимости агрегируется.
С точки зрения использования метод ForkJoinTask.join() похож на аналогичный метод класса Thread. Но в случае fork-join поток может на самом деле не заснуть, а переключиться на выполнение другой задачи. Такая стратегия называется work stealing, и позволяет эффективнее использовать ограниченное количество потоков. Это похоже на переиспользование потоков корутинах Kotlin (green threads).
Примеры практического использования ForkJoinPool.
Подписывайся на наш канал в Max🟪
ForkJoinPool – специальный вид ExecutorService (пулла потоков), который появился в Java с версии 7. Предназначен для выполнения рекурсивных задач.
Задача для сервиса представляется экземпляром класса ForkJoinTask. В основном используются подклассы RecursiveTask и RecursiveAction, для задач с результатом и без соответственно. Аналогично интерфейсам Callable и Runnable обычного ExecutorService.
Тело рекурсивной операции задается в реализации метода compute() задачи ForkJoinTask. Здесь же создаются новые подзадачи, и запускаются параллельно методом fork(). Чтобы дождаться завершения выполнения задачи, на каждой форкнутой подзадаче вызывается блокирующий метод join(), результат выполнения при необходимости агрегируется.
С точки зрения использования метод ForkJoinTask.join() похож на аналогичный метод класса Thread. Но в случае fork-join поток может на самом деле не заснуть, а переключиться на выполнение другой задачи. Такая стратегия называется work stealing, и позволяет эффективнее использовать ограниченное количество потоков. Это похоже на переиспользование потоков корутинах Kotlin (green threads).
Примеры практического использования ForkJoinPool.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥2
👩💻 Открытый урок «Изучаем Java с нуля на примере простой консольной игры»
🗓 2 марта в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java-разработчик» от Otus.
На вебинаре вы узнаете как установить и настроить инструменты, необходимые для программирования на языке Java. Научитесь создавать небольшие консольные проекты. Изучите несколько базовых блоков из которых строится любое приложение. Познакомитесь с процессом разработки простой консольной игры.
О чем поговорим:
✔️ Как установить и настроить инструменты, необходимые для программирования на языке Java
✔️ Как создавать небольшие консольные проекты
✔️ Несколько базовых блоков, из которых строится любое приложение
✔️ Процесс разработки простой консольной игры
Кому будет интересно:
Урок будет полезен тем, кто хочет:
— Начать изучать программирование на языке Java с нуля.
— Посмотреть, как на практике создаются Java-приложения.
🔗 Ссылка на регистрацию: https://vk.cc/cUL8lZ
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🗓 2 марта в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java-разработчик» от Otus.
На вебинаре вы узнаете как установить и настроить инструменты, необходимые для программирования на языке Java. Научитесь создавать небольшие консольные проекты. Изучите несколько базовых блоков из которых строится любое приложение. Познакомитесь с процессом разработки простой консольной игры.
О чем поговорим:
✔️ Как установить и настроить инструменты, необходимые для программирования на языке Java
✔️ Как создавать небольшие консольные проекты
✔️ Несколько базовых блоков, из которых строится любое приложение
✔️ Процесс разработки простой консольной игры
Кому будет интересно:
Урок будет полезен тем, кто хочет:
— Начать изучать программирование на языке Java с нуля.
— Посмотреть, как на практике создаются Java-приложения.
🔗 Ссылка на регистрацию: https://vk.cc/cUL8lZ
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
👍2🔥2❤1
Чем ForkJoinPool отличается от ExecutorService?
ForkJoinPool сам по себе является наследником ExecutorService. Вопрос подразумевает его отличия от обычного пула потоков – ThreadPoolExecutor.
Преимущества, которые дает work stealing по сравнению с обычным пулом:
• Сокращение расходов на переключение контекста;
• Защита от проблемы голодания потоков (thread starvation);
• Защита от дедлока для рекурсивных задач.
Как положено любому представителю ExecutorService, ForkJoinPool тоже умеет выполнять Runnable и Callable, но помимо этого работает и со специальными задачами ForkJoinTask, о которых также говорилось ранее.
Интерфейс настройки и мониторинга остается тем же, что и в классических тред-пулах.
Каждый обычный пул использует собственный набор потоков. ForkJoinPool по умолчанию использует общий пул-синглтон commonPool. Альтернативный отдельный пул всё еще можно задать в конструкторе.
ForkJoinPool сам регулирует количество запущенных потоков, достигая максимальной эффективности при заданном уровне параллелизма.
Подписывайся на наш канал в Max🟪
ForkJoinPool сам по себе является наследником ExecutorService. Вопрос подразумевает его отличия от обычного пула потоков – ThreadPoolExecutor.
Преимущества, которые дает work stealing по сравнению с обычным пулом:
• Сокращение расходов на переключение контекста;
• Защита от проблемы голодания потоков (thread starvation);
• Защита от дедлока для рекурсивных задач.
Как положено любому представителю ExecutorService, ForkJoinPool тоже умеет выполнять Runnable и Callable, но помимо этого работает и со специальными задачами ForkJoinTask, о которых также говорилось ранее.
Интерфейс настройки и мониторинга остается тем же, что и в классических тред-пулах.
Каждый обычный пул использует собственный набор потоков. ForkJoinPool по умолчанию использует общий пул-синглтон commonPool. Альтернативный отдельный пул всё еще можно задать в конструкторе.
ForkJoinPool сам регулирует количество запущенных потоков, достигая максимальной эффективности при заданном уровне параллелизма.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3❤2👍2⚡1🤨1
Как работают параллельные стримы?
Основная цель, ради которой в Java 8 был добавлен Stream API – удобство многопоточной обработки.
Обычный стрим будет выполняться параллельно после вызова промежуточной операции parallel(). Некоторые стримы создаются уже многопоточными, например результат вызова Collection#parallelStream(). Для распараллеливания используется единый общий ForkJoinPool.
Внутри реализации потока его сплиттератор оборачивается в AbstractTask, который и отправляется на выполнение в пул. AbstractTask при выполнении считывает estimateSize сплиттератора и текущую степень параллелизма пула. На основе этих данных он принимает решение, распараллелить ли сплиттератор на два методом trySplit().
У удобства такого решения есть обратная сторона. Так как пул единый, нагрузка распределяется на всех пользователей параллельных стримов в программе. Если в одном потоке выполняются долгие блокирующие операции, это может ударить по производительности в совершенно не связанном с ним другом потоке.
Если всё же требуется использовать отдельный пул потоков, сам стрим выполняется как задача этого отдельного пула.
Подписывайся на наш канал в Max🟪
Основная цель, ради которой в Java 8 был добавлен Stream API – удобство многопоточной обработки.
Обычный стрим будет выполняться параллельно после вызова промежуточной операции parallel(). Некоторые стримы создаются уже многопоточными, например результат вызова Collection#parallelStream(). Для распараллеливания используется единый общий ForkJoinPool.
Внутри реализации потока его сплиттератор оборачивается в AbstractTask, который и отправляется на выполнение в пул. AbstractTask при выполнении считывает estimateSize сплиттератора и текущую степень параллелизма пула. На основе этих данных он принимает решение, распараллелить ли сплиттератор на два методом trySplit().
У удобства такого решения есть обратная сторона. Так как пул единый, нагрузка распределяется на всех пользователей параллельных стримов в программе. Если в одном потоке выполняются долгие блокирующие операции, это может ударить по производительности в совершенно не связанном с ним другом потоке.
Если всё же требуется использовать отдельный пул потоков, сам стрим выполняется как задача этого отдельного пула.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍2❤1
Как инстанцировать экземпляр generic типа?
Внутри класса class Foo<T> на generic параметре T невозможно выполнить никакой оператор: нельзя взять его .class, нельзя применить его в instanceof. Также и вызов на нем оператора new приведет к ошибке.
Причина этих ограничений кроется в стирании типов. Дженерик параметры правильно воспринимать скорее как ограничения типов, чем как конкретные типы. Эти ограничения действуют для более строгих проверок на этапе компиляции. В рантайме же информация о конкретных переданных типах-параметрах стирается. А все эти операторы выполняются именно в рантайме.
Стандартный простой способ действия здесь – кроме значения типа T передавать еще и объект-дескриптор для этого типа, экземпляр класса Class<T>. Объект может быть создан из дескриптора рефлекшеном.
Но существует один хак, способный справиться со стиранием типов. Тип-параметр все-таки остается в одном месте в рантайме. Метод метакласса наследника определившего конкретный тип getGenericSuperclass() возвращает класс, которым параметризован родитель.
Подписывайся на наш канал в Max🟪
Внутри класса class Foo<T> на generic параметре T невозможно выполнить никакой оператор: нельзя взять его .class, нельзя применить его в instanceof. Также и вызов на нем оператора new приведет к ошибке.
Причина этих ограничений кроется в стирании типов. Дженерик параметры правильно воспринимать скорее как ограничения типов, чем как конкретные типы. Эти ограничения действуют для более строгих проверок на этапе компиляции. В рантайме же информация о конкретных переданных типах-параметрах стирается. А все эти операторы выполняются именно в рантайме.
Стандартный простой способ действия здесь – кроме значения типа T передавать еще и объект-дескриптор для этого типа, экземпляр класса Class<T>. Объект может быть создан из дескриптора рефлекшеном.
Но существует один хак, способный справиться со стиранием типов. Тип-параметр все-таки остается в одном месте в рантайме. Метод метакласса наследника определившего конкретный тип getGenericSuperclass() возвращает класс, которым параметризован родитель.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍4❤2
Как написать иммутабельный класс?
Immutable (неизменяемый) класс – это класс, состояние экземпляров которого невозможно изменить после создания.
С иммутабельным классом всегда легче работать. Его состояние не поменяется, значит обращаться к нему в многопоточной среде можно без дополнительной синхронизации. Функции, зависящие только от состояния экземпляра будут возвращать один и тот же результат от вызова к вызову – это облегчает например реализацию hashCode(). Также вместо нескольких одинаковых экземпляров можно использовать один закэшированный объект, экономя память (паттерн Приспособленец).
Шаги, которые необходимо предпринять, чтобы класс стал immutable:
1. Запретите расширение класса – либо объявите его final, либо закройте доступ наследникам ко всем способам мутации, перечисленным в следующих пунктах;
2. Сделайте все поля финальными;
3. Не выставляйте наружу методов-мутаторов, которые меняют состояние;
4. Не отдавайте наружу поля ссылочного изменяемого типа (объекты классов, массивы) – если объект под ссылкой не иммутабельный, должна возвращаться его глубокая копия (defensive copy);
5. Создавайте объект правильно (подробнее в следующем посте).
Если вам нужны преимущества иммутабельного объекта, но также нужно иногда изменять его, подойдет подход copy on write: каждый метод-мутатор должен мутировать и возвращать не сам объект, а только что созданную его копию. Оригинал всё так же остается неизменным.
Подписывайся на наш канал в Max🟪
Immutable (неизменяемый) класс – это класс, состояние экземпляров которого невозможно изменить после создания.
С иммутабельным классом всегда легче работать. Его состояние не поменяется, значит обращаться к нему в многопоточной среде можно без дополнительной синхронизации. Функции, зависящие только от состояния экземпляра будут возвращать один и тот же результат от вызова к вызову – это облегчает например реализацию hashCode(). Также вместо нескольких одинаковых экземпляров можно использовать один закэшированный объект, экономя память (паттерн Приспособленец).
Шаги, которые необходимо предпринять, чтобы класс стал immutable:
1. Запретите расширение класса – либо объявите его final, либо закройте доступ наследникам ко всем способам мутации, перечисленным в следующих пунктах;
2. Сделайте все поля финальными;
3. Не выставляйте наружу методов-мутаторов, которые меняют состояние;
4. Не отдавайте наружу поля ссылочного изменяемого типа (объекты классов, массивы) – если объект под ссылкой не иммутабельный, должна возвращаться его глубокая копия (defensive copy);
5. Создавайте объект правильно (подробнее в следующем посте).
Если вам нужны преимущества иммутабельного объекта, но также нужно иногда изменять его, подойдет подход copy on write: каждый метод-мутатор должен мутировать и возвращать не сам объект, а только что созданную его копию. Оригинал всё так же остается неизменным.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥4
Как реализовать метод equals?
Сначала нужно решить, действительно ли вам нужно переопределять equals(). Реализация по умолчанию делает объект равным только самому себе (сравнение на идентичность). Это имеет смысл, если у вашего класса не бывает отдельных, но логически одинаковых экземпляров.
Если два экземпляра всё-таки могут быть равны, equals() нужно переопределять. Реализация должна соблюдать контракт: это отношение эквивалентности (рефлексивность, транзитивность, симметричность), ни один объект не равен null.
Рефлексивность. первым делом проверим, не идентичен ли переданный объект текущему. Если да – сразу вернем true.
Неравенство null. Если аргумент null – сразу вернем false.
Симметричность. Если мы допускаем наследование и расширение метода equals(), в наследнике может появиться дополнительная логика, которая сделает !other.equals(this) при this.equals(other). Проще всего избежать этого, добавив сравнение типов. Если типы не равны – сразу вернем false. Почему не надо использовать instanceof.
Транзитивность. Оператор == обладает свойствами транзитивности и симметричности. Далее мы сравниваем на равенство все примитивные свойства. Для ссылочных типов этими характеристиками по контракту обладает equals – для сравнения ссылочных типов пользуемся им.
Речь здесь идет о логических свойствах. Фактически одно логическое свойство может быть представлено несколькими полями класса, или же может вычисляться на лету. Некоторые поля служат для внутренних технических нужд, и не имеют отношения к логическому состоянию. Такие поля обычно исключают из сравнения.
Подписывайся на наш канал в Max🟪
Сначала нужно решить, действительно ли вам нужно переопределять equals(). Реализация по умолчанию делает объект равным только самому себе (сравнение на идентичность). Это имеет смысл, если у вашего класса не бывает отдельных, но логически одинаковых экземпляров.
Если два экземпляра всё-таки могут быть равны, equals() нужно переопределять. Реализация должна соблюдать контракт: это отношение эквивалентности (рефлексивность, транзитивность, симметричность), ни один объект не равен null.
Рефлексивность. первым делом проверим, не идентичен ли переданный объект текущему. Если да – сразу вернем true.
Неравенство null. Если аргумент null – сразу вернем false.
Симметричность. Если мы допускаем наследование и расширение метода equals(), в наследнике может появиться дополнительная логика, которая сделает !other.equals(this) при this.equals(other). Проще всего избежать этого, добавив сравнение типов. Если типы не равны – сразу вернем false. Почему не надо использовать instanceof.
Транзитивность. Оператор == обладает свойствами транзитивности и симметричности. Далее мы сравниваем на равенство все примитивные свойства. Для ссылочных типов этими характеристиками по контракту обладает equals – для сравнения ссылочных типов пользуемся им.
Речь здесь идет о логических свойствах. Фактически одно логическое свойство может быть представлено несколькими полями класса, или же может вычисляться на лету. Некоторые поля служат для внутренних технических нужд, и не имеют отношения к логическому состоянию. Такие поля обычно исключают из сравнения.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥4❤2
👩💻 Открытый урок «Реализация простого HTTP сервера на Java»
🗓 10 марта в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java-разработчик» от Otus.
Одно из основных направлений в Java разработке – разработка веб-приложений. Любому веб-разработчику важно понимать, что из себя представляет протокол HTTP и как внутри работает HTTP-сервер.
О чем поговорим:
✔️ Что из себя представляет протокол HTTP и как внутри работает HTTP-сервер
✔️ Как с помощью штатных средств языка Java (без использования сторонних библиотек) разработать простой HTTP сервер
✔️ Вопросы возможности экономии ресурсов системы на обработку каждого запроса
Кому будет интересно:
Вебинар будет полезен тем, у кого есть уверенные знания в базе языка Java и ООП; желательно понимание базовых принципов работы с потоками ввода/вывода (java.io) и многопоточностью.
🔗 Ссылка на регистрацию: https://vk.cc/cV0AlJ
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🗓 10 марта в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java-разработчик» от Otus.
Одно из основных направлений в Java разработке – разработка веб-приложений. Любому веб-разработчику важно понимать, что из себя представляет протокол HTTP и как внутри работает HTTP-сервер.
О чем поговорим:
✔️ Что из себя представляет протокол HTTP и как внутри работает HTTP-сервер
✔️ Как с помощью штатных средств языка Java (без использования сторонних библиотек) разработать простой HTTP сервер
✔️ Вопросы возможности экономии ресурсов системы на обработку каждого запроса
Кому будет интересно:
Вебинар будет полезен тем, у кого есть уверенные знания в базе языка Java и ООП; желательно понимание базовых принципов работы с потоками ввода/вывода (java.io) и многопоточностью.
🔗 Ссылка на регистрацию: https://vk.cc/cV0AlJ
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
❤3👍2🔥2
Как реализовать метод hashCode?
Если вы переопределили equals(), то обязательно также переопределить и hashCode(). Это не просто теоретическое требование. Если класс нарушает это правило, хранение его экземпляров в качестве например ключей HashMap приводит к непредсказуемому поведению.
Результат hashCode() должен быть одинаковый для равных в смысле equals объектов. Обычно для этого значение хэш-кода вычисляется на основе значений полей, которые участвуют в equals(). Но и возвращение одной и той же константы 42 для любого экземпляра класса тоже будет валидной реализацией.
Результат hashCode() должен быть равномерно распределен. Это правило не такое строгое как остальные. Его нарушение не сломает программу, хотя может сильно ухудшить производительность. Поэтому константа 42 – допустимая, но не лучшая идея. Вместо этого все значения полей сначала приводятся к int: boolean превращается в любую пару констант, null в 0, для ссылочных типов берется их hashCode(). Затем все эти значения смешиваются с помощью бинарного оператора XOR (^). Дополнительно для лучшего распределения можно применять битовые сдвиги. Если вы владеете информацией о распределении значений полей в конкретно вашем случае, эту реализацию можно улучшить.
Результат hashCode() должен быть одинаковый на протяжении времени жизни объекта. Если вычисление хэш-кода зависит от переменных значений, сохраните его значение во внутреннее поле при первом вызове. При следующих вызовах сразу возвращайте это закэшированное значение.
Подписывайся на наш канал в Max🟪
Если вы переопределили equals(), то обязательно также переопределить и hashCode(). Это не просто теоретическое требование. Если класс нарушает это правило, хранение его экземпляров в качестве например ключей HashMap приводит к непредсказуемому поведению.
Результат hashCode() должен быть одинаковый для равных в смысле equals объектов. Обычно для этого значение хэш-кода вычисляется на основе значений полей, которые участвуют в equals(). Но и возвращение одной и той же константы 42 для любого экземпляра класса тоже будет валидной реализацией.
Результат hashCode() должен быть равномерно распределен. Это правило не такое строгое как остальные. Его нарушение не сломает программу, хотя может сильно ухудшить производительность. Поэтому константа 42 – допустимая, но не лучшая идея. Вместо этого все значения полей сначала приводятся к int: boolean превращается в любую пару констант, null в 0, для ссылочных типов берется их hashCode(). Затем все эти значения смешиваются с помощью бинарного оператора XOR (^). Дополнительно для лучшего распределения можно применять битовые сдвиги. Если вы владеете информацией о распределении значений полей в конкретно вашем случае, эту реализацию можно улучшить.
Результат hashCode() должен быть одинаковый на протяжении времени жизни объекта. Если вычисление хэш-кода зависит от переменных значений, сохраните его значение во внутреннее поле при первом вызове. При следующих вызовах сразу возвращайте это закэшированное значение.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
Каждый, проходя интервью, думал: «Ну что они хотят услышать? Я же правильно ответил! Почему меня не взяли?»
4 марта(уже завтра!) в 19:00 по мск приходи онлайн на открытое интервью, где будут собеседовать МЕНТОРА ШОРТКАТ
Как это будет:
📂 Виктор Анохин, старший разработчик из WildBerries, будет задавать реальные вопросы и задачи старшему разработчику Сергею Чамкину
📂 Сергей будет отвечать на каждый вопрос так, как это ожидает сам от вас на собеседованиях
📂 В конце можно будет задать любой вопрос Сергею и Виктору
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍2🔥2
Чем отличается Closeable от AutoCloseable?
Интерфейс AutoCloseable представляет объект-хранилище некоего ресурса, пока тот не закрыт. В единственном его методе close() объявляется логика закрытия этого ресурса. Пример – дескриптор открытого файла (ObjectOutputStream).
Особенность этого интерфейса в том, что его применение позволяет использовать объект в языковой конструкции try-with-resource. Всё это появилось в Java версии 7.
До Java 7 уже существовал похожий интерфейс – Closeable. Смысл его точно такой же. Он всё еще доступен в стандартной библиотеке для обратной совместимости, но в новом коде рекомендуется использовать AutoCloseable. Чтобы экземпляры старого Closeable тоже можно было использовать в try-with-resource, новый интерфейс был добавлен его родителем.
Проблема старого интерфейса Closeable была в узости типа исключений, которые может выбрасывать close(). Ковариантность позволила расширить тип в базовом интерфейсе AutoCloseable с IOException до Exception.
Еще одно отличие – контракт метода close(). Старый Closeable требует его идемпотентности, тогда как новый AutoCloseable разрешает методу иметь побочные эффекты.
Подписывайся на наш канал в Max🟪
Интерфейс AutoCloseable представляет объект-хранилище некоего ресурса, пока тот не закрыт. В единственном его методе close() объявляется логика закрытия этого ресурса. Пример – дескриптор открытого файла (ObjectOutputStream).
Особенность этого интерфейса в том, что его применение позволяет использовать объект в языковой конструкции try-with-resource. Всё это появилось в Java версии 7.
До Java 7 уже существовал похожий интерфейс – Closeable. Смысл его точно такой же. Он всё еще доступен в стандартной библиотеке для обратной совместимости, но в новом коде рекомендуется использовать AutoCloseable. Чтобы экземпляры старого Closeable тоже можно было использовать в try-with-resource, новый интерфейс был добавлен его родителем.
Проблема старого интерфейса Closeable была в узости типа исключений, которые может выбрасывать close(). Ковариантность позволила расширить тип в базовом интерфейсе AutoCloseable с IOException до Exception.
Еще одно отличие – контракт метода close(). Старый Closeable требует его идемпотентности, тогда как новый AutoCloseable разрешает методу иметь побочные эффекты.
Подписывайся на наш канал в Max
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥3