Denemek gerekir ... yakalamak bir döngü içine veya dışına gitmek?


185

Ben böyle bir şey görünüyor bir döngü var:

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

Bu, tek amacı yüzen diziyi döndürmek olan bir yöntemin ana içeriğidir. nullBir hata varsa bu yöntemi döndürmek istiyorum , bu yüzden döngü şöyle bir try...catchblok içine koymak :

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

Ama sonra try...catchbloğu döngü içine koymayı da düşündüm, şöyle:

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

Birini diğerine tercih etmek için herhangi bir neden, performans veya başka bir neden var mı?


Düzenleme: Fikir birliği, muhtemelen kendi yöntemi içinde try / catch içine döngü koymak daha temiz gibi görünüyor. Ancak, hala daha hızlı olan tartışmalar var. Birisi bunu test edebilir ve birleşik bir cevapla geri gelebilir mi?


2
Performans hakkında bilmiyorum, ancak kod for döngüsü dışında try catch ile daha temiz görünüyor.
Jack B Nimble

Yanıtlar:


132

VERİM:

Deneme / yakalama yapılarının nereye yerleştirildiği konusunda hiçbir performans farkı yoktur. Dahili olarak, yöntem çağrıldığında oluşturulan bir yapıda kod aralığı tablosu olarak uygulanırlar. Yöntem yürütülürken, bir atış olmadıkça try / catch yapıları tamamen resim dışındadır, ardından hatanın yeri tablo ile karşılaştırılır.

İşte bir referans: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

Tablo yaklaşık yarı yolda açıklanmaktadır.


Tamam, yani estetik dışında bir fark olmadığını söylüyorsun. Bir kaynağa bağlanabilir misiniz?
Michael Myers

2
Teşekkürler. Sanırım 1997'den bu yana bir şeyler değişmiş olabilir, ama daha az verimli hale getiremezlerdi.
Michael Myers

1
Kesinlikle zor bir kelime. Bazı durumlarda derleyici optimizasyonunu engelleyebilir. Doğrudan bir maliyet değil, ancak dolaylı bir performans cezası olabilir.
Tom Hawtin - tackline

2
Doğru, sanırım coşkumda biraz hüküm sürmüş olmalıydım, ama yanlış bilgi sinirlerime giriyordu! Optimizasyonlarda, performansın hangi yoldan etkileneceğini bilemediğimiz için, sanırım denemeye ve test etmeye geri döndük (her zaman olduğu gibi).
Jeffrey L Whitledge

1
performans farkı yoktur, ancak kod istisnasız çalışırsa.
Onur Bıyık

72

Performans : Jeffrey yanıtında söylediği gibi , Java'da çok fazla fark yaratmıyor.

genellikle , kodun okunabilirliği için, istisnayı nerede yakalayacağınız seçimi, döngünün işlemeye devam etmesini isteyip istemediğinize bağlıdır.

Örneğinizde bir istisna yakaladıktan sonra geri döndünüz. Bu durumda, try / catch'i döngünün etrafına koyardım. Sadece kötü bir değer yakalamak istiyorsanız, ancak işlemeye devam ediyorsanız, içine koyun.

Üçüncü yol : Her zaman kendi statik ParseFloat yönteminizi yazabilir ve özel durum işlemeyi döngünüz yerine bu yöntemde ele alabilirsiniz. Özel durum işlemeyi döngünün kendisine izole etmek!

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;
    }
}

1
Yakından bak. Her ikisinde de ilk istisnadan çıkıyor. İlk başta de aynı şeyi düşündüm.
kervin

2
Bu yüzden onun durumunda dedim, çünkü bir probleme çarptığında geri dönüyor, o zaman dışarıda bir yakalama kullanırdım. Açıklık için kullanacağım kural bu ...
Ray Hayes

Güzel kod, ama ne yazık ki Java dışarı parametreleri lüksü yok.
Michael Myers

ByRef? Java'ya dokunduğumdan beri çok uzun zaman oldu!
Ray Hayes

