Какой из них быстрее:
Либо этот
try {
n.foo();
}
catch(NullPointerException ex) {
}
, либо
if (n != null) n.foo();
Это не вопрос о том, что быстрее, скорее вопрос о правильности.
Исключение составляют именно такие обстоятельства, исключительные .
Если n
может быть null
как часть нормальной бизнес-логики, тогда используйте бросок if..else
, else .
исключение.
Явное тестирование нулевого указателя намного быстрее, чем использование обработки исключений.
Для справки, большинство других проблем при использовании исключений возникает при создании экземпляра объекта исключения. В частности, в вызове fillInStackTrace ()
, который должен:
В некоторых случаях вы можете уменьшить это, повторно используя объект исключения или переопределив метод исключения fillInStackTrace ()
конкретного приложения, чтобы он не выполнялся. Обратной стороной в обоих случаях является то, что правильные трассировки стека больше не будут доступны, чтобы помочь вам отладить неожиданные исключения. (И ни то, ни другое не применимо к примеру OP.)
Хотя создание исключений обходится дорого, выброс, распространение и перехват исключений также не совсем дешевы.
Есть вторая причина, по которой явное нулевое тестирование является лучшей идеей. Рассмотрим следующее:
try {
doSomething(a.field);
} catch (NullPointerException ex) {
System.err.println("a.field is null");
}
Что произойдет, если NPE произойдет во время вызова doSomething (...)
, а не во время вычисления выражения a.field
? Конечно, мы поймаем NPE, но мы неправильно диагностируем его, а затем попытаемся продолжить ... неправильно предполагая, что a.field
не задано или что-то в этом роде.
Отличить «ожидаемый» NPE от «неожиданного» NPE теоретически возможно, но на практике очень сложно. Гораздо более простой и надежный подход заключается в явном тестировании ожидаемых значений null
(например, с помощью оператора if
) и обработки всех NPE как ошибок.
(Я уверен, что именно это имеет в виду @Mitch, говоря «рассматривая исключения как исключительные», но я думаю, что это помогает разъяснить ситуацию с помощью наглядного примера ...)
Я заметил, что я не единственный, кто читает информационный бюллетень специалиста по Java:)
Помимо того факта, что существует семантическая разница (NPE не обязательно вызвано разыменованием n
, оно могло быть вызвано некоторой ошибкой в foo ()
) и проблемой читабельности (try / catch более сбивает с толку для читателя, чем if
), они должны быть примерно одинаково быстрыми в случае, когда n! = null
(с версией if / else, имеющей небольшое преимущество), но когда n == null
if / else намного быстрее. Почему?
n == null
, виртуальная машина должна создать новый объект исключения и заполнить его трассировку стека . Информацию о трассировке стека получить очень дорого, поэтому здесь версия try / catch намного дороже. if
, они думают, что ушли дешево, когда n! = Null
. Дело, однако, в том, что виртуальная машина будет выполнять неявную нулевую проверку при разыменовании ... то есть, если JIT не может определить, что n
должен быть ненулевым, что он может в версии if / else. Это означает, что версии if / else и try / catch должны работать примерно одинаково. Но ... Если n.foo ()
вызывает внутренний вызов NPE, вы отключены от длительного сеанса отладки (или, что еще хуже, ваше приложение не работает в производственной среде ..). Просто не делай этого.
В любом случае, сколько наносекунд вы планируете сэкономить?
Ответ на этот вопрос не так прост, как кажется, поскольку он зависит от процента случаев, когда объект действительно является нулевым. Если это происходит очень редко (скажем, в 0,1% случаев), то это может быть даже быстрее. Чтобы проверить это, я провел несколько сравнительных тестов и получил следующие результаты (с клиентом Java 1.6):
Benchmaring with factor 1.0E-4
Average time of NullIfTest: 0.44 seconds
Average time of NullExceptionTest: 0.45 seconds
Benchmaring with factor 0.0010
Average time of NullIfTest: 0.44 seconds
Average time of NullExceptionTest: 0.46 seconds
Benchmaring with factor 0.01
Average time of NullIfTest: 0.42 seconds
Average time of NullExceptionTest: 0.52 seconds
Benchmaring with factor 0.1
Average time of NullIfTest: 0.41 seconds
Average time of NullExceptionTest: 1.30 seconds
Benchmaring with factor 0.9
Average time of NullIfTest: 0.07 seconds
Average time of NullExceptionTest: 7.48 seconds
Это кажется мне довольно убедительным. NPE просто очень медленные. (При желании я могу выложить код бенчмарка)
edit: Я только что сделал интересное открытие: при проведении бенчмарка с использованием серверной JVM результаты кардинально меняются:
Benchmaring with factor 1.0E-4
Average time of NullIfTest: 0.33 seconds
Average time of NullExceptionTest: 0.33 seconds
Benchmaring with factor 0.0010
Average time of NullIfTest: 0.32 seconds
Average time of NullExceptionTest: 0.33 seconds
Benchmaring with factor 0.01
Average time of NullIfTest: 0.31 seconds
Average time of NullExceptionTest: 0.32 seconds
Benchmaring with factor 0.1
Average time of NullIfTest: 0.28 seconds
Average time of NullExceptionTest: 0.30 seconds
Benchmaring with factor 0.9
Average time of NullIfTest: 0.05 seconds
Average time of NullExceptionTest: 0.04 seconds
При использовании серверной VM разница едва заметна. И все же: я бы предпочел не использовать отлов NullPointerException, если это действительно не исключение.
Обработка исключений обычно требует больших затрат. Спецификация VM может дать вам некоторое представление о том, сколько, но в приведенном выше случае if (n! = Null) n.foo ();
быстрее.
Хотя я согласен с Митчем Уитом относительно реального вопроса, так это правильность.
@Mitch Wheat - В его защиту это довольно надуманный пример. :)
Конструкция if работает быстрее. Условие можно легко перевести в машинный код (инструкции процессора).
Альтернатива ( try-catch ) требует создания объекта NullPointerException.
Определенно вторая форма намного быстрее. В сценарии try-catch
он генерирует исключение
, которое вызывает новое исключение ()
той или иной формы. Затем вызывается блок catch
, который является вызовом метода и должен выполнить любой код в нем. Вы поняли.
Во-первых, if... then ... else лучше, по многочисленным причинам, указанным другими постерами.
Однако это не обязательно быстрее! Это полностью зависит от соотношения нулевых и не нулевых объектов. Вероятно, обработка исключения, а не проверка на null занимает в сотни тысяч раз больше ресурсов, однако, если нулевой объект встречается только один раз на каждый миллион объектов, то вариант с исключением будет немного быстрее. Но не настолько быстрее, чтобы это стоило того, чтобы сделать вашу программу менее читабельной и более трудной для отладки.
Этот вопрос недавно обсуждался доктором Хайнцем:
http://javaspecialists.eu/webinars/recordings/ if-else-npe-teaser.mov
Помимо хороших ответов (используйте исключения в исключительных случаях), я вижу, что вы в основном пытаетесь избежать повсюду нулевых проверок. Java 7 будет иметь "нулевой безопасный" оператор, который будет возвращать null при вызове n? .Foo ()
вместо того, чтобы бросать NPE. Это заимствовано из языка Groovy. Также существует тенденция вообще избегать использования null в коде, за исключением случаев, когда это действительно необходимо (например, при работе с библиотеками). См. Этот другой ответ для более подробного обсуждения этого.
Избегайте! = Null операторов
if-else работает быстрее, потому что блок try-catch вызывает трассировку стека исключений. Вы можете принять это за то, что блок If-Else выполняет одну инструкцию для выполнения оценки, но Try-Catch будет запускать тысячи инструкций, чтобы вызвать исключение, когда это произойдет.