Строгая Типизация в Java не всегда спасает в Runtime
Java - это язык строгой типизации. Но это далеко не всегда дает гарантию безопасности во время выполнения программы (т.е. в Runtime).
В отличие от интерпретируемых языков, строгая типизация в Java позволяет избегать миллиарда ошибок, проверяя типы на момент компиляции. Вы не можете назначить строку целому числу (про приведение типов речи не идет). В статье мы рассмотрим наиболее частые ошибки, которые компилятор не может отловить.
1. Null Pointer Exception - топ-1 проблема в Java.
К сожалению, в Java до сих пор нет null-safe типов. А это значит, что любой объект потенциально может быть не инициализирован и указывать на null. Рассмотрим очевидный случай:"
Решением данной проблемы будет введение null-safe типов, которые будут позволять объекту быть null только если это явно указано.
2.Удаление из коллекции во время итерирования.
Классическая ошибка при работе с коллекциями - удаление записей из коллекции без использования итератора. Рассмотрим пример ниже:
При попытке запуска такого кода в рантайме мы получим:
Если присмотреться поглубже, можно разглядеть, что внутри итератора это вызывает ошибку при попытке обратиться к следующему элементу:
3. Немодифицируемые коллекции в Java реализуют интерфейсы, предназначенные для модифицируемых коллекций. Аналогичная ситуация существует и для коллекций фиксированного размера.
На мой взгляд, это представляет собой определенный недостаток в дизайне Java. В языке присутствует понятный интерфейс Collection, который устанавливает требования для всех коллекций, включая возможность удаления, добавления и других модификаций данных. Однако существуют реализации, такие как:
List<Integer> myNumbers = Arrays.of(1,2,3)
List<Integer> myNumbers = Collections.unmodifiableList(new ArrayList<>(mutableNumbers));
список можно продолжать
Все эти реализации реализованы через интерфейсы, предполагающие наличие функциональности для модификации данных, но на практике они этого не делают. Об этом становится известно только во время выполнения программы. По вопросу модификаций коллекций, Дуг Ли (Doug Lea) высказывал следующее(см. первый параграф).
Кратко говоря, он не стал разделять существующие коллекции на обычные и немодифицируемые, так как это привело бы к увеличению числа интерфейсов и итераторов: "Now we're up to twenty or so interfaces and five iterators." Создание минимум 20 новых интерфейсов и 5 новых итераторов. Более того это все равно не помогло избежать всех потенциальных Runtime исключений.
Я не могу судить человека, кто написал java.util.concurrent. Но я знаю, что в том же Kotlin'e были созданных Mutable/Immutable коллекции, которые позволяют избежать подобных проблем. Мне не важно, добавит ли Java 25 или 2500 новых классов и надеюсь, что в будущем будут добавлены интерфейсы и реализации, предназначенные исключительно для немодифицируемых коллекций без методов типа get/remove/add и так далее. Все текущие недоразумения могут быть отмечены как устаревшие (deprecated).
4. Перезапуск ранее запущенного потока.
В случае работы с многопоточностью можно допустить ряд ошибок, которые не будут отловлены на моменте компиляции. Например, попытка "переиспользовать" один и тот же поток, запустив его дважды:
5.Некорректная работа с монитором.
Также при работе с монитором нужно соблюдать ряд правил. Я не буду вдаваться в подробности, просто приведу пример неправильной попытки работы с локом:
6.Бесконечная рекурсия.
Компилятор не может отличить рекурсию, которая будет работать с условием прерывания, или без. Поэтому бесконечная рекурсия вполне может быть скомпилирована:
Большинство всех показанных выше проблем отлавливаются утилитами которые анализируют код на ошибки вроде SonarQube или ему подобных, об этом я писал в этой статье.
Спасибо за внимание больше о Java и смежных технологиях я публикую в этом канале.