2
Birisi C # /. Net içinde istisnalarla uğraşmadan güvenli bir ayrıştırma yapmanıza izin veren TryParse'i önerdi, bu şimdi çoğaltılıyor ve yöntem yeniden kullanılabilir. Orijinal soruya gelince, yakalamanın içindeki döngüyü tercih ederim, performans sorunu olmasa bile daha temiz görünüyor ..
Ray Hayes

46

Tamam, Jeffrey L Whitledge (1997 itibariyle) hiçbir performans farkı olmadığını söyledikten sonra gittim ve test ettim. Bu küçük ölçütü çalıştırdım:

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;
        }
    }
}

Hiçbir şey satır içine var emin olmak için javap kullanarak sonuç bayt kodunu kontrol ettim.

Sonuçlar, önemsiz JIT optimizasyonları varsayarak, Jeffrey'in doğru olduğunu gösterdi ; Java 6, Sun istemci VM'sinde kesinlikle hiçbir performans farkı yoktur (diğer sürümlere erişimim yoktu). Toplam zaman farkı, tüm test boyunca birkaç milisaniye civarındadır.

Bu nedenle, tek şey en temiz görünen şeydir. İkinci yolun çirkin olduğunu düşünüyorum, bu yüzden ya ilk yola ya da Ray Hayes'in yoluna bağlı kalacağım .


İstisna-işleyici döngü dışında akış alırsa, o zaman ben bunu yerleştirmek için en net hissediyorum dışında döngü - Aksine görünüşte döngü, yine de döner içerisinde bir yakalama maddesi yerleştirerek daha. Ben daha net ayırmak için, örneğin "ucu açık" yöntemlerinin "yakalandı" vücudun etrafında boşluk bir satırını kullanın vücuda gelen her çevreleyen try'ın .
Thomas W

16

Performans aynı olabilir ve "daha iyi" görünen şey çok öznel olsa da, işlevsellikte hala oldukça büyük bir fark vardır. Aşağıdaki örneği alın:

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 döngüsü try catch bloğunun içindedir, 'j' değişkeni 40'a kadar artırılır, j mod 4 sıfır olduğunda yazdırılır ve j 20'ye vurulduğunda bir istisna atılır.

Herhangi bir ayrıntıdan önce, diğer örnek:

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"); }
    }

Yukarıdaki ile aynı mantık, tek fark try / catch bloğunun while döngüsü içinde olmasıdır.

İşte çıktı (try / catch sırasında):

4
8
12 
16
in catch block

Ve diğer çıktı (çalışırken / yakalamaya çalışın):

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

Orada oldukça önemli bir fark var:

denerken / yakalarken döngüden kopar

döngü etkin tutarken denemek / yakalamak


Bu yüzden try{} catch() {}, döngü tek bir "birim" olarak ele alınırsa ve bu birimde bir sorun bulunması tüm "birimi" (veya bu durumda döngü) güneye götürürse, döngünün etrafına koyun . try{} catch() {}Döngünün tek bir yinelemesini veya bir dizideki tek bir öğeyi bir bütün "birim" olarak ele alıyorsanız, döngünün içine koyun . Bu "birim" de bir istisna oluşursa, sadece o öğeyi atlarız ve daha sonra döngünün bir sonraki yinelemesine devam ederiz.
Galaxy

14

Tüm performans ve okunabilirlik yayınlarına katılıyorum. Bununla birlikte, gerçekten önemli olduğu durumlar vardır. Birkaç kişi bundan bahsetti, ancak örneklerle görmek daha kolay olabilir.

Bu biraz değiştirilmiş örneği düşünün:

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;
}

Herhangi bir hata varsa (orijinal örnek gibi) parseAll () yönteminin null değerini döndürmesini istiyorsanız, try / catch komutunu dışa şu şekilde koyabilirsiniz:

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;
}

Gerçekte, burada null yerine bir hata döndürmelisiniz ve genellikle birden fazla geri dönüşü sevmiyorum, ama fikri anlıyorsunuz.

Öte yandan, sadece sorunları görmezden gelmesini ve ne tür Dizeleri ayrıştırmasını istiyorsanız, denemeyi / yakalamayı döngünün içine şu şekilde koyacaksınız:

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;
}

2
Bu yüzden dedim ki, "Eğer sadece kötü bir değer yakalamak istiyorsanız, ancak işlemeye devam ediyorsanız, içine koyun."
Ray Hayes

5

Daha önce de belirtildiği gibi, performans aynı. Ancak, kullanıcı deneyimi aynı olmak zorunda değildir. İlk durumda, hızlı bir şekilde başarısız olursunuz (yani ilk hatadan sonra), ancak try / catch bloğunu döngünün içine koyarsanız, yönteme verilen bir çağrı için oluşturulacak tüm hataları yakalayabilirsiniz. Bazı biçimlendirme hataları beklediğiniz dizelerden bir değer dizisini ayrıştırırken, kesinlikle tüm hataları kullanıcıya sunabilmek istediğiniz durumlar vardır, böylece bunları tek tek denemeye ve düzeltmeye gerek kalmazlar. .


Bu, bu özel durum için doğru değil (catch bloğuna geri dönüyorum), ancak genel olarak akılda tutmak iyi bir şey.
Michael Myers

4

Eğer bir ya hep ya hiç başarısız olursa, ilk format mantıklıdır. Başarısız olmayan tüm öğeleri işleyebilmek / iade edebilmek istiyorsanız, ikinci formu kullanmanız gerekir. Bu yöntemler arasında seçim yapmak için temel kriterlerim olurdu. Şahsen, ya hep ya hiç değilse, ikinci formu kullanmam.


4

Döngüde neyi başarmanız gerektiğinin farkında olduğunuz sürece, try catch'i döngü dışına koyabilirsiniz. Ancak, istisnanın meydana geldiği anda döngünün sona ereceğini ve bu her zaman istediğiniz şey olmayabilir. Bu aslında Java tabanlı yazılımlarda çok yaygın bir hatadır. İnsanların bir kuyruğu boşaltmak gibi bir dizi öğeyi işlemesi ve tüm olası istisnaları işleyen bir dış try / catch deyimine yanlışlıkla güvenmesi gerekir. Ayrıca, döngü içinde yalnızca belirli bir özel durumu işliyor olabilirler ve başka bir özel durum oluşmasını beklemezler. Sonra döngü içinde işlenmeyen bir istisna oluşursa, döngü "önceden" olacaktır, muhtemelen erken biter ve dış catch deyimi istisnayı işler.

Döngü, bir kuyruğu boşaltmak için yaşamdaki rolü olarak olsaydı, o döngü gerçekten boşaltılmadan önce büyük olasılıkla sona erebilirdi. Çok yaygın hata.


2

Örneklerinizde fonksiyonel bir fark yoktur. İlk örneğinizi daha okunabilir buluyorum.


2

Dış versiyonu iç versiyona tercih etmelisiniz. Bu, kuralın yalnızca belirli bir sürümüdür, döngü dışına taşıyabileceğiniz her şeyi döngü dışına taşıyın. IL derleyicisine ve JIT derleyicisine bağlı olarak iki sürümünüz farklı performans özelliklerine sahip olabilir veya olmayabilir.

Başka bir not muhtemelen float.TryParse veya Convert.ToFloat'a bakmalısınız.


2

Try / catch'i döngü içine koyarsanız, bir istisnadan sonra döngü devam eder. Döngünün dışına koyarsanız, bir istisna atılır atmaz durursunuz.


İstisnada null döndürüyorum, bu yüzden benim durumumda işlemeye devam etmeyecekti.
Michael Myers

2

Benim bakış açım denemek / yakalamak blokları uygun istisna işleme sağlamak için gerekli olacaktır, ancak bu tür bloklar oluşturmak performans etkileri vardır. Döngüler yoğun tekrarlayan hesaplamalar içerdiğinden, döngülerin içine try / catch blokları koymanız önerilmez. Ayrıca, bu durumun ortaya çıktığı yerlerde, sık sık yakalanan "Exception" veya "RuntimeException" olur. Kodda yakalanan RuntimeException durumundan kaçınılmalıdır. Yine, eğer büyük bir şirkette çalışıyorsanız, bu istisnayı düzgün bir şekilde kaydetmeniz veya çalışma zamanı istisnasının gerçekleşmesini durdurmanız önemlidir. Bu açıklamanın amacıPLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS


1

try / catch için özel bir yığın çerçevesi ayarlamak ek yük getirir, ancak JVM geri döndüğünüzü tespit edebilir ve bunu optimize edebilir.

yineleme sayısına bağlı olarak, performans farkı muhtemelen önemsiz olacaktır.

Ancak ben döngü dışında olması döngü vücut daha temiz görünmesini yapmak diğerleri ile katılıyorum.

Geçersiz bir sayı varsa, çıkmak yerine işleme devam etmek istemeniz için bir şans varsa, kodun döngü içinde olmasını istersiniz.


1

İçerideyse, dışarıdaki bir kerenin aksine, denemek / yakalamak yapısının yükünü N kez kazanacaksınız.


Bir Try / Catch yapısı her çağrıldığında, yöntemin yürütülmesine ek yük ekler. Sadece biraz bellek ve işlemci keneleri yapısı ile başa çıkmak için gerekli. 100 kez bir döngü yürütüyorsanız ve varsayımsal aşk için, maliyetin deneme / yakalama çağrısı başına 1 kene olduğunu varsayalım, daha sonra döngü içinde Try / Catch'un size 100 keneye mal olması, sadece 1 kene yerine döngü dışında.


Havai yük üzerinde biraz ayrıntı verebilir misiniz?
Michael Myers

1
Tamam, ama Jeffrey L Whitledge fazladan bir yük olmadığını söylüyor . Var olduğunu söyleyen bir kaynak sağlayabilir misiniz?
Michael Myers

Maliyet küçük, neredeyse ihmal edilebilir, ama orada - her şeyin bir maliyeti vardır. İşte Programmer's Heaven'dan ( tiny.cc/ZBX1b ) .NET ile ilgili bir blog makalesi ve işte Reshat Sabiq'ten JAVA ( tiny.cc/BV2hS ) ile ilgili bir haber grubu yazısı
Stephen Wrighton

Anlıyorum ... Şimdi soru şu ki, try / catch'e girmenin maliyeti (bu durumda döngünün dışında olması gerekir) mi yoksa try / catch süresi boyunca sabit mi (bu durumda döngü içinde olmalıdır)? Diyelim ki # 1. Ve hala tamamen orada ikna olmadım olan bir maliyeti.
Michael Myers

1
Bu Google Kitaba ( tinyurl.com/3pp7qj ) göre, "en son VM'ler ceza vermiyor ".
Michael Myers

1

İstisnaların tümü, birinci stili teşvik etmektir: hata işlemenin, olası her hata sitesinde hemen değil, bir kez birleştirilmesine ve ele alınmasına izin vermek.


Yanlış! İstisnaların amacı, bir "hata" oluştuğunda hangi istisnai davranışın benimseneceğini belirlemektir. Bu nedenle, iç veya dış deneme / yakalama bloğunu seçmek gerçekten döngü tedavisini nasıl ele almak istediğinize bağlıdır.
gizmo

Bu, "hata oluştu" bayraklarını geçirme gibi istisna öncesi hata tedavileri ile eşit derecede iyi yapılabilir.
wnoise

1

içine koy. İşlemeye devam edebilirsiniz (isterseniz) veya istemciye myString'in değerini ve hatalı değeri içeren dizinin dizinini söyleyen yararlı bir istisna atabilirsiniz. NumberFormatException zaten kötü değeri söyleyeceğini düşünüyorum ama ilke, tüm yararlı verileri atmak istisnalar yerleştirmektir. Programın bu noktasında hata ayıklayıcıda sizin için neyin ilginç olacağını düşünün.

Düşünmek:

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

İhtiyaç duyduğunuzda, mümkün olduğunca fazla bilgi içeren böyle bir istisnayı gerçekten takdir edeceksiniz.


Evet, bu da geçerli bir nokta. Benim durumumda bir dosyadan okuyorum, bu yüzden gerçekten ihtiyacım olan tek şey ayrıştırılamayan dize. Bu yüzden başka bir istisna atmak yerine System.out.println (ex) yaptım (yasal olarak dosya kadar ayrıştırmak istiyorum).
Michael Myers

1

0.02cİstisna işlemeyi nerede konumlandıracağına dair genel soruna bakarken, iki rakip husus hakkında kendiminkini eklemek istiyorum :

  1. try-catchBloğun "daha geniş" sorumluluğu (yani, durumunuzdaki döngünün dışında), kodu daha sonraki bir noktada değiştirirken, mevcut catchbloğunuz tarafından işlenen bir satırı yanlışlıkla ekleyebileceğiniz anlamına gelir ; muhtemelen istemeden. Sizin durumunuzda, bu daha az olasıdır çünkü açık bir şekildeNumberFormatException

  2. try-catchBloğun sorumluluğu "dar" hale geldiğinde, yeniden düzenleme zorlaşır. Özellikle de (sizin durumunuzda olduğu gibi) catchblok içinde bir "yerel olmayan" talimat yürütürken ( return nullifade).


1

Bu, arıza yönetimine bağlıdır. Sadece hata öğelerini atlamak istiyorsanız, içini deneyin:

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

Başka bir durumda ben dışında denemek tercih ederim. Kod daha okunabilir, daha temiz. Belki de null döndürürse hata durumunda bir IllegalArgumentException özel durumunu atmak daha iyi olur.


1

Ben 0.02 $ benim koyacağım. Bazen kodunuzda daha sonra bir "nihayet" eklemek gerek rüzgar (çünkü kim ilk kez kodlarını mükemmel yazar?). Bu durumlarda, aniden denemenin / yakalamanın döngü dışında olması daha mantıklıdır. Örneğin:

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.
}

Çünkü bir hata alırsanız veya almazsanız, veritabanı bağlantınızı yalnızca bir kez serbest bırakmak (veya en sevdiğiniz kaynak türünü seçmek ...) istersiniz.


1

Yukarıda yer almayan diğer yönü her try-catch vardır gerçektir bazı özyinelemeli yöntemleri için etkileri olabilir istifi üzerinde etkisi.

"External ()" yöntemi "inner ()" yöntemini çağırırsa (bu özyinelemeli olarak kendini çağırabilir), mümkünse "outer ()" yönteminde try-catch'i bulmaya çalışın. Bir performans sınıfında kullandığımız basit bir "yığın çökmesi" örneği, try-catch iç yöntemdeyken yaklaşık 6.400 karede ve dış yöntemdeyken yaklaşık 11.600'de başarısız olur.

Gerçek dünyada, Kompozit deseni kullanıyorsanız ve büyük, karmaşık iç içe yapılara sahipseniz bu bir sorun olabilir.


0

Her bir yineleme için İstisna yakalamak veya ne yineleme İstisna atıldığını kontrol etmek ve bir yinelemedeki tüm İstisnaları yakalamak istiyorsanız, deneyin ... döngü içinde yakalayın. İstisna oluşursa bu döngü kesilmez ve döngü boyunca her bir yinelemede her İstisna yakalayabilirsiniz.

Döngüyü kırmak ve her atıldığında İstisna'yı incelemek istiyorsanız, try ... catch'i döngüden çıkarın. Bu, döngüyü kırar ve catch'ten sonra ifadeleri (varsa) yürütür.

Her şey ihtiyacınıza bağlıdır. İstisna oluşursa, sonuçlar belirsiz değildir ve döngü tamamen kırılmaz ve yürütülmez gibi konuşlandırırken try ... catch'i döngü içinde kullanmayı tercih ederim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.