Java if vs. try / catch накладные расходы - PullRequest
53 голосов
/ 24 декабря 2011

Существуют ли какие-либо издержки в Java для использования блока try / catch , в отличие от блока if (при условии, что в противном случае приложенный код не запрашивает)?

Например, возьмем следующие две простые реализации метода «безопасной обрезки» для строк:

public String tryTrim(String raw) {
    try {
        return raw.trim();
    } catch (Exception e) {
    }
    return null;
}

public String ifTrim(String raw) {
    if (raw == null) {
        return null;
    }
    return raw.trim();
}

Если вход raw очень редко null, естьесть ли разница в производительности между этими двумя методами?

Кроме того, - это хороший шаблон программирования для использования подхода tryTrim() для упрощения компоновки кода, особенно когда много если блоки проверки редких состояний ошибки можно избежать, заключив код в один блок try / catch?

Например, часто встречается метод с N parameters, который использует M <= N из них около его начала, быстро и детерминировано, если любой такой параметр является «недействительным» (например, нулевая или пустая строка), не затрагивая остальную часть кода.

В таких случаях вместоприходитсяобряд k * M если блоки (где k - среднее количество проверок на параметр, например k = 2 для нулевых или пустых строк), блок try / catch будет значительноСократите код, и комментарий в 1-2 строки можно использовать для явного указания на «нетрадиционную» логику.

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

Ответы [ 5 ]

61 голосов
/ 24 декабря 2011

Я знаю, что вы спрашиваете о снижении производительности, но вы действительно не должны использовать try / catch и if взаимозаменяемо.

try / catch для вещей, которые идутнеправильно, что находятся вне вашего контроля и не в нормальном потоке программы.Например, пытаясь записать в файл и файловая система заполнена?Эту ситуацию обычно следует обрабатывать с помощью операторов try / catch.

if, которые должны представлять собой обычный поток и обычную проверку ошибок.Так, например, пользователь не может заполнить обязательное поле ввода?Для этого используйте if, а не try / catch.

Мне кажется, что ваш пример кода убедительно указывает на то, что в правильном подходе есть оператор if, а не try /catch.

Чтобы ответить на ваш вопрос, я бы предположил, что обычно в try / catch накладных расходов больше, чем if.Чтобы узнать наверняка, получите профилировщик Java и выясните, какой именно код вам нужен.Возможно, что ответ может отличаться в зависимости от ситуации.

33 голосов
/ 24 декабря 2011

На этот вопрос почти "ответили до смерти", но я думаю, что есть еще несколько моментов, которые можно было бы с пользой сделать:

  • Использование try / catch для неисключительного потока управления - плохой стиль (в Java). (Часто идут споры о том, что означает «не исключение» ... но это другая тема.)

  • Отчасти причина того, что это плохой стиль, заключается в том, что try / catch на порядков дороже, чем обычный оператор потока управления 1 . Фактическая разница зависит от программы и платформы, но я ожидаю, что она будет в 1000 и более раз дороже. Помимо прочего, объект исключения creation захватывает трассировку стека, просматривая и копируя информацию о каждом кадре в стеке. Чем глубже стек, тем больше его нужно скопировать.

  • Другая причина плохого стиля в том, что код труднее читать.

1 - JIT в последних версиях Java 7 может оптимизировать обработку исключений, чтобы в некоторых случаях значительно сократить накладные расходы. Однако по умолчанию эти оптимизации не включены.

Есть также проблемы с тем, как вы написали пример:

  • Поймать Exception - очень плохая практика, потому что есть шанс, что вы случайно поймаете другие непроверенные исключения. Например, если вы сделаете это вокруг вызова raw.substring(1), вы также поймаете потенциальные ошибки StringIndexOutOfBoundsException s ... и hide .

  • То, что пытается сделать ваш пример, (вероятно) является результатом плохой практики работы со строками null. Как правило, вы должны попытаться свести к минимуму использование null строк и попытаться ограничить их (преднамеренное) распространение. Где возможно, используйте пустую строку вместо null, чтобы обозначать «нет значения». И когда у вас есть случай, когда вам нужно передать или вернуть строку null, четко документируйте ее в своем методе javadocs. Если ваши методы вызываются с null, когда они не должны ... это ошибка. Пусть это исключение. Не пытайтесь компенсировать ошибку, возвращая (в этом примере) null.


Followup

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

... и большинство пунктов в моем ответе не о нулевых значениях!

Но имейте в виду, что во многих случаях вы хотите разрешить случайное нулевое значение или любое другое значение, которое может вызвать исключение, и просто игнорировать их. Это имеет место, например, при чтении значений ключа / пары откуда-то и передаче их методу, подобному tryTrim () выше.

Да, есть ситуации, когда ожидаются значения null, и вам нужно разобраться с ними.

Но я бы сказал, что то, что делает tryTrim() (обычно) - неправильный способ справиться с null. Сравните эти три бита кода:

// Version 1
String param = httpRequest.getParameter("foo");
String trimmed = tryTrim(param);
if (trimmed == null) {
    // deal with case of 'foo' parameter absent
} else {
    // deal with case of 'foo' parameter present
}

// Version 2
String param = httpRequest.getParameter("foo");
if (param == null) {
    // deal with case of 'foo' parameter absent
} else {
    String trimmed = param.trim();
    // deal with case of 'foo' parameter present
}

// Version 3
String param = httpRequest.getParameter("foo");
if (param == null) {
    // treat missing and empty parameters the same
    param = "";
}
String trimmed = param.trim();

В конечном итоге вы должны иметь дело с null не так, как с обычной строкой, и обычно обычно - хорошая идея сделать это как можно скорее. Чем дальше разрешено распространение null от его точечного источника, тем более вероятно, что программист забудет , что значение null является возможным, и напишет ошибочный код, который предполагает не нулевое значение И забывать, что параметр HTTP-запроса может отсутствовать (т.е. param == null), это классический случай, когда это происходит.

Я не говорю, что tryTrim() изначально плох. Но тот факт, что программист чувствует необходимость в написании таких методов, как , вероятно, , свидетельствует о неоптимальной обработке нуля.

10 голосов
/ 24 декабря 2011

Используйте вторую версию.Никогда не используйте исключения для потока управления, когда доступны другие альтернативы, поскольку это не то, для чего они существуют.Исключения составляют исключительные обстоятельства.

Находясь в теме, не ловите Exception здесь, и особенно не глотайте его.В вашем случае вы ожидаете NullPointerException.Если бы вы поймали что-то, это то, что вы поймали (, но вернитесь к первому пункту, сделайте , а не , сделайте это ).Когда вы ловите (и глотаете!) Exception, вы говорите: «Что бы ни случилось, я справлюсь. Мне все равно, что это».Ваша программа может быть в безотзывном состоянии!Поймайте только то, с чем вы готовы иметь дело, позвольте всему остальному распространяться на слой, с которым может справиться, даже если этот слой является верхним слоем, и все, что он делает, это регистрирует исключение, а затем нажимает кнопку извлеченияпереключатель.

4 голосов
/ 24 декабря 2011

В противном случае исключения быстро генерируются и перехватываются (хотя if, вероятно, все еще быстрее), но медленная задача - создать трассировку стека исключения, поскольку ему необходимо пройти через весь текущий стек. (Как правило, использовать исключения для потока управления плохо, но когда это действительно необходимо и исключения должны быть быстрыми, можно пропустить построение трассировки стека, переопределив метод Throwable.fillInStackTrace(), или сохранить один экземпляр исключения и выбросить его. несколько раз вместо того, чтобы всегда создавать новый экземпляр исключения.)

0 голосов
/ 24 декабря 2011

Что касается накладных расходов, вы можете проверить сами:

public class Overhead {

public static void main(String[] args) {
    String testString = "";

    long startTimeTry = System.nanoTime();
    tryTrim(testString);
    long estimatedTimeTry = System.nanoTime() - startTimeTry;

    long startTimeIf = System.nanoTime();
    ifTrim(testString);
    long estimatedTimeIf = System.nanoTime() - startTimeIf;

    System.out.println("Try time:" + estimatedTimeTry);
    System.out.println("If time:" + estimatedTimeIf);

}

public static String tryTrim(String raw) {
    try {
        return raw.trim();
    } catch (Exception e) {
    }
    return null;
}

public static String ifTrim(String raw) {
    if (raw == null) {
        return null;
    }
    return raw.trim();
}

}

Полученные мной цифры:

Try time:8102
If time:1956

НасколькоСтиль - это целый отдельный вопрос.Оператор if выглядит довольно естественным, но попытка выглядит действительно странно по нескольким причинам: - вы поймали Exception, хотя вы проверяете значение NULL, ожидаете ли вы чего-то «исключительного» (иначе перехватите NullException)?- Вы поймали это исключение, вы собираетесь сообщить об этом или проглотить?и т. д. и т. д.

Редактировать: см. мой комментарий, почему это недопустимый тест, но я действительно не хотел оставлять это положение здесь.Просто поменяв местами tryTrim и ifTrim, мы неожиданно получим следующие результаты (на моей машине):

Try time:2043
If time:6810

Вместо того, чтобы начать объяснять все это, просто прочитайте this для начала -У Клиффа также есть несколько отличных слайдов по всей теме, но я пока не могу найти ссылку.

Зная, как работает обработка исключений в Hotspot, я вполне уверен, что в правильном тесте try / catchбез исключения) будет базовой производительностью (потому что нет никаких накладных расходов), но JIT может сыграть некоторые трюки с проверками Nullpointer, сопровождаемыми вызовами методов (без явной проверки, но перехватывать исключение hw, если объект равен нулю), в этом случаемы получили бы тот же результат.Также не забывайте: мы говорим о разнице одного легко предсказуемого, если бы это был ОДИН цикл ЦП!Стоимость обрезки будет стоить в миллион раз больше.

...