Введение: Почему новички в Java часто ошибаются?
Java — один из самых популярных и востребованных языков программирования в мире. Однако его строгая типизация, обилие шаблонного кода и особенности работы с памятью часто ставят в тупик начинающих разработчиков. Даже опытные программисты иногда допускают классические ляпы, но для новичков они могут стать причиной долгих часов дебага.
В этой статье мы разберем 10 самых частых ошибок начинающих Java разработчиков. Вы узнаете, как их распознать, исправить и, что важнее, — не допускать в будущем. Каждый пункт сопровождается наглядным примером кода и практическим советом.
1. Забывают про NullPointerException (NPE)
Самая известная и коварная ошибка в Java. Чаще всего NPE возникает, когда вы пытаетесь вызвать метод или обратиться к полю у объекта, который равен null.
Пример ошибки
public class Main {
public static void main(String[] args) {
String text = null;
int length = text.length(); // NullPointerException!
}
}
Как исправить
- Всегда проверяйте объекты на
nullперед использованием. - Используйте
Optionalдля методов, которые могут вернуть пустое значение. - Применяйте аннотации
@Nullableи@NonNullдля документирования кода.
public static int getLengthSafely(String text) {
return Optional.ofNullable(text)
.map(String::length)
.orElse(0);
}
2. Сравнение строк через == вместо equals()
Оператор == сравнивает ссылки на объекты, а не их содержимое. Для строк это критично, так как Java использует пул строк и создает новые объекты при вызове new String().
Пример ошибки
String a = "Hello";
String b = new String("Hello");
System.out.println(a == b); // false! Хотя строки одинаковые
Как исправить
- Всегда используйте
equals()для сравнения содержимого строк. - Для игнорирования регистра применяйте
equalsIgnoreCase().
System.out.println(a.equals(b)); // true
3. Игнорирование ресурсов: не закрывают Connection, File или Stream
Работа с файлами, базами данных или сетевыми соединениями требует явного закрытия ресурсов. Если этого не делать, происходит утечка памяти и блокировка файлов.
Пример ошибки
FileInputStream fis = new FileInputStream("file.txt");
// Работаем с файлом
// Забыли вызвать fis.close();
Как исправить
Начиная с Java 7, используйте конструкцию try-with-resources. Ресурсы будут закрыты автоматически, даже если возникнет исключение.
try (FileInputStream fis = new FileInputStream("file.txt")) {
// Работаем с файлом
} catch (IOException e) {
e.printStackTrace();
}
4. Непонимание принципов работы equals() и hashCode()
Если вы переопределяете equals(), вы обязаны переопределить и hashCode(). Иначе ваши объекты не смогут корректно работать в коллекциях HashSet, HashMap и подобных.
Пример ошибки
class User {
String name;
User(String name) { this.name = name; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name);
}
// hashCode не переопределен!
}
Set<User> users = new HashSet<>();
users.add(new User("Alice"));
users.add(new User("Alice"));
System.out.println(users.size()); // 2, хотя ожидалось 1
Как исправить
@Override
public int hashCode() {
return Objects.hash(name);
}
5. Изменение коллекции во время итерации по ней
Попытка удалить элемент из ArrayList или HashMap во время цикла for-each приводит к ConcurrentModificationException.
Пример ошибки
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : list) {
if (s.equals("B")) {
list.remove(s); // ConcurrentModificationException!
}
}
Как исправить
Используйте Iterator с его методом remove() или removeIf() (Java 8+).
list.removeIf(s -> s.equals("B")); // Работает корректно
6. Путаница между && и & (также || и |)
Операторы && и || — это короткозамкнутые (short-circuit) операторы. Если результат уже известен, второе условие не вычисляется. & и | всегда вычисляют оба операнда. Для логических выражений почти всегда нужно использовать короткозамкнутые версии.
Пример ошибки
if (obj != null & obj.getName().equals("Test")) { // & вызывает NPE, если obj == null
...
}
Как исправить
if (obj != null && obj.getName().equals("Test")) { // Безопасно
...
}
7. Использование raw типов (generics без указания типа)
Raw типы (например, List вместо List<String>) лишают вас проверки типов на этапе компиляции. Это может привести к ClassCastException во время выполнения.
Пример ошибки
List list = new ArrayList();
list.add("Hello");
list.add(123); // Компилятор не ругается
String s = (String) list.get(1); // ClassCastException!
Как исправить
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // Ошибка компиляции