Секреты Java
رفتن به کانال در Telegram
Обсуждаем Java, архитектуру, фреймворки и всё, о чем должны говорить разработчики. Владелец: @Alexey_Morl Реклама на бирже: https://telega.in/c/java_secrets
نمایش بیشتر7 991
مشترکین
اطلاعاتی وجود ندارد24 ساعت
اطلاعاتی وجود ندارد7 روز
اطلاعاتی وجود ندارد30 روز
آرشیو پست ها
7 991
✅Примитивы vs обертки над примитивами
Когда нам лучше использовать примитивы типа
int, boolean и др., а когда обертки над ними: Integer, Boolean и др.?
▫️Если вы хотите использовать коллекции и дженерики вам стоит выбрать Wrapper классы, т.к. boxing/unboxing приведет к дополнительным накладным расходам и увеличению нагрузки на Garbage Collector
▫️Если у вас очень большое количество различных математических вычислений, используйте примитивы, вычисления с ними всегда быстрее и менее ресурсозатратны
▫️Используйте Wrapper классы, когда вы хотите, чтобы переменная (поле) могла иметь значение null. Или по другому - когда поле не обязательно для инициализации. Может быть удобно инициализировать переменные нулевым значением, а это невозможно сделать с примитивами.
Например у нас есть какой-то Request:
class Request {
Boolean success;
}
Тут он может по сути иметь три статуса:
success = true - успешно выполнен
success = false - не успешный
success = null - еще не определен (запрос еще не выполнен)
В этом случае, если бы мы использовали примитив, то при создании объекта Request и до его условного выполнения, он бы имел статус success = false, что в рамках этого сценария было бы неверно и вводило бы в заблуждение.
Делитесь в комментариях, какие еще случаи встречали на практике?
🫡 Секреты Java
#autoboxing #primitives7 991
✅Разберемся в понятиях объявления, сигнатуры и перегрузки методов
Объявление метода состоит из:
<модификаторы> <возвращаемый тип> <имя метода> <список параметров> <выбрасываемое исключение>{
<тело метода>
}
Сигнатура метода - это имя метода и типы параметров в указанном порядке. На примере на изображении это -
getCongratulation(String, int). Все остальные части объявления метода, включая имена параметров, не являются частью сигнатуры метода.
Причем даже если методы отличаются только типом параметров, и один из типов является наследником другого, это все равно разные сигнатуры.
Сигнатура нужна компилятору, чтобы понять какой метод вызывается на данном участке кода, поэтому в пределах класса она должны быть уникальной.
Перегрузка метода (Method Overloading) - это объявление методов с одинаковыми названиями в одном классе. НО они должны иметь разные сигнатуры.
Поэтому попытки перегрузить метод поменяв, например, возвращаемое значение
public static []char getCongratulation(String name, int age) throws Exception
{
return ...;
}
или выбрасываемое исключение
public static String getCongratulation(String name, int age) throws IOException
{
return ...;
}
приведут к ошибке компиляции 'method is already defined in class'.
Перегрузка относится к полиморфизму времени компиляции (Compile time polymorphism) или по другому - статическому полиморфизму.
🫡 Секреты Java
#method #overloading7 991
✅ double vs. BigDecimal
В Java есть два типа данных, которые могут быть использованы для работы с числами с плавающей точкой:
double и BigDecimal, однако они имеют свои особенности и различия.
Тип double представляет собой 64-битное число с плавающей точкой, работающее по стандарту IEEE 754. Этот стандарт определяет способ преобразования действительных чисел в двоичный формат, а т.к в двоичном виде иногда невозможно точно представить десятичные дроби, то как следствие double имеют ограниченную точность и проблемы с округлением.
Рассмотрим это на примере:
double first = 0.1;
double second = 0.2;
double result = first + second;
System.out.println(result); // Выводит 0.30000000000000004
Мы ожидали результат 0.3, но фактически он отличается.
Класс BigDecimal представляет неизменяемое (immutable) десятичное число с произвольной точностью. Говоря простым языком - BigDecimal состоит из двух частей: целого числа и значения scale, определяющего количество цифр после запятой.
Класс BigDecimal обеспечивает более высокую точность, чем double, поскольку он выполняет вычисления с десятичными дробями используя целочисленную арифметику, которая более точна, чем арифметика чисел с плавающей точкой.
Те же вычисления, но с BigDecimal:
var first = new BigDecimal("0.1");
var second = new BigDecimal("0.2");
var result = first.add(second);
System.out.println(result); // Выводит 0.3
Результат точный, но важно отметить, что работа с BigDecimal может быть более медленной по сравнению с double, так как BigDecimal использует более сложный механизм для хранения и выполнения операций с числами.
В заключение, если вам необходимо обрабатывать числа с высокой точностью без проблем с округлением, например финансовые вычисления, то используйте BigDecimal. Если скорость и производительность является приоритетом, а абсолютная точность не столь важна, то используйте double.
🫡 Секреты Java
#double #bigdecimal7 991
Написать код, понятный компьютеру, может каждый, но только хорошие программисты пишут код, понятный людям© Мартин Фаулер 🫡 Секреты Java #quotes
7 991
✅Как управлять выделенной памятью для нашего приложения?
Может случится, что в процессе развития наше приложение выросло на столько, что начинает долго соображать или вовсе падать с
OutOfMemoryError. Если дело не в memory leak, то пора выделить приложению больше памяти.
Параметры -Xms и -Xmx в Java используются для управления выделением и использованием памяти при выполнении приложений.
Пример:
java -Xms512m -Xmx2g MyApp
▫️Тут мы запускаем приложение MyApp стандартной командой (при запуске из Idea идем в "Run" -> "Edit Configurations..." и у выбранного приложения прописываем параметры в "VM options")
▫️Параметр -Xms задает начальный размер выделенной памяти для JVM. В примере выше JVM начинает работу, выделяя 512 мегабайт. Установка оптимального значения -Xms может помочь ускорить запуск JVM и улучшить производительность приложения.
▫️Параметр -Xmx указывает максимальный размер памяти, который JVM может использовать. На примере - 2ГБ. Установка правильного значения -Xmx поможет нам предотвратить переполнение памяти.
▫️Виды значений, которые можно установить для параметров -Xms и -Xmx, указываются в килобайтах (k), мегабайтах (m) или гигабайтах (g).
▫️Без указания параметров, дефолтные значения рассчитываются JVM в зависимости от операционной системы и характеристик компьютера.
Обычно имеет смысл устанавливать значение -Xms равным -Xmx, это позволит приложению запускаться быстрее, не тратить время на выделение дополнительной памяти и не конкурировать с другими приложениями за свободную память компьютера.
🫡 Секреты Java
#java_memory7 991
✅Deadlock или взаимная блокировка
Это одна из проблем многопоточности и может возникнуть, когда нескольким потокам одновременно требуются одни и те же ресурсы, но они получают доступ к ним в разном порядке. Такая ситуация приведет к зависанию потоков или всей программы.
Deadlock может возникнуть, когда потоки взаимодействуют с ресурсами в следующем порядке:
1. Поток А блокирует ресурс А и ожидает ресурс Б.
2. Поток В блокирует ресурс Б и ожидает ресурс А.
Для наглядности рассмотрим пример кода, иллюстрирующего возникновение deadlock:
public class DeadLockExample {
public static void main(String[] args) {
var resourceA = new Object();
var resourceB = new Object();
new Thread(() -> someMethod(resourceA, resourceB)).start();
new Thread(() -> someMethod(resourceB, resourceA)).start();
}
@SneakyThrows
public static void someMethod(Object first, Object second) {
synchronized (first) {
System.out.println("First synchronized block");
Thread.sleep(100);
synchronized (second) {
System.out.println("Second synchronized block");
}
}
}
}
В данном примере выполняются два потока. Каждый поток передает в someMethod() одни и те же объекты, но в разном порядке и метод пытается заблокировать сначала один ресурс, а затем другой. Однако первый поток блокирует resourceA и ожидает resourceB, в то время как второй поток блокирует resourceB и ожидает resourceA.
Если запустить данный код мы увидим, что потоки достигнут первого блока синхронизации, выведут:
First synchronized block
First synchronized block
и на попытке получить блокировку второго объекта программа зависнет, т.к объекты уже заблокированы.
Для избежания deadlock'ов существует ряд методов:
▫️Избегать получения нескольких блокировок для потока
▫️Если потоку необходимо несколько блокировок, то убедится, что каждый поток получает блокировки в одном и том же порядке
▫️Использовать классы Lock для блокировок
🫡 Секреты Java
#multithreading7 991
✅Паттерн "Стратегия"
Этот паттерн является поведенческим паттерном проектирования, который позволяет заменять какое-либо поведение класса во время выполнения и следует принципу "композиция лучше наследования".
Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми- такое определение дает "Банда четырех" в своей книге. Разберем на простом примере: Допустим, у нас есть класс
SortContext, который должен уметь сортировать набор данных. Сначала мы "определяем семейство алгоритмов и инкапсулируем их" (в нашем случае алгоритмы сортировки) в интерфейс SortStrategy:
public interface SortStrategy {
void sort(int[] dataset);
}
И реализации алгоритмов:
public class BubbleSort implements SortStrategy {
public void sort(int[] dataset) {
// Реализация алгоритма сортировки пузырьком
}
}
public class QuickSort implements SortStrategy {
public void sort(int[] dataset) {
// Реализация алгоритма быстрой сортировки
}
}
Так будет выглядеть наш класс SortContext с "инкапсулированным и заменяемым" алгоритмом сортировки:
public class SortContext {
private SortStrategy sortStrategy;
public void setSortStrategy(SortStrategy sortStrategy)
{
this.sortStrategy = sortStrategy;
}
public void sortData(int[] dataset)
{
sortStrategy.sort(dataset);
}
}
Теперь мы можем использовать класс SortContext для выполнения сортировки данных:
public class Main {
public static void main(String[] args) {
int[] dataset = {2, 4, 1, 5, 3};
Context context = new Context();
context.setSortStrategy(new BubbleSort()); // используем сортировку пузырьком
context.sortData(dataset);
context.setSortStrategy(new QuickSort()); // используем быструю сортировку
context.sortData(dataset);
}
}
Паттерн позволяет нам менять алгоритм во время выполнения программы, просто изменяя объект стратегии, который передается классу контекста.
🫡 Секреты Java
#pattern7 991
✅Magic Numbers и Magic Strings
Магические числа и строки - это числа и строки в коде, смысл которых невозможно понять без глубокого понимания происходящего на этом участке кода.
Несмотря на "волшебные" названия, эти числа и строки несут большое зло и, при своей кажущейся безобидности, могут быть источником очень больших проблем как для разработчика, так и для бизнеса. Программист, не являющийся автором кода, который столкнется с ними, скорее всего будет вынужден потратить много времени, чтобы разобраться в их значении. Да и сам автор через какое-то время может забыть, какой смысл несут эти числа и строки.
Рассмотрим пример:
public double calculateNetProfit(double costPrice, double sellingPrice) {
return (sellingPrice - costPrice) * (1 - 0.2);
}
Программа вычисляет чистую прибыль с продажи товара. В какой-то момент условия вычисления меняются и вам дают задачу поправить алгоритм. Сможете ли вы с первого взгляда на код понять значения чисел в нем и куда вносить изменения? Скорее всего без вопросов к коллегам и досконального изучения функционала - нет.
Решение в таких ситуация достаточно простое - всегда создавайте константы (или локальные переменные) с названиями, которые четко и однозначно передают смысл значения:
private final double VALUE_ADDED_TAX = 0.2;
private final double NET_PROFIT_MULTIPLIER = 1 - VALUE_ADDED_TAX;
public double calculateNetProfit(double costPrice, double sellingPrice) {
return (sellingPrice - costPrice) * NET_PROFIT_MULTIPLIER;
}
Тут нам сразу понятно, что метод учитывает НДС и вычисляет чистую прибыль с учетом налога. Более того, мы можем использовать эту константу и в других вычислениях. И если ставка налога изменится нам не придется искать по всему коду, какие вычисления на ней завязаны.
🫡 Секреты Java
#bad_practice #anti_pattern7 991
✅Предпочтительный способ сравнения объектов
Сравнивая объекты через оператор равенства "==" мы сравниваем только ссылки на эти объекты, и если ссылки указывают на разные объекты, даже если эти объекты идентичные по "наполнению" мы получим false. С примитивами проще - оператор их сравнивает по значению всегда.
int a = 1;
int b = 1;
System.out.println(a == b);
вернет true, тогда как
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a == b);
выведет false.
Более широкую концепцию равенства предлагает метод equals() класса Object.
a.equals(b);
тут уже результат будет true. Но в это случае есть опасность получить NullPointerException, если в переменную 'a' попадет null. Делать проверку на null перед сравнением будет не самым элегантным решением:
a == null ? b == null : && a.equals(b);
Эту проблему решает статический метод equals() утилитарного класса Objects
Objects.equals(a, b);
Если один из аргументов равен null, результатом сравнения будет false. Если оба равны null - результат true.
Метод Objects.equals() обрабатывает null значения, использует equals() объектов для сравнения и запись получается более удобочитаемой.
🫡 Секреты Java
#equals7 991
✅В этом посте мы рассмотрим как возможно сделать конвертацию из массива фиксированного размера (Array) в список (List) используя стандартные инструменты Java.
Рассмотрим пример:
Integer[] array = { 1, 2, 3, 4 };
List<Integer> list = Arrays.asList(array)
Важно отметить, что в этом случае изменения в полученном списке также повлияют на исходный массив и наоборот (так как это всего лишь представление массива в виде списка), а так же, что созданный список будет фиксированной длины (immutable)
Чтобы создать список, не связанный с исходным массивом, мы можем использовать следующий прием:
List<Integer> list = List.of(array);
Правда полученный список все еще будет immutable.
Немного преобразуем:
List<Integer> list = new ArrayList<>(List.of(array));
и получаем изменяемый (mutable) и не связанный с исходным массивом список.
То же самое можно сделать используя Stream API (с Java 8, а метод toList() с Java 16):
List<Integer> list = Arrays.stream(array).toList();
- immutable список
List<Integer> list = Arrays.stream(array)
.collect(Collectors.toCollection(ArrayList::new));
- стандартный расширяемый ArrayList. Если нам нужен другой тип списка, передаем в параметр toCollection() конструктор нужного типа.
По производительности вариант со Stream API немного проигрывает, но разница незначительная и будет заметна только при очень огромных размерах массива.
Для преобразования массива в список есть инструменты также и у Guava с Apache Commons Collections, но на самом деле, учитывая необходимость дополнительных зависимостей, они не так интересны.
🫡 Секреты Java
#list #array7 991
Привет, коллега! Не буду лукавить и скромничать)
В следующие 90 секунд (столько времени ты потратишь на этот текст) я хочу убедить тебя остаться со мной на канале “Секреты Java”.
✅ Дочитай до конца и поймешь – подписаться действительно стоит!
Меня зовут Антон и в своей компании я Senior Java Developer/Architect – формирую видение технической части проекта для команды разработчиков и отвечаю за достижение поставленных целей.
За последние 6 лет участвовал в 15 проектах, в том числе, и для крупнейших корпораций России.
Занимаюсь менторством, люблю и умею учить, всегда доброжелателен и открыт к общению.
✅ На канале ты найдешь:
- простые и понятные объяснения сложных концепций;
- интересные примеры кода;
- полезные советы по оптимизации и улучшению производительности;
- разбор bad и good practice, паттернов проектирования и архитектуры
- а так же советы по поиску работы и много другое
На “Секретах Java” помимо чисто технических вопросов мы также будем обсуждать актуальные тенденции и новости из мира Java, а иногда и за жизнь айтишников можем поговорить.
✅ “Не стыдно чего-нибудь не знать, но стыдно не хотеть учиться”
Для того, чтобы стать активным участником нашего будущего грандиозного комьюнити, твой возраст, пол и уровень текущих знаний о Java не имеет никакого принципиального значения.
Например, я начал изучать язык в 28 лет, а в IT попал в 29, имея за плечами гуманитарное образование, но при этом всегда помнил следующее изречение Сократа: “Не стыдно чего-нибудь не знать, но стыдно не хотеть учиться”. Поэтому смело спрашивай все, что захочешь, и мы вместе будем разбираться в нашем искусстве. Кстати, я тоже открыт для новых знаний и постоянно продолжаю учиться)
✅ Стремление к профессионализму – ключ к успеху в современном мире.
Подписываясь на канал, “Секреты Java”, ты бесплатно получишь доступ к эксклюзивному контенту, улучшишь свои навыки и знания в программировании.
Не упускай возможность стать частью нашего сообщества. Здесь ты сможешь общаться с единомышленниками, делиться своими идеями и находить вдохновение для новых проектов.
Ты готов к новым вызовам и достижению очередных высот в своей карьере?
✅ Тогда, вперед – жми “Подписаться”!
7 991
✅Блок finally и гарантия выполнения
Иногда при написании программы нам необходимо, чтобы какой-то блок кода гарантированно был выполнен при любых исключительных ситуациях.
Если мы обернем код, который может выбросить исключение в try-catch, то такую гарантию нам даст блок
finally.
Блок finally будет выполняется всегда, кроме ситуаций когда поток не дошел до его выполнения (System.exit(), ошибки JVM, бесконечный цикл и др.)
Рассмотрим несколько интересных наглядных примеров:
public String method() {
try {
System.out.println("Try block");
}
finally {
System.out.println("Finally block");
}
}
Вывод программы:
Try block
Finally block
А что вернет этот метод?
public String method() {
try {
return "Try block";
}
finally {
return "Finally block";
}
}
Ответ: Строку "Finally block"
И даже если код в блоке try бросает исключение:
public String method() {
try {
throw new Exception();
}
finally {
return "Finally block";
}
}
Метод все равно вернет строку "Finally block"!
Если в блоке finally есть оператор return или throw, то все брошенные исключения или возвращаемые значения в блоках try и catch выше будут забыты!
Поэтому как и любым инструментом, блоком finally необходимо пользоваться осторожно.
🫡 Секреты Java
#finally