Топ ошибок начинающих Java разработчиков: от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Топ ошибок начинающих 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)); //                

Блоги

Рекомендаций по книгам