2011-12-24 2 views
47

Есть ли накладные расходы на Java для использования TRY/поймать блок-, в отличии от , если блок (при условии, что приложенный код в противном случае не требует так)?Java, если против попытаться/поймать накладные

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

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() подход для упрощения компоновки коды, особенно, когда многие , если блоки проверки редких условий ошибки можно избежать, заключив код в одну попытке/поймать блок ?

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

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

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

ответ

52

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

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

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

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

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

+0

Правильно, и, как указывает ответ Сергея, есть еще больше накладных расходов, даже если исключение никогда не выбрасывается. Благодаря! – PNS

+3

@PNS Неправда, сергей просто не измеряет, что вы и сергей думаете, что он измеряет (о, мне нравится это предложение, извините, не смог удержаться;)). Накладных расходов нет, если вы не поймаете исключение в блоке catch. Но тогда проверка Nullpointer со стороны JIT, как правило, бесплатна (не делайте явной проверки и вместо этого поймайте исключение hw), так что это просто зависит от того, достаточно ли JIT достаточно для оптимизации в этой ситуации. Не малейшая идея, но в любом случае разница измеряется в тактовых циклах - абсолютно неинтересная. – Voo

+0

Я редактировал этот комментарий, когда вы добавили свою. Итак, вы считали бы, что разница равна нулю, если не исключено исключение (или просто исключение NullPointerException), по крайней мере, в Sun JDK? – PNS

9

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

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

+0

+1 для определения «исключения». Это действительно очень плохая практика. –

+0

Я хотел бы сыграть адвоката дьявола в том, чтобы поймать Исключение и занести его в журнал и развить его. Это может быть необходимо, если, например, вы находитесь где-то в коде, и происходит непредвиденное исключение, вы можете его поймать, занести в журнал значимую информацию о состоянии, которая не будет доступна в простой трассе стека, а затем перестроить ее на верхние уровни до иметь дело с. – slashdottir

4

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

-3

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

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 выглядит довольно естественно, но попытка выглядит очень странно по нескольким причинам: - вы поймали Исключение, даже если вы проверяете значение NULL, ожидаете ли вы чего-то «исключительного» (иначе поймите NullException)? - ты поймал это Исключение, ты собираешься сообщить об этом или проглотить? и т. Д. И т. Д. И т. Д.

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

Try time:2043 
If time:6810 

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

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

+4

Надеюсь, никто здесь не предполагает, что тест действительно? У нас проблемы с загрузкой классов, неточности таймера (ваша точность по умолчанию для таймеров составляет 10-15 мс, теперь просто подумайте в течение одной секунды о том, что это говорит нам), проверьте код без кода и так далее. Микро-тесты на Java трудно получить правильно, но это даже не имеет смысла на компилированном языке ... – Voo

+0

Я читал в некоторых других сообщениях, которые try/catch имеет о тех же накладных расходах, как если бы при условии, что исключение не было но, видимо, это не так, не говоря уже о «правильности шаблонов». Разумеется, значения времени, измеренные в тесте, во многом зависят от времени установки компилятора для двух разных блоков, что обычно может быть незначительным по времени выполнения вложенного кода в более реальных сценариях, но оно все еще имеет значение. – PNS

+1

@PNS Просто замените блоки if и try и внезапно попробуйте в 4 раза быстрее, чем если бы я .. – Voo

28

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

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

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

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

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

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

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

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


Followup

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

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

Но, пожалуйста, помните, что есть много случаев, когда вы хотите разрешить случайное значение null или любое другое значение, которое может вызвать исключение, и просто игнорировать их. Это происходит, например, при чтении значений ключей/пар где-то и передаче их методу, например 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() по своей сути плохой. Но тот факт, что программист считает, что нужно написать такие методы написания, это , вероятно,, что указывает на то, что обработка нулевого значения не идеальна.

+0

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

+0

Ваши последующие комментарии, конечно, имеют смысл, поскольку все остальные указали на проблему правильности шаблона tryTrim() и спасибо за дальнейшее расширение аргумента. Я отредактировал вопрос, чтобы представить случай, когда tryTrim() может показаться привлекательным (хотя и нетрадиционным). – PNS

+0

Старый ответ, но все же релевантный. Чтобы взглянуть на вещи, я хотел бы отметить, что «превращение» нулевых строк в пустые строки для этой цели можно фактически рассматривать как проявление [нулевого шаблона объекта] (https://en.wikipedia.org/ вики/Null_Object_pattern). Это интересно, потому что теперь у вас есть более общие рекомендации, чтобы решить, когда их использовать, а когда нет. – tne

Смежные вопросы