Топ ошибок начинающих Java разработчиков: от А до Я
Java — мощный, но строгий язык. Новички часто спотыкаются на одних и тех же граблях, которые опытные разработчики обходят автоматически. В этой статье мы разберем 10 самых частых ошибок, которые совершают начинающие Java-программисты. Вы узнаете не только почему они возникают, но и как их исправить с помощью правильных паттернов и best practices.
1. Забытый NullPointerException (NPE)
Самая популярная ошибка в Java. Она возникает, когда вы пытаетесь вызвать метод или обратиться к полю объекта, который равен null.
Проблема
public class User {
private String name;
public int getNameLength() {
return name.length(); // NPE, если name == null
}
}
Решение
Всегда проверяйте объекты на null перед использованием. Используйте Optional или аннотации @Nullable / @NonNull.
public int getNameLength() {
return Optional.ofNullable(name)
.map(String::length)
.orElse(0);
}
2. Игнорирование контракта equals() и hashCode()
Если вы переопределяете equals(), вы обязаны переопределить и hashCode(). Иначе ваши объекты не будут корректно работать в коллекциях HashSet, HashMap и других.
Проблема
public class Person {
private String id;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(id, person.id);
}
// hashCode() не переопределен!
}
// В HashMap два "равных" объекта окажутся в разных корзинах
Map<Person, String> map = new HashMap<>();
map.put(new Person("1"), "Alice");
System.out.println(map.get(new Person("1"))); // null
Решение
Всегда используйте Objects.hash() для генерации хэш-кода:
@Override
public int hashCode() {
return Objects.hash(id);
}
3. Неправильное использование Generics (сырые типы)
Использование сырых типов (raw types) лишает вас проверки типов на этапе компиляции. Это приводит к ClassCastException в рантайме.
Проблема
List list = new ArrayList(); // сырой тип
list.add("строка");
list.add(123); // компилятор не ругается
String s = (String) list.get(1); // ClassCastException!
Решение
List<String> list = new ArrayList<>();
list.add("строка");
// list.add(123); // ошибка компиляции — отлично!
String s = list.get(0); // безопасно
4. Мутация объектов в коллекциях
Изменение полей объекта, который уже находится в HashSet или является ключом в HashMap, ломает структуру данных. Хэш-код объекта меняется, но корзина остается прежней.
Проблема
Set<StringBuilder> set = new HashSet<>();
StringBuilder sb = new StringBuilder("A");
set.add(sb);
sb.append("B"); // мутация
System.out.println(set.contains(sb)); // может быть false!
Решение
Используйте иммутабельные объекты (String, Integer, record) в качестве ключей. Если нужно изменить объект — удалите его из коллекции, измените, а затем добавьте заново.
5. Неправильная обработка исключений
Две крайности: либо глотать исключения (пустой catch), либо бросать слишком общие исключения (throws Exception).
Проблема
try {
// опасный код
} catch (Exception e) {
// ничего не делаем — ошибка потеряна
}
Решение
Ловите конкретные исключения и хотя бы логируйте их:
try {
// опасный код
} catch (IOException | SQLException e) {
logger.error("Ошибка при работе с БД", e);
throw new CustomBusinessException("Не удалось сохранить данные", e);
}
6. Использование float/double для денежных расчетов
Числа с плавающей точкой (float, double) не подходят для точных финансовых вычислений из-за ошибок округления.
Проблема
double price = 0.1;
double total = price * 3;
System.out.println(total); // 0.30000000000000004
Решение
Используйте BigDecimal с конструктором String:
BigDecimal price = new BigDecimal("0.1");
BigDecimal total = price.multiply(BigDecimal.valueOf(3));
System.out.println(total); // 0.3
7. Закрытие ресурсов вручную (до Java 7)
Старый стиль try-finally легко приводит к утечкам ресурсов, если в блоке finally тоже возникает исключение.
Проблема
InputStream is = null;
try {
is = new FileInputStream("file.txt");
// работаем
} finally {
if (is != null) is.close(); // может бросить исключение
}
Решение
Используйте try-with-resources (Java 7+):
try (InputStream is = new FileInputStream("file.txt")) {
// работаем — ресурс закроется автоматически
} catch (IOException e) {
logger.error("Ошибка", e);
}
8. Сравнение строк через ==
Оператор == сравнивает ссылки, а не содержимое строк. Это одна из самых коварных ловушек для новичков.
Проблема
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2); // false — разные объекты!
Решение
Всегда используйте equals():
System.out.println(s1.equals(s2)); //