Кофе-брейк #134. Чего следует избегать при написании Java-кода. Как в Java используются final, finally и finalize

Статья из группы Random

Чего следует избегать при написании Java-кода

Источник: Medium Вашему вниманию предлагается статья с перечислением распространенных ошибок, которые разработчики допускают при написании Java-кода. Кофе-брейк #134. Чего следует избегать при написании Java-кода. Как в Java используются final, finally и finalize - 1

Использование Enum.values

То, что это считается ошибкой, застало меня врасплох, поскольку я регулярно использовал Enum.values. Проблема здесь заключается в том, что Enum.values() по спецификации должно быть неизменяемым списком. Для этого он должен возвращать новый экземпляр массива со значениями перечисления при каждом вызове.

public enum Fruits {
    APPLE, PEAR, ORANGE, BANANA;

    public static void main(String[] args) {
        System.out.println(Fruits.values());
        System.out.println(Fruits.values());
    }
}
// output
// [Lcom.test.Fruits;@7ad041f3
// [Lcom.test.Fruits;@251a69d7
Здесь два отдельных объекта в памяти, поэтому может показаться, что это не имеет большого значения. Но если вы используете Fruit.values() при обработке запроса и у вас высокая нагрузка, то это может привести к проблемам с памятью. Вы можете легко исправить ситуацию, введя приватную статическую конечную переменную VALUES для кэширования.

Передача параметров Optional в качестве параметра метода

Рассмотрим следующий код:

LocalDateTime getCurrentTime(Optional<ZoneId> zoneId) {
    return zoneId.stream()
        .map(LocalDateTime::now)
        .findFirst()
        .orElse(LocalDateTime.now(ZoneId.systemDefault()));
}
Мы передаем Optional параметр zoneId и на основании его наличия решаем, давать ли время в системном часовом поясе или использовать указанный пояс. Однако это не совсем правильный способ работы с Optional. Мы должны избегать их использования в качестве параметра и вместо этого использовать перегрузку методов.

LocalDateTime getCurrentTime(ZoneId zoneId) {
  return LocalDateTime.now(zoneId);
}

LocalDateTime getCurrentTime() {
  return getCurrentTime(ZoneId.systemDefault());
}
Код, который вы видите выше, намного легче читать и отлаживать.

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

Строки в Java неизменяемы. Это означает, что после создания они больше не редактируются. JVM поддерживает пул строк и перед созданием нового вызывает метод String.intern(), который возвращает экземпляр из пула строк, соответствующий значению, если таковой существует. Допустим, мы хотим создать длинную строку, объединив в нее элементы.

String longString = new StringBuilder()
  .append("start")
  .append("middle")
  .append("middle")
  .append("middle")
  .append("end")
  .toString();
Недавно нам сказали, что это очень плохая идея, потому что старые версии Java делали следующее:
  • в строке 1 строка “start” вставляется в пул строк и на нее указывает longString
  • в строке 2 в пул добавляется строка “startmiddle” и на нее указывает longString
  • в строке 3 у нас есть “startmiddlemiddle”
  • в строке 4 — “startmiddlemiddlemiddle”
  • и, наконец, в строке 5 мы добавляем в пул “startmiddlemiddlemiddleend” и указываем на него longString
Все эти строки остаются в пуле и никогда не используются, что приводит к потере большого объема оперативной памяти. Чтобы этого избежать, мы можем использовать StringBuilder.

String longString = new StringBuilder()
  .append("start")
  .append("middle")
  .append("middle")
  .append("middle")
  .append("end")
  .toString();
StringBuilder создает только одну строку при вызове метода toString, тем самым избавляя нас от всех промежуточных строк, которые изначально добавлены в пул. Однако после Java 5 это делается компилятором автоматически, поэтому лучше использовать конкатенацию строк с помощью “+”.

Использование примитивных классов-оберток (primitive wrappers), когда они не нужны

Рассмотрим следующие два фрагмента:

int sum = 0;
for (int i = 0; i < 1000 * 1000; i++) {
  sum += i;
}
System.out.println(sum);

Integer sum = 0;
for (int i = 0; i < 1000 * 1000; i++) {
  sum += i;
}
System.out.println(sum);
Первый пример выполняется на моем компьютере в шесть раз быстрее второго. Единственное отличие состоит в том, что мы используем класс-обертку Integer. Суть работы Integer состоит в том, что в строке 3 среда выполнения должна преобразовать переменную суммы в примитивное целое (автоматическая распаковка), и после выполнения сложения результат затем упаковывается в новый класс Integer. (автоматическая упаковка). Это означает, что мы создаем 1 миллион классов Integer и выполняем два миллиона операций упаковки, что объясняет резкое замедление. Классы-обертки следует использовать только тогда, когда их нужно хранить в коллекциях. Однако будущие версии Java будут поддерживать коллекции примитивных типов, что сделает обертки устаревшими.

Как в Java используются final, finally и finalize

Источник: Newsshare В этой статье вы узнаете, где, когда и зачем используется ключевое слово Finalize, и стоит ли его вообще применять в Java. Также вы узнаете о различиях между final, finally и finalize.

Где используется блок finally?

Блок finally в Java используется для размещения таких важных частей кода, как код очистки, закрытие файла или, например, закрытие connection. Блок finally выполняется независимо от того, появляется ли исключение и обрабатывается ли это исключение. A finally содержит все важные операторы независимо от того, произошло исключение или нет.

Как вы завершаете (finalize) переменную в Java?

Есть 3 способа инициализировать конечную (final) переменную Java:
  • Вы можете инициализировать конечную переменную, когда она объявлена. Этот подход является наиболее распространенным.
  • Пустая конечная переменная инициализируется в блоке экземпляра-инициализатора или в конструкторе.
  • Пустая конечная статическая переменная инициализируется внутри статического блока.

Можем ли мы вызвать метод finalize вручную в Java?

Метод finalize() не нужно и не рекомендуется вызывать при выходе объекта из области действия. Поскольку заранее неизвестно, когда будет (и будет ли вообще) выполняться метод finalize(). В целом finalize — не лучший метод для использования, если нет конкретной потребности в нем. Начиная с Java 9 он не рекомендуется к использованию.

Когда вызывается метод finalize?

Метод finalize() вызывается для выполнения очистки непосредственно перед сборкой мусора.

В чем разница между final, finally и finalize?

Основное различие между final, finally и finalize заключается в том, что final — это модификатор доступа, finally — блок в обработке исключений, а finalize — метод класса объекта.

Можно ли переопределить методы final?

Нет, методы, объявленные окончательными (final), не могут быть переопределены или скрыты.

Можем ли мы использовать try без catch?

Да, можно иметь блок try без блока catch, используя блок final. Как мы знаем, блок final всегда будет выполняться, даже если в блоке try возникло любое исключение, кроме System.

Что такое методы final?

Окончательные (final) методы не могут быть переопределены или скрыты подклассами. Они используются для предотвращения неожиданного поведения подкласса, изменяющего метод, который может иметь решающее значение для функции или согласованности класса.

Может ли конструктор быть final?

Конструкторы НИКОГДА не могут быть объявлены как final. Ваш компилятор всегда будет выдавать ошибку типа “модификатор final не разрешен”. Final при применении к методам означает, что метод не может быть переопределен в подклассе. Конструкторы НЕ являются обычными методами.

Для чего используется ключевое слово finally?

Ключевое слово finally используется для создания блока кода, следующего за блоком try. Блок finally всегда выполняется независимо от того, произошло ли исключение. Использование блока finally позволяет вам запускать любые операторы типа очистки, которые вы хотите выполнить, независимо от того, что происходит в защищенном коде.

Какая вообще польза от finally?

Блок finally в Java — это блок, используемый для выполнения таких частей кода, как закрытие соединения и других. Блок finally в Java всегда выполняется независимо от того, обрабатывается исключение или нет. Следовательно, он содержит все необходимые операторы, которые нужно вывести независимо от того, произошло исключение или нет.

Почему метод Finalize защищен?

Почему модификатор доступа метода finalize() сделан защищенным? Почему он не может быть публичным? Он не является публичным, потому что его не должен вызывать кто-либо, кроме JVM. При этом он должен быть защищен, чтобы его могли переопределить подклассы, которым необходимо определить для него поведение.

Всегда ли метод finalize вызывается в Java?

Метод finalize вызывается, когда объект должен собрать мусор. Это может произойти в любое время после того, как он стал пригодным для сборки мусора. Обратите внимание: вполне возможно, что объект никогда не подвергается сборке мусора (и, следовательно, finalize никогда не вызывается).

В чем разница между throw и throws?

Ключевое слово throw используется для намеренного создания исключения. Ключевое слово throws используется для объявления одного или нескольких исключений, разделенных запятыми. При использовании throw создается только одно исключение.

В чем разница между ключевыми словами try catch и finally?

Это два разных понятия: блок catch выполняется только в том случае, если в блоке try возникает исключение. Блок finally всегда выполняется после блока try(-catch), вне зависимости от того, появилось исключение или нет.

Можем ли мы использовать finally без try в Java?

Блок finally должен быть связан с блоком try, вы не можете использовать finally без блока try. Вы должны поместить в этот блок те операторы, которые должны выполняться всегда.

Можем ли мы наследовать метод final?

Наследуется ли метод final? Да, метод final наследуется, но вы не можете его переопределить.

Какой метод нельзя переопределить?

Мы не можем переопределить статические методы, потому что переопределение методов основано на динамическом связывании во время выполнения, а статические методы связываются с помощью статического связывания во время компиляции. Таким образом, переопределение статических методов невозможно.

Что произойдет, если метод final будет переопределен?

Метод final, объявленный в родительском классе, не может быть переопределен дочерним классом. Если мы попытаемся переопределить последний метод, компилятор выдаст исключение во время компиляции.

Ключевое слово final и метод finalize работают одинаково?

В их функциях нет сходства, кроме похожих названий. Ключевое слово final может использоваться с классом, методом или переменной в Java. Класс final не является расширяемым в Java, метод final не может быть переопределен, а переменная final не может быть изменена.

Что такое ключевое слово super в Java?

Ключевое слово super в Java — это ссылочная переменная, которая используется для ссылки на непосредственный объект родительского класса. Всякий раз, когда вы создаете экземпляр подкласса, параллельно создается экземпляр родительского класса, на который ссылается ссылочная переменная super. Ключевое слово super можно использовать для непосредственного вызова метода родительского класса.

В чем разница между ключевыми словами static и final?

Основное различие между ключевыми словами static и final заключается в том, что ключевое слово static используется для определения члена класса, который можно использовать независимо от любого объекта этого класса. Ключевое слово Final используется для объявления постоянной переменной, а также метода, который нельзя переопределить, и класса, который нельзя наследовать.
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Anonymous #1384518 Уровень 35 Expert
9 июня 2022
По поводу StringBuilder. Там в примере два одинаковых кода. Наверно в первом случае автор хотел привести пример с конкатенацией через "+" Однако меня смутила фраза: Однако после Java 5 это делается компилятором автоматически, поэтому лучше использовать конкатенацию строк с помощью “+”. Дело в том что он это делает только в тому случае, если заранее известно сколько будет сложений. Например при вызове какого-либо метода ("строка1" + "два" + "три"). Здесь да, будет автоматически создан один единственный билдер. Если же конкатенация будет в цикле, компилятор не поймёт какой должен быть итоговый результат, и при каждом склеивании будет создаваться новый билдер. А это уже лишняя затрата ресурсов. Именно в таких случаях билдер стоит применять самому вручную. finaly блок не отрабатывает, если в блоке catch или try было вызвано System.exit либо если поток выполнялся в какой-то нити и её выполнение прервали. (Особенно такое бывает например в демон потоках). Также блок может не отработать из-за некоторых ошибок на уровне jvm, например OutOfMemoryError