Следует ли trycatch входить в цикл или вне его?

У меня есть цикл, который выглядит примерно так:

for (int i = 0; i < max; i++) {
    String myString = ...;
    float myNum = Float.parseFloat(myString);
    myFloats[i] = myNum;
}

Это основное содержимое метода, единственная цель которого - вернуть массив чисел с плавающей запятой. Я хочу, чтобы этот метод возвращал null в случае ошибки, поэтому я помещаю цикл в блок try...catch, например:

try {
    for (int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    }
} catch (NumberFormatException ex) {
    return null;
}

Но потом я также подумал о том, чтобы поместить блок try...catch внутрь цикла, вот так:

for (int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
    } catch (NumberFormatException ex) {
        return null;
    }
    myFloats[i] = myNum;
}

Есть ли какая-то причина, по производительности или как-то иначе, предпочесть одно другому?


Изменить. Похоже, что все согласны с тем, что проще поместить цикл внутри метода try / catch, возможно, внутри его собственного метода. Однако до сих пор ведутся споры о том, что быстрее. Может ли кто-нибудь проверить это и дать единый ответ?


person Michael Myers    schedule 26.09.2008    source источник
comment
Я не знаю о производительности, но код выглядит чище с try catch вне цикла for.   -  person Jack B Nimble    schedule 26.09.2008


Ответы (21)


ПРЕДСТАВЛЕНИЕ:

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

Вот ссылка: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

Таблица описана примерно наполовину.

person Jeffrey L Whitledge    schedule 26.09.2008
comment
Итак, вы говорите, что нет никакой разницы, кроме эстетики. Вы можете ссылаться на источник? - person Michael Myers; 27.09.2008
comment
Спасибо. Я полагаю, что с 1997 года все могло измениться, но вряд ли это сделало бы его менее эффективным. - person Michael Myers; 27.09.2008
comment
Абсолютно жесткое слово. В некоторых случаях это может препятствовать оптимизации компилятора. Это не прямые затраты, но это может быть косвенное снижение производительности. - person Tom Hawtin - tackline; 27.09.2008
comment
Да, наверное, мне следовало немного повлиять на свой энтузиазм, но дезинформация действовала мне на нервы! В случае оптимизации, поскольку мы не можем знать, каким образом это повлияет на производительность, я думаю, что вернулся, чтобы попробовать-и-протестировать (как всегда). - person Jeffrey L Whitledge; 27.09.2008
comment
разницы в производительности нет, только если код работает без исключений. - person Onur Bıyık; 03.02.2010

Производительность: как сказал в своем ответе Джеффри, в Java это не имеет большого значения.

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

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

Третий способ: вы всегда можете написать свой собственный статический метод ParseFloat и иметь дело с обработкой исключений в этом методе, а не в вашем цикле. Сделать обработку исключений изолированной от самого цикла!

class Parsing
{
    public static Float MyParseFloat(string inputValue)
    {
        try
        {
            return Float.parseFloat(inputValue);
        }
        catch ( NumberFormatException e )
        {
            return null;
        }
    }

    // ....  your code
    for(int i = 0; i < max; i++) 
    {
        String myString = ...;
        Float myNum = Parsing.MyParseFloat(myString);
        if ( myNum == null ) return;
        myFloats[i] = (float) myNum;
    }
}
person Ray Hayes    schedule 26.09.2008
comment
Присмотрись. Он уходит по первому исключению в обоих. Я тоже сначала подумал о том же. - person kervin; 26.09.2008
comment
Я знал, вот почему я сказал в его случае, поскольку он возвращается, когда он сталкивается с проблемой, я бы использовал внешний захват. Для ясности, это правило, которое я бы использовал ... - person Ray Hayes; 27.09.2008
comment
Хороший код, но, к сожалению, Java не имеет возможности использовать наши параметры. - person Michael Myers; 27.09.2008
comment
ByRef? Я так давно не прикасался к Java! - person Ray Hayes; 27.09.2008
comment
Нет, практически единственный способ сделать это - обернуть float как свойство объекта. Затем внутри ParseFloat вы должны установить outputValue.myFloat вместо того, чтобы напрямую устанавливать outputValue. Но спешить становится некрасиво. - person Michael Myers; 27.09.2008
comment
Изменен на использование типа Float, который имеет либо значение NULL, либо значение с плавающей запятой, которое вы хотите! - person Ray Hayes; 27.09.2008
comment
Ах, неплохая идея. Но мы как бы уходим от вопроса, не так ли? Хотя, думаю, это делает сам цикл красивее. - person Michael Myers; 27.09.2008
comment
Кто-то еще предложил TryParse, который в C # /. Net позволяет выполнять безопасный синтаксический анализ, не имея дело с исключениями, которые теперь реплицируются, и метод можно использовать повторно. Что касается исходного вопроса, я бы предпочел цикл внутри улова, он просто выглядит чище, даже если нет проблем с производительностью. - person Ray Hayes; 27.09.2008

Хорошо, после того как Джеффри Л. Уитледж сказал, что не было разницы в производительности (по состоянию на 1997 год), я пошел и протестировал. Я провел этот небольшой тест:

public class Main {

    private static final int NUM_TESTS = 100;
    private static int ITERATIONS = 1000000;
    // time counters
    private static long inTime = 0L;
    private static long aroundTime = 0L;

    public static void main(String[] args) {
        for (int i = 0; i < NUM_TESTS; i++) {
            test();
            ITERATIONS += 1; // so the tests don't always return the same number
        }
        System.out.println("Inside loop: " + (inTime/1000000.0) + " ms.");
        System.out.println("Around loop: " + (aroundTime/1000000.0) + " ms.");
    }
    public static void test() {
        aroundTime += testAround();
        inTime += testIn();
    }
    public static long testIn() {
        long start = System.nanoTime();
        Integer i = tryInLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static long testAround() {
        long start = System.nanoTime();
        Integer i = tryAroundLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static Integer tryInLoop() {
        int count = 0;
        for (int i = 0; i < ITERATIONS; i++) {
            try {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            } catch (NumberFormatException ex) {
                return null;
            }
        }
        return count;
    }
    public static Integer tryAroundLoop() {
        int count = 0;
        try {
            for (int i = 0; i < ITERATIONS; i++) {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            }
            return count;
        } catch (NumberFormatException ex) {
            return null;
        }
    }
}

Я проверил полученный байт-код с помощью javap, чтобы убедиться, что ничего не встроено.

Результаты показали, что, предполагая незначительную оптимизацию JIT, Джеффри прав; нет абсолютно разницы в производительности на Java 6, клиентской виртуальной машине Sun (у меня не было доступа к другим версиям). Общая разница во времени составляет порядка нескольких миллисекунд за весь тест.

Поэтому единственное, что нужно учитывать - это то, что выглядит наиболее чистым. Я считаю, что второй способ уродлив, поэтому я буду придерживаться либо первого способа, либо Путь Рэя Хейса.

person Michael Myers    schedule 29.09.2008
comment
Если обработчик исключений принимает поток за пределы цикла, то я считаю, что лучше всего разместить его вне цикла - вместо того, чтобы помещать предложение catch, очевидно, внутри цикла, которое, тем не менее, возвращается. Я использую строку пробелов вокруг перехваченного тела таких универсальных методов, чтобы более четко отделить body от охватывающего всех блока try. - person Thomas W; 18.10.2013

Хотя производительность может быть такой же, а то, что «выглядит» лучше, очень субъективно, все же существует довольно большая разница в функциональности. Возьмем следующий пример:

Integer j = 0;
    try {
        while (true) {
            ++j;

            if (j == 20) { throw new Exception(); }
            if (j%4 == 0) { System.out.println(j); }
            if (j == 40) { break; }
        }
    } catch (Exception e) {
        System.out.println("in catch block");
    }

Цикл while находится внутри блока try catch, переменная j увеличивается до 40, распечатывается, когда j mod 4 равен нулю, и возникает исключение, когда j достигает 20.

Прежде чем приступить к подробностям, приведу другой пример:

Integer i = 0;
    while (true) {
        try {
            ++i;

            if (i == 20) { throw new Exception(); }
            if (i%4 == 0) { System.out.println(i); }
            if (i == 40) { break; }

        } catch (Exception e) { System.out.println("in catch block"); }
    }

Та же логика, что и выше, с той лишь разницей, что блок try / catch теперь находится внутри цикла while.

Вот результат (в режиме try / catch):

4
8
12 
16
in catch block

И другой вывод (попробуйте / поймайте в то время):

4
8
12
16
in catch block
24
28
32
36
40

Вот вам и довольно существенная разница:

в то время как в try / catch выходит из цикла

попробуйте / поймайте, пока цикл остается активным

person seBaka28    schedule 30.10.2015
comment
Поэтому поместите try{} catch() {} вокруг цикла, если цикл рассматривается как единый блок и обнаружение проблемы в этом блоке заставляет весь блок (или цикл в данном случае) двигаться на юг. Поместите try{} catch() {} внутри цикла, если вы обрабатываете одну итерацию цикла или отдельный элемент в массиве как единое целое. Если в этом модуле возникает исключение, мы просто пропускаем этот элемент и переходим к следующей итерации цикла. - person Galaxy; 16.10.2018

Я согласен со всеми сообщениями о производительности и удобочитаемости. Однако есть случаи, когда это действительно имеет значение. Еще пара людей упомянула об этом, но это может быть легче увидеть на примерах.

Рассмотрим этот слегка измененный пример:

public static void main(String[] args) {
    String[] myNumberStrings = new String[] {"1.2345", "asdf", "2.3456"};
    ArrayList asNumbers = parseAll(myNumberStrings);
}

public static ArrayList parseAll(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        myFloats.add(new Float(numberStrings[i]));
    }
    return myFloats;
}

Если вы хотите, чтобы метод parseAll () возвращал null, если есть какие-либо ошибки (как в исходном примере), вы бы поместили try / catch снаружи следующим образом:

public static ArrayList parseAll1(String[] numberStrings){
    ArrayList myFloats = new ArrayList();
    try{
        for(int i = 0; i < numberStrings.length; i++){
            myFloats.add(new Float(numberStrings[i]));
        }
    } catch (NumberFormatException nfe){
        //fail on any error
        return null;
    }
    return myFloats;
}

На самом деле вам, вероятно, следует вернуть здесь ошибку вместо null, и, как правило, мне не нравится многократный возврат, но вы поняли идею.

С другой стороны, если вы хотите, чтобы он просто игнорировал проблемы и анализировал любые строки, которые он мог бы, вы бы поместили try / catch внутри цикла следующим образом:

public static ArrayList parseAll2(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        try{
            myFloats.add(new Float(numberStrings[i]));
        } catch (NumberFormatException nfe){
            //don't add just this one
        }
    }

    return myFloats;
}
person Matt N    schedule 30.09.2008
comment
Вот почему я сказал, что если вы просто хотите поймать неверное значение, но продолжить обработку, поместите его внутрь. - person Ray Hayes; 01.10.2008

Как уже было сказано, производительность такая же. Однако пользовательский опыт не обязательно идентичен. В первом случае вы быстро потерпите неудачу (т.е. после первой ошибки), однако, если вы поместите блок try / catch внутри цикла, вы сможете зафиксировать все ошибки, которые будут созданы для данного вызова метода. При синтаксическом разборе массива значений из строк, где вы ожидаете некоторых ошибок форматирования, определенно есть случаи, когда вы хотите иметь возможность представить все ошибки пользователю, чтобы им не нужно было пытаться исправить их одну за другой. .

person user19810    schedule 13.02.2009
comment
Это неверно для данного конкретного случая (я возвращаюсь в блоке catch), но в целом хорошо иметь в виду. - person Michael Myers; 13.02.2009

Если это не удается, то первый формат имеет смысл. Если вы хотите иметь возможность обрабатывать / возвращать все исправные элементы, вам необходимо использовать вторую форму. Это были бы мои основные критерии выбора между методами. Лично я бы не стал использовать вторую форму, если это все или ничего.

person Joe Skora    schedule 26.09.2008

Пока вы знаете, что вам нужно выполнить в цикле, вы можете поместить try catch за пределы цикла. Но важно понимать, что цикл завершится, как только возникнет исключение, и это может не всегда быть тем, что вам нужно. На самом деле это очень распространенная ошибка в программном обеспечении на основе Java. Людям необходимо обработать ряд элементов, например очистить очередь, и ложно полагаться на внешний оператор try / catch, обрабатывающий все возможные исключения. Они также могут обрабатывать только конкретное исключение внутри цикла и не ожидать возникновения каких-либо других исключений. Затем, если возникает исключение, которое не обрабатывается внутри цикла, цикл будет «вытеснен», он, возможно, завершится преждевременно, и внешний оператор catch обработает исключение.

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

person Gunnar Forsgren - Mobimation    schedule 31.08.2012

В ваших примерах нет функциональной разницы. Я считаю ваш первый пример более читабельным.

person Jamie    schedule 26.09.2008

Вы должны предпочесть внешнюю версию внутренней версии. Это просто конкретная версия правила: перемещайте все, что находится за пределами цикла, что вы можете переместить за пределы цикла. В зависимости от компилятора IL и JIT ваши две версии могут иметь или не иметь разные характеристики производительности.

С другой стороны, вам, вероятно, следует посмотреть на float.TryParse или Convert.ToFloat.

person Orion Adrian    schedule 26.09.2008

Если вы поместите команду try / catch внутрь цикла, вы продолжите цикл после исключения. Если вы поместите его за пределы цикла, вы остановитесь, как только возникнет исключение.

person Joel Coehoorn    schedule 26.09.2008
comment
Что ж, я возвращаю null при исключении, поэтому в моем случае обработка не выполняется. - person Michael Myers; 27.09.2008
comment
По крайней мере, в python это зависит от того, что возвращает исключение. Если исключение вызывает ненулевой выход, оно выйдет из цикла. - person Vijay Vat; 04.03.2021

Я считаю, что блоки try / catch необходимы для обеспечения правильной обработки исключений, но создание таких блоков влияет на производительность. Поскольку циклы содержат интенсивные повторяющиеся вычисления, не рекомендуется помещать блоки try / catch внутри циклов. Кроме того, кажется, что это условие часто перехватывается «Exception» или «RuntimeException». Следует избегать попадания RuntimeException в код. Опять же, если вы работаете в большой компании, важно правильно зарегистрировать это исключение или остановить возникновение исключения времени выполнения. Весь смысл этого описания PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS

person surendrapanday    schedule 13.02.2019

установка специального кадра стека для try / catch добавляет дополнительные накладные расходы, но JVM может обнаружить факт возврата и оптимизировать его.

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

Однако я согласен с другими в том, что если он находится вне цикла, тело цикла выглядит чище.

Если есть вероятность, что вы когда-нибудь захотите продолжить обработку, а не выйти, если есть недопустимый номер, тогда вам нужно, чтобы код находился внутри цикла.

person Matt    schedule 26.09.2008

Если он внутри, то вы получите накладные расходы на структуру try / catch N раз, а не только один раз снаружи.


Каждый раз, когда вызывается структура Try / Catch, это увеличивает накладные расходы на выполнение метода. Просто немного тиков памяти и процессора, необходимых для работы со структурой. Если вы запускаете цикл 100 раз, и предположим, что стоимость составляет 1 тик на вызов try / catch, тогда использование Try / Catch внутри цикла будет стоить вам 100 тиков, а не только 1 тик, если он вне цикла.

person Stephen Wrighton    schedule 26.09.2008
comment
Не могли бы вы уточнить накладные расходы? - person Michael Myers; 27.09.2008
comment
Хорошо, но Джеффри Л. Уитледж говорит, что дополнительных накладных расходов нет. Можете ли вы предоставить источник, который говорит, что есть? - person Michael Myers; 27.09.2008
comment
Стоимость небольшая, почти ничтожная, но она есть - у всего есть своя цена. Вот статья в блоге Programmer's Heaven (tiny.cc/ZBX1b) о .NET и сообщение группы новостей от Решата Sabiq относительно JAVA (tiny.cc/BV2hS) - person Stephen Wrighton; 27.09.2008
comment
Понятно ... Итак, теперь вопрос в том, являются ли затраты, понесенные при входе в try / catch (в этом случае они должны быть вне цикла), или они постоянны на время выполнения try / catch (в этом случае это должен быть внутри цикла)? Вы говорите, что это №1. И я до сих пор не совсем уверен, что есть цена. - person Michael Myers; 27.09.2008
comment
Согласно этой книге Google (tinyurl.com/3pp7qj) на последних виртуальных машинах штрафов нет. - person Michael Myers; 27.09.2008
comment
Приведенная выше ссылка на Java не указывает, является ли снижение производительности результатом вызова метода с обработкой исключений или выполнения цикла с обработкой исключений. Вызов метода имеет снижение производительности, которое будет одинаковым в обоих сценариях, указанных в вопросе. - person Jeffrey L Whitledge; 27.09.2008

Весь смысл исключений состоит в том, чтобы поощрять первый стиль: позволить объединить и обработать обработку ошибок один раз, а не сразу на каждом возможном месте ошибки.

person wnoise    schedule 26.09.2008
comment
Неправильный! Смысл исключений - определить, какое исключительное поведение следует применять при возникновении ошибки. Таким образом, выбор внутреннего или внешнего блока try / catch действительно зависит от того, как вы хотите обрабатывать цикл. - person gizmo; 27.09.2008
comment
То же самое можно сделать и с обработкой ошибок до исключения, например с передачей флагов ошибки. - person wnoise; 28.09.2008

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

Рассмотреть возможность:

try {
   // parse
} catch (NumberFormatException nfe){
   throw new RuntimeException("Could not parse as a Float: [" + myString + 
                              "] found at index: " + i, nfe);
} 

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

person Kyle Dyer    schedule 01.10.2008
comment
Да, это тоже верный момент. В моем случае я читаю из файла, поэтому все, что мне действительно нужно, это строка, которую не удалось проанализировать. Поэтому я сделал System.out.println (ex) вместо того, чтобы генерировать другое исключение (я хочу проанализировать как можно больше файла). - person Michael Myers; 01.10.2008

Я хотел бы добавить свой собственный 0.02c о двух конкурирующих соображениях при рассмотрении общей проблемы размещения обработки исключений:

  1. «Более широкая» ответственность блока try-catch (т.е. вне цикла в вашем случае) означает, что при изменении кода в какой-то более поздний момент вы можете по ошибке добавить строку, которая обрабатывается вашим существующим блоком catch; возможно непреднамеренно. В вашем случае это менее вероятно, потому что вы явно ловите NumberFormatException

  2. Чем «уже» ответственность блока try-catch, тем сложнее становится рефакторинг. В частности, когда (как в вашем случае) вы выполняете «нелокальную» инструкцию из блока catch (оператор return null).

person oxbow_lakes    schedule 05.10.2008

Это зависит от обработки сбоя. Если вы просто хотите пропустить элементы ошибки, попробуйте внутри:

for(int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    } catch (NumberFormatException ex) {
        --i;
    }
}

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

person Arne Burmeister    schedule 07.10.2008

Я вложу свои 0,02 доллара. Иногда вам нужно добавить «наконец» позже в свой код (потому что кто-нибудь когда-либо писал свой код идеально с первого раза?). В таких случаях внезапно становится более целесообразным иметь try / catch вне цикла. Например:

try {
    for(int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        dbConnection.update("MY_FLOATS","INDEX",i,"VALUE",myNum);
    }
} catch (NumberFormatException ex) {
    return null;
} finally {
    dbConnection.release();  // Always release DB connection, even if transaction fails.
}

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

person Ogre Psalm33    schedule 09.10.2008

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

Если метод external () вызывает метод inner () (который может вызывать сам себя рекурсивно), попытайтесь найти try-catch в методе external (), если это возможно. Простой пример «сбоя стека», который мы используем в классе производительности, дает сбой примерно на 6400 кадрах, когда try-catch находится во внутреннем методе, и примерно на 11 600 кадрах, когда он находится во внешнем методе.

В реальном мире это может быть проблемой, если вы используете шаблон Composite и имеете большие сложные вложенные структуры.

person Jeff Hill    schedule 11.03.2010

Если вы хотите перехватить исключение для каждой итерации или проверить, на какой итерации выбрано исключение, и перехватить все исключения в итерации, поместите try ... catch внутри цикла. Это не прервет цикл, если возникнет исключение, и вы сможете перехватить каждое исключение в каждой итерации цикла.

Если вы хотите разорвать цикл и исследовать исключение при каждом вызове, используйте try ... catch out of the loop. Это прервет цикл и выполнит операторы после перехвата (если есть).

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

person Juned Khan Momin    schedule 23.12.2018