जावा के लिए वर्कअराउंड अपवादों की जाँच की


49

मैं लैंबडास और डिफॉल्ट मेथड इंटरफेस के बारे में कई नए जावा 8 फीचर्स की सराहना करता हूं। फिर भी, मैं अभी भी जाँच किए गए अपवादों से ऊब गया हूं। उदाहरण के लिए, यदि मैं किसी वस्तु के सभी दृश्यमान क्षेत्रों को सूचीबद्ध करना चाहता हूं, तो मैं इसे बस लिखना चाहूंगा:

    Arrays.asList(p.getClass().getFields()).forEach(
        f -> System.out.println(f.get(p))
    );

फिर भी, चूंकि getविधि एक जाँच अपवाद को फेंक सकती है, जो Consumerइंटरफ़ेस अनुबंध से सहमत नहीं है , तो मुझे उस अपवाद को पकड़ना होगा और निम्नलिखित कोड लिखना होगा:

    Arrays.asList(p.getClass().getFields()).forEach(
            f -> {
                try {
                    System.out.println(f.get(p));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
    );

हालाँकि ज्यादातर मामलों में मैं सिर्फ एक अपवाद के रूप में फेंका जाना चाहता हूं RuntimeExceptionऔर कार्यक्रम को संकलित त्रुटियों के बिना संभालता हूं या नहीं।

इसलिए, मैं चेक किए गए अपवादों की झुंझलाहट के लिए मेरे विवादास्पद समाधान के बारे में आपकी राय रखना चाहूंगा। उस अंत तक, मैंने एक सहायक इंटरफ़ेस ConsumerCheckException<T>और एक उपयोगिता समारोह बनाया rethrow( डोभाल की टिप्पणी के अनुसार अद्यतन ) इस प्रकार है:

  @FunctionalInterface
  public interface ConsumerCheckException<T>{
      void accept(T elem) throws Exception;
  }

  public class Wrappers {
      public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
        return elem -> {
          try {
            c.accept(elem);
          } catch (Exception ex) {
            /**
             * within sneakyThrow() we cast to the parameterized type T. 
             * In this case that type is RuntimeException. 
             * At runtime, however, the generic types have been erased, so 
             * that there is no T type anymore to cast to, so the cast
             * disappears.
             */
            Wrappers.<RuntimeException>sneakyThrow(ex);
          }
        };
      }

      /**
       * Reinier Zwitserloot who, as far as I know, had the first mention of this
       * technique in 2009 on the java posse mailing list.
       * http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

और अब मैं लिख सकता हूँ:

    Arrays.asList(p.getClass().getFields()).forEach(
            rethrow(f -> System.out.println(f.get(p)))
    );

मुझे यकीन नहीं है कि चेक किए गए अपवादों को चालू करने के लिए यह सबसे अच्छा मुहावरा है, लेकिन जैसा कि मैंने समझाया, मैं जाँच किए गए अपवादों से निपटने के बिना अपने पहले उदाहरण को प्राप्त करने का एक और अधिक सुविधाजनक तरीका चाहूंगा और यह सबसे सरल तरीका है जो मुझे मिला। इसे करने के लिए।



2
रॉबर्ट के लिंक के अलावा, चुपके से फेंके गए चेक अपवादों पर भी एक नज़र डालें । यदि आप चाहते थे, तो आप इसे फेंकने के बजाय मूल, चेक किए गए अपवाद को फेंकने sneakyThrowके rethrowलिए अंदर का उपयोग कर सकते थे RuntimeException। वैकल्पिक रूप से आप प्रोजेक्ट@SneakyThrows लोम्बोक से एनोटेशन का उपयोग कर सकते हैं जो समान कार्य करता है।
डोभाल

1
यह भी ध्यान रखें कि Consumerरों में forEachजब समानांतर का उपयोग कर समानांतर फैशन में निष्पादित किया जा सकता Streamहै। एक फेंकने योग्य उपभोक्ता के साथ उठाया गया तो कॉलिंग थ्रेड को प्रचारित करेगा, जो 1) अन्य समवर्ती चलने वाले उपभोक्ताओं को नहीं रोकेगा, जो उचित नहीं हो सकता है, और 2) यदि एक से अधिक उपभोक्ता कुछ फेंकते हैं, तो ही फेंकने वालों में से एक को कॉलिंग थ्रेड द्वारा देखा जाएगा।
जूनास पुलका


2
लैम्ब्डा इतने बदसूरत हैं।
ट्यूलेंस कोर्डोवा

जवाबों:


16

आपकी तकनीक के फायदे, नुकसान और सीमाएं:

  • यदि कॉलिंग-कोड चेक किए गए अपवाद को संभालने के लिए है, तो आपको इसे उस विधि के थ्रो क्लॉज़ में जोड़ना होगा जिसमें स्ट्रीम शामिल है। कंपाइलर आपको इसे जोड़ने के लिए बाध्य नहीं करेगा, इसलिए इसे भूलना आसान है। उदाहरण के लिए:

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
    
  • यदि कॉलिंग-कोड पहले से ही चेक किए गए अपवाद को संभालता है, तो कंपाइलर आपको थ्रो क्लॉज को मेथड डिक्लेरेशन में जोड़ने के लिए याद दिलाएगा जिसमें स्ट्रीम शामिल है (यदि आप ऐसा नहीं कहेंगे: अपवाद कभी भी संबंधित स्टेटमेंट के शरीर में नहीं फेंका जाता है) ।

  • किसी भी स्थिति में, आप चेक किए गए अपवाद को पकड़ने के लिए स्वयं स्ट्रीम को घेरने में सक्षम नहीं होंगे। इसमें वह तरीका शामिल है जिसमें स्ट्रीम शामिल है (यदि आप प्रयास करते हैं, तो कंपाइलर कहेगा: अपवाद को कभी भी संबंधित स्टेटमेंट के शरीर में नहीं फेंका जाता है)।

  • यदि आप एक ऐसी विधि कह रहे हैं जो शाब्दिक रूप से कभी भी उस अपवाद को नहीं फेंक सकती है जिसे वह घोषित करता है, तो आपको थ्रोज़ क्लॉज़ को शामिल नहीं करना चाहिए। उदाहरण के लिए: नया स्ट्रिंग (बाइटएयर, "यूटीएफ -8") UnsupportedEncodingException को फेंकता है, लेकिन यूटीएफ -8 की गारंटी जावा कल्पना द्वारा हमेशा मौजूद रहती है। यहां, थ्रो डिक्लेरेशन एक उपद्रव है और न्यूनतम बॉयलरप्लेट के साथ इसे चुप करने का कोई भी समाधान स्वागत योग्य है।

  • यदि आप चेक किए गए अपवादों से घृणा करते हैं और महसूस करते हैं कि उन्हें जावा भाषा में कभी नहीं जोड़ा जाना चाहिए जिससे शुरू हो सके (लोगों की बढ़ती संख्या इस तरह से सोचती है, और मैं उनमें से एक नहीं हूं), तो बस फेंके गए अपवाद को न जोड़ें विधि का वह खंड जिसमें धारा होती है। इसके बाद चेक किया गया अपवाद, UNChecked अपवाद की तरह व्यवहार करेगा।

  • यदि आप एक सख्त इंटरफ़ेस लागू कर रहे हैं, जहां आपके पास फेंकता घोषणा को जोड़ने का विकल्प नहीं है, और फिर भी एक अपवाद को फेंकना पूरी तरह से उपयुक्त है, तो अपवाद को केवल एक अपवाद के रूप में लपेटकर इसे फेंकने का विशेषाधिकार प्राप्त होता है, जिसमें एक अपवाद को छोड़कर वास्तव में क्या गलत हुआ, इस बारे में कोई जानकारी नहीं है। एक अच्छा उदाहरण Runnable.run () है, जो किसी भी चेक किए गए अपवादों को नहीं फेंकता है। इस स्थिति में, आप उस पद्धति के फेंके हुए खंड में चेक किए गए अपवाद को नहीं जोड़ने का निर्णय ले सकते हैं जिसमें धारा शामिल है।

  • किसी भी मामले में, यदि आप विधि को जोड़ने के लिए (या जोड़ने के लिए मत भूलना) का निर्णय लेते हैं, तो उस पद्धति के फेंके हुए खंड को चेक किया गया है जिसमें धारा शामिल है, CHECKED अपवादों को फेंकने के इन 2 परिणामों से अवगत रहें:

    1. कॉलिंग-कोड इसे नाम से पकड़ने में सक्षम नहीं होगा (यदि आप प्रयास करते हैं, तो कंपाइलर कहेगा: अपवाद को कभी भी संबंधित स्टेटमेंट के शरीर में नहीं फेंका जाता है)। यह बुलबुला होगा और संभवतः मुख्य प्रोग्राम लूप में कुछ "कैच एक्सेप्शन" या "कैच थ्रोबेबल" द्वारा पकड़ा जा सकता है, जो कि वैसे भी हो सकता है।

    2. यह कम से कम आश्चर्य के सिद्धांत का उल्लंघन करता है: अब सभी अपवादों को पकड़ने की गारंटी देने में सक्षम होने के लिए रनटाइम एक्ससेप्शन को पकड़ने के लिए पर्याप्त नहीं होगा। इस कारण से, मेरा मानना ​​है कि इसे फ्रेमवर्क कोड में नहीं किया जाना चाहिए, बल्कि केवल व्यावसायिक कोड में जिसे आप पूरी तरह से नियंत्रित करते हैं।

संदर्भ:

नोट: यदि आप इस तकनीक का उपयोग करने का निर्णय लेते हैं, तो आप LambdaExceptionUtilStackOverflow से सहायक वर्ग की नकल कर सकते हैं : https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exception-from-inside-java-8 -बचता है । यह आपको उदाहरणों के साथ पूर्ण कार्यान्वयन (फ़ंक्शन, उपभोक्ता, आपूर्तिकर्ता ...) देता है।


6

इस उदाहरण में, क्या यह कभी भी विफल हो सकता है? ऐसा मत सोचो, लेकिन शायद आपका मामला विशेष है। अगर यह वास्तव में "विफल नहीं हो सकता", और इसकी सिर्फ एक कष्टप्रद संकलक बात है, तो मुझे अपवाद को लपेटना और Errorएक टिप्पणी "नहीं हो सकती" के साथ फेंकना पसंद है । रखरखाव के लिए चीजें बहुत स्पष्ट करती हैं । अन्यथा वे आश्चर्य करेंगे कि "यह कैसे हो सकता है" और "कौन बिल्ली इसे संभालती है?"

यह एक विवादास्पद व्यवहार में इसलिए वाई.एम.एम.वी. मैं शायद कुछ डाउनवोट पाऊंगा।


3
+1 क्योंकि आप एक एरर फेंकेंगे। कैच ब्लॉक पर डिबगिंग के घंटों के बाद मैं कितनी बार समाप्त हो गया हूं जिसमें केवल एक ही लाइन टिप्पणी "नहीं हो सकती" ...
एक्सल

3
-1 क्योंकि आप एक त्रुटि फेंक देंगे। त्रुटियों को इंगित करना है कि जेवीएम में कुछ गलत है और ऊपरी स्तर उन्हें तदनुसार संभाल सकते हैं। यदि आप इस तरह से चुनते हैं, तो आपको RuntimeException को फेंक देना चाहिए। एक अन्य संभावित वर्कआर्ड हैं एसेर्ट्स (आवश्यकता-सक्षम ध्वज) या लॉगिंग।
जोड़ी

3
@duros: इस बात पर निर्भर करता है कि अपवाद "क्यों नहीं हो सकता", तथ्य यह है कि इसे फेंक दिया जाता है यह संकेत दे सकता है कि कुछ गंभीर रूप से गलत है। मान लीजिए, उदाहरण के लिए, यदि कोई cloneएक सीलबंद प्रकार पर कॉल करता है Fooजो इसे समर्थन करने के लिए जाना जाता है, और यह फेंकता है CloneNotSupportedException। जब तक कोड किसी अन्य अप्रत्याशित प्रकार के साथ लिंक नहीं हो जाता, तब तक ऐसा कैसे हो सकता है Foo? और अगर ऐसा होता है, तो क्या कुछ भी भरोसा किया जा सकता है?
Supercat

1
@supercat उत्कृष्ट उदाहरण। अन्य लोग अनुपलब्ध स्ट्रिंग एन्कोडिंग या संदेशडिस्ट से अपवाद फेंक रहे हैं यदि UTF-8 या SHA गायब हैं तो आपका रनटाइम दूषित होने की संभावना है।
user949300

1
@duros यह पूरी तरह से गलत धारणा है। An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.(जावदोक से उद्धृत Error) यह वास्तव में उस तरह की स्थिति है, इसलिए अपर्याप्त होने से दूर, Errorयहां फेंकना सबसे उपयुक्त विकल्प है।
biziclop

1

इस कोड का एक और संस्करण जहाँ जाँच अपवाद केवल देरी से किया गया है:

public class Cocoon {
         static <T extends Throwable> T forgetThrowsClause(Throwable t) throws T{
            throw (T) t;
        }

        public static <X, T extends Throwable> Consumer<X> consumer(PeskyConsumer<X,T> touchyConsumer) throws T {
            return new Consumer<X>() {
                @Override
                public void accept(X t) {
                    try {
                        touchyConsumer.accept(t);
                    } catch (Throwable exc) {
                        Cocoon.<RuntimeException>forgetThrowsClause(exc) ;
                    }

                }
            } ;
        }
// and so on for Function, and other codes from java.util.function
}

जादू है कि अगर आप कहते हैं:

 myArrayList.forEach(Cocoon.consumer(MyClass::methodThatThrowsException)) ;

तब आपका कोड अपवाद को पकड़ने के लिए बाध्य होगा


अगर यह एक समानांतर धारा पर चलाया जाता है तो क्या होता है जहां विभिन्न थ्रेड में तत्वों का सेवन किया जाता है?
prunge

0

डरपोक शांत है! मैं पूरी तरह से आपका दर्द महसूस करता हूं।

यदि आपको जावा में रहना है ...

पगुरो में कार्यात्मक इंटरफेस हैं जो चेक किए गए अपवादों को लपेटते हैं ताकि आपको इसके बारे में और अधिक नहीं सोचना पड़े। इसमें क्लूज ट्रांसड्यूसर (या जावा 8 स्ट्रीम) की तर्ज पर अपरिवर्तनीय संग्रह और कार्यात्मक परिवर्तन शामिल हैं जो कार्यात्मक इंटरफेस और चेक किए गए अपवादों को लपेटते हैं।

बाहर की कोशिश करने के लिए VAVr और द एक्लिप्स कलेक्शन भी है।

अन्यथा, कोटलिन का उपयोग करें

कोटलिन जावा के साथ 2-तरफा संगत है, इसमें कोई अपवाद नहीं है, कोई आदिम नहीं है (अच्छी तरह से, प्रोग्रामर को इसके बारे में नहीं सोचना है), एक बेहतर प्रकार की प्रणाली, अपरिवर्तनीयता को मानता है, आदि। हालांकि मैं ऊपर पगुरो पुस्तकालय को क्यूरेट करता हूं, मैं ' मी सभी कोटलिन के लिए कर सकते हैं मैं जावा कोड परिवर्तित। कुछ भी मैं जावा में करता था अब मैं कोटलिन में करना पसंद करता हूं।


किसी ने इसे वोट दिया, जो ठीक है, लेकिन मैं कुछ भी नहीं सीखूंगा जब तक कि आप मुझे यह नहीं बताएं कि आपने इसे क्यों वोट दिया।
ग्लेनपेटर्सन

1
GitHub में अपने पुस्तकालय के लिए +1, तुम भी जाँच कर सकते हैं eclipse.org/collections या परियोजना vavr जो कार्यात्मक और अपरिवर्तनीय संग्रह प्रदान करते हैं। इन पर आपके क्या विचार हैं?
फायरफिल

-1

जाँच किए गए अपवाद बहुत उपयोगी हैं और उनकी हैंडलिंग उस उत्पाद पर निर्भर करती है जिस पर आप कोड का हिस्सा हैं:

  • एक पुस्तकालय
  • एक डेस्कटॉप अनुप्रयोग
  • कुछ बॉक्स के भीतर एक सर्वर चल रहा है
  • एक अकादमिक अभ्यास

आमतौर पर एक पुस्तकालय को चेक किए गए अपवादों को संभालना नहीं चाहिए, लेकिन सार्वजनिक एपीआई द्वारा फेंके जाने की घोषणा करना चाहिए। दुर्लभ मामला जब वे सादे रंटाइमएक्सपेप्टन में लिपटे हो सकते हैं, उदाहरण के लिए, लाइब्रेरी कोड के अंदर कुछ निजी xml दस्तावेज़ बनाना और इसे पार्स करना, कुछ अस्थायी फ़ाइल बनाना और फिर पढ़ना।

डेस्कटॉप एप्लिकेशन के लिए आपको अपना स्वयं का अपवाद हैंडलिंग फ्रेमवर्क बनाकर उत्पाद का विकास शुरू करना होगा। अपने स्वयं के ढांचे का उपयोग करते हुए आप आमतौर पर दो से चार रैपरों में से कुछ में अपने अपवाद की जांच करेंगे (आपकी कस्टम उप-कक्षाएं रंटेइमेसेपियन) और एक एकल अपवाद हैंडलर द्वारा उन्हें संभालेंगे।

सर्वर के लिए आमतौर पर आपका कोड कुछ ढांचे के अंदर चलेगा। यदि आप किसी भी (एक नंगे सर्वर) का उपयोग नहीं करते हैं, तो ऊपर वर्णित के समान (रंटाइम्स के आधार पर) अपना स्वयं का ढांचा बनाएं।

अकादमिक अभ्यास के लिए आप हमेशा उन्हें सीधे RuntimeExcepton में लपेट सकते हैं। कोई एक छोटा ढांचा भी बना सकता है।


-1

आप इस परियोजना पर एक नज़र रखना चाहते हैं ; मैंने इसे विशेष रूप से जाँच किए गए अपवादों और कार्यात्मक इंटरफेस की समस्या के कारण बनाया था।

इसके साथ आप अपने कस्टम कोड लिखने के बजाय ऐसा कर सकते हैं:

// I don't know the type of f, so it's Foo...
final ThrowingConsumer<Foo> consumer = f -> System.out.println(f.get(p));

चूंकि उन सभी थ्रोइंग * इंटरफेस अपने गैर थ्रोइंग समकक्षों का विस्तार करते हैं, तो आप उन्हें सीधे स्ट्रीम में उपयोग कर सकते हैं:

Arrays.asList(p.getClass().getFields()).forEach(consumer);

या आप बस:

import static com.github.fge.lambdas.consumers.Consumers.wrap;

Arrays.asList(p.getClass().getFields())
    .forEach(wrap(f -> System.out.println(f.get(p)));

और बाकी चीज़ें।


बेहतर अभी भीfields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))
user2418306

क्या डाउनवॉटर कृपया समझाएंगे?
fge
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.