Найти в Дзене
От Джуна до Лида (IT Jobs)

5 ошибок, которые совершают 99% Java-разработчиков

Каждый программист, работающий на Java, знает, как плохо тратить кучу времени на устранение ошибок в коде. Иногда на это уходит несколько часов. При этом многие ошибки появляются из-за того, что разработчик игнорирует базовые правила, — то есть это ошибки очень низкого уровня. Сегодня мы рассмотрим некоторые распространенные ошибки кодирования, а затем объясним, как их устранить. Надеюсь, это поможет вам избежать проблем в повседневной работе Сравнение объектов с использованием Objects.equals Я полагаю, что вы знакомы с этим методом. Многие разработчики часто его используют. Это метод, появившийся в JDK 7, помогает быстро сравнивать объекты и эффективно избегать надоедливую проверку нулевого указателя. Но этот метод иногда используется неправильно. Вот что я имею в виду: Long longValue = 123L;
System.out.println(longValue==123); //true
System.out.println(Objects.equals(longValue,123)); //false Почему замена == на Objects.equals() приведет к неверному результату? Это связано с тем, что
Оглавление

Каждый программист, работающий на Java, знает, как плохо тратить кучу времени на устранение ошибок в коде. Иногда на это уходит несколько часов. При этом многие ошибки появляются из-за того, что разработчик игнорирует базовые правила, — то есть это ошибки очень низкого уровня. Сегодня мы рассмотрим некоторые распространенные ошибки кодирования, а затем объясним, как их устранить. Надеюсь, это поможет вам избежать проблем в повседневной работе

Сравнение объектов с использованием Objects.equals

Я полагаю, что вы знакомы с этим методом. Многие разработчики часто его используют. Это метод, появившийся в JDK 7, помогает быстро сравнивать объекты и эффективно избегать надоедливую проверку нулевого указателя. Но этот метод иногда используется неправильно. Вот что я имею в виду:

Long longValue = 123L;
System.out.println(longValue==123); //true
System.out.println(Objects.equals(longValue,123)); //false

Почему замена == на Objects.equals() приведет к неверному результату? Это связано с тем, что с помощью компилятора == будет получен базовый тип данных, соответствующий типу упаковки longValue, а затем происходит сравнение его с этим базовым типом данных. Это эквивалентно тому, что компилятор автоматически преобразует константы в базовый тип данных сравнения.

После использования метода Objects.equals() базовым типом данных константы компилятора по умолчанию является int. Ниже приведен исходный код Objects.equals(), в котором a.equals(b) использует Long.equals() и определяет тип объекта. Это происходит потому, что компилятор посчитал, что константа имеет тип int, поэтому результат сравнения должен быть ложным.

public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}

public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}

Зная причину, исправить ошибку очень просто. Просто объявляйте тип данных констант, например, Objects.equals(longValue,123L). Вышеперечисленные проблемы не возникнут, если логика будет строгой. Что нам нужно сделать, так это следовать четким правилам программирования.

Неправильный формат даты

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

Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant));//2022-12-31 08:00:00

Здесь используется формат YYYY-MM-dd для изменения даты с 2021 по 2022 год. Так делать не стоит. Почему? Это связано с тем, что шаблон Java DateTimeFormatter “YYYY” основан на стандарте ISO-8601, в котором год определяется по четвергу каждой недели. Но 31 декабря 2021 года пришлось на пятницу, поэтому программа ошибочно указывает 2022 год. Чтобы избежать подобного, для форматирования даты необходимо использовать формат yyyy-MM-dd.

Эта ошибка встречается нечасто, только с приходом нового года. Но в моей компании это стало причиной сбоя в производстве.

Использование ThreadLocal в ThreadPool

Если вы создадите переменную ThreadLocal, то поток, обращающийся к этой переменной, создаст локальную переменную потока. Так вы сможете избежать проблем с безопасностью потоков.

Однако если вы используете ThreadLocal в пуле потоков, вам нужно быть осторожным. В вашем коде может появиться неожиданный результат. Для простого примера предположим, что у нас есть платформа электронной торговли, и пользователям необходимо отправить электронное письмо для подтверждения выполненной покупки товаров.

private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);

private ExecutorService executorService = Executors.newFixedThreadPool(4);

public void executor() {
executorService.submit(()->{
User user = currentUser.get();
Integer userId = user.getId();
sendEmail(userId);
});
}

Если мы используем ThreadLocal для сохранения информации о пользователе, появится скрытая ошибка. Поскольку используется пул потоков, а потоки можно использовать повторно, то при использовании ThreadLocal для получения информации о пользователе может ошибочно отображаться чужая информация. Для решения этой проблемы стоит использовать сессии.

Используйте HashSet для удаления повторяющихся данных

При кодировании у нас часто возникает потребность в дедупликации. При упоминании дедупликации многие первым делом думают использовать HashSet. Однако неосторожное использование HashSet может привести к сбою дедупликации.

User user1 = new User();
user1.setUsername("test");

User user2 = new User();
user2.setUsername("test");

List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size());// the size is 2

Некоторые внимательные читатели должны догадаться о причине сбоя. HashSet использует хэш-код для обращения к хэш-таблице и использует метод equals, чтобы определить, равны ли объекты. Если определяемый пользователем объект не переопределяет метод хэш-кода и метод equals, то по умолчанию будут использоваться метод хэш-кода и метод equals родительского объекта. Таким образом HashSet предположит, что это два разных объекта, что приведет к сбою дедупликации.

Исключение "съеденного" потока пула

ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(()->{
//do something
double result = 10/0;
});

Приведенный выше код имитирует сценарий, где в пуле потоков выдается исключение. Бизнес-код должен предполагать различные ситуации, поэтому очень вероятно, что по некоторым причинам он вызовет RuntimeException.

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

#it #programmer #java #mistakes #code