Προγραμματισμός

Αποτελεσματικός χειρισμός Java NullPointerException

Δεν χρειάζεται μεγάλη εμπειρία ανάπτυξης Java για να μάθετε από πρώτο χέρι τι είναι το NullPointerException. Στην πραγματικότητα, ένα άτομο έχει επισημάνει ότι αντιμετωπίζει αυτό ως το νούμερο ένα λάθος που κάνουν οι προγραμματιστές Java. Έχω κάνει blog στο παρελθόν σχετικά με τη χρήση του String.value (Object) για τη μείωση των ανεπιθύμητων NullPointerExceptions. Υπάρχουν πολλές άλλες απλές τεχνικές που μπορεί κανείς να χρησιμοποιήσει για να μειώσει ή να εξαλείψει τα περιστατικά αυτού του κοινού τύπου RuntimeException που είναι μαζί μας από το JDK 1.0. Αυτή η ανάρτηση ιστολογίου συλλέγει και συνοψίζει μερικές από τις πιο δημοφιλείς από αυτές τις τεχνικές.

Ελέγξτε κάθε αντικείμενο για μηδέν πριν από τη χρήση

Ο πιο σίγουρος τρόπος για να αποφύγετε ένα NullPointerException είναι να ελέγξετε όλες τις αναφορές αντικειμένων για να βεβαιωθείτε ότι δεν είναι μηδενικές πριν από την πρόσβαση σε ένα από τα πεδία ή τις μεθόδους του αντικειμένου. Όπως δείχνει το ακόλουθο παράδειγμα, αυτή είναι μια πολύ απλή τεχνική.

final String causStr = "προσθήκη String στο Deque που έχει οριστεί σε null."; final String elementStr = "Fudd"; Deque deque = μηδέν; δοκιμάστε το {deque.push (elementStr); log ("Επιτυχής στο" + menyebabkanStr, System.out); } catch (NullPointerException nullPointer) {log (penyebabStr, nullPointer, System.out); } δοκιμάστε {if (deque == null) {deque = new LinkedList (); } deque.push (elementStr); log ("Επιτυχής στο" + menyebabkanStr + "(ελέγχοντας πρώτα για μηδενική και θεσμική εφαρμογή Deque)", System.out); } catch (NullPointerException nullPointer) {log (penyebabStr, nullPointer, System.out); } 

Στον παραπάνω κώδικα, το Deque που χρησιμοποιείται αρχικοποιείται σκόπιμα για να μηδενιστεί για να διευκολυνθεί το παράδειγμα. Ο κωδικός στην πρώτη δοκιμάστε Το μπλοκ δεν ελέγχει το null πριν προσπαθήσει να αποκτήσει πρόσβαση σε μια μέθοδο Deque. Ο κωδικός στο δεύτερο δοκιμάστε Το μπλοκ ελέγχει το μηδέν και δημιουργεί την εφαρμογή του Ντεκ (LinkedList) εάν είναι μηδενική. Η έξοδος και από τα δύο παραδείγματα μοιάζει με αυτό:

ΣΦΑΛΜΑ: Παρουσιάστηκε NullPointerException κατά την προσπάθεια προσθήκης String στο Deque που έχει οριστεί ως null. java.lang.NullPointerException ΠΛΗΡΟΦΟΡΙΕΣ: Επιτυχής προσθήκη String στο Deque που έχει οριστεί ως null. (ελέγχοντας πρώτα για μηδενική και τεκμηρίωση της εφαρμογής Deque) 

Το μήνυμα μετά το ΣΦΑΛΜΑ στην παραπάνω έξοδο υποδεικνύει ότι a NullPointerException ρίχνεται όταν μια κλήση μεθόδου επιχειρείται στο μηδέν Ντεκ. Το μήνυμα που ακολουθεί το INFO στην έξοδο παραπάνω δείχνει ότι με τον έλεγχο Ντεκ για το μηδέν πρώτα και στη συνέχεια για τη δημιουργία μιας νέας εφαρμογής για αυτό όταν είναι μηδενική, η εξαίρεση αποφεύχθηκε εντελώς.

Αυτή η προσέγγιση χρησιμοποιείται συχνά και, όπως φαίνεται παραπάνω, μπορεί να είναι πολύ χρήσιμη για την αποφυγή ανεπιθύμητων (απροσδόκητων) NullPointerException περιπτώσεις. Ωστόσο, δεν είναι χωρίς το κόστος του. Ο έλεγχος για μηδέν πριν από τη χρήση κάθε αντικειμένου μπορεί να φουσκώσει τον κώδικα, μπορεί να είναι κουραστικός για να γράψει και να ανοίξει περισσότερο χώρο για προβλήματα με την ανάπτυξη και τη συντήρηση του πρόσθετου κώδικα. Για αυτόν τον λόγο, έγινε λόγος για την εισαγωγή υποστήριξης γλώσσας Java για ενσωματωμένο μηδενικό εντοπισμό, αυτόματη προσθήκη αυτών των ελέγχων για μηδενική μετά την αρχική κωδικοποίηση, τύπους μηδενικής ασφάλειας, χρήση του Προγράμματος Προσανατολισμένου Προγράμματος (AOP) για προσθήκη μηδενικού ελέγχου σε κώδικα byte και άλλα εργαλεία ανίχνευσης μηδενικών.

Ο Groovy παρέχει ήδη έναν βολικό μηχανισμό για την αντιμετώπιση αναφορών αντικειμένων που είναι δυνητικά μηδενικές. Χειριστής ασφαλούς πλοήγησης του Groovy (?.) επιστρέφει null αντί να ρίχνει a NullPointerException όταν γίνεται πρόσβαση σε αναφορά μηδενικού αντικειμένου.

Επειδή ο έλεγχος μηδενικού για κάθε αναφορά αντικειμένου μπορεί να είναι κουραστικός και να φουσκώνει τον κώδικα, πολλοί προγραμματιστές επιλέγουν να επιλέξουν με σύνεση ποια αντικείμενα θα ελέγχουν για μηδέν. Αυτό συνήθως οδηγεί στον έλεγχο του null σε όλα τα αντικείμενα δυνητικά άγνωστης προέλευσης. Η ιδέα εδώ είναι ότι τα αντικείμενα μπορούν να ελεγχθούν σε εκτεθειμένες διεπαφές και στη συνέχεια να θεωρηθούν ασφαλή μετά τον αρχικό έλεγχο.

Αυτή είναι μια κατάσταση όπου ο τριμερής χειριστής μπορεί να είναι ιδιαίτερα χρήσιμος. Αντί

// ανέκτησε ένα BigDecimal που ονομάζεται someObject String returnString; εάν (someObject! = null) {returnString = someObject.toEngineeringString (); } αλλιώς {returnString = ""; } 

ο τριαδικός χειριστής υποστηρίζει αυτήν την πιο περιεκτική σύνταξη

// ανέκτησε ένα BigDecimal που ονομάζεται someObject final String returnString = (someObject! = null); someObject.toEngineeringString (): ""; } 

Έλεγχος μεθόδων Επιχειρήματα για Null

Η τεχνική που μόλις συζητήθηκε μπορεί να χρησιμοποιηθεί σε όλα τα αντικείμενα. Όπως αναφέρεται στην περιγραφή αυτής της τεχνικής, πολλοί προγραμματιστές επιλέγουν να ελέγχουν αντικείμενα μόνο για μηδενικά όταν προέρχονται από "μη αξιόπιστες" πηγές. Αυτό σημαίνει συχνά δοκιμή για το μηδενικό πρώτο σε μεθόδους που εκτίθενται σε εξωτερικούς καλούντες. Για παράδειγμα, σε μια συγκεκριμένη τάξη, ο προγραμματιστής μπορεί να επιλέξει να ελέγξει για μηδενικά όλα τα αντικείμενα στα οποία έχει περάσει δημόσιο μεθόδους, αλλά όχι έλεγχος για null in ιδιωτικός μεθόδους.

Ο παρακάτω κώδικας δείχνει αυτόν τον έλεγχο για μηδενική καταχώριση μεθόδου. Περιλαμβάνει μια μεμονωμένη μέθοδο ως την επίδειξη μέθοδο που γυρίζει και καλεί δύο μεθόδους, περνώντας κάθε μέθοδο ένα μηδενικό όρισμα. Μία από τις μεθόδους που λαμβάνουν ένα μηδενικό όρισμα ελέγχει πρώτα αυτό το όρισμα για το μηδέν, αλλά η άλλη απλά υποθέτει ότι η παράμετρος που πέρασε δεν είναι μηδενική.

 / ** * Προσθήκη προκαθορισμένου κειμένου συμβολοσειράς στο παρεχόμενο StringBuilder. * * @param builder Το StringBuilder που θα έχει προσαρτημένο κείμενο σε αυτό. θα πρέπει να είναι μηδενικό. * @throws IllegalArgumentException Πετάγεται εάν το παρεχόμενο StringBuilder είναι * null. * / private void appendPredefinedTextToProvidedBuilderCheckForNull (final StringBuilder builder) {if (builder == null) {ρίξτε νέο IllegalArgumentException ("Η παρεχόμενη StringBuilder ήταν μηδενική. Πρέπει να παρέχεται μη μηδενική τιμή."); } builder.append ("Ευχαριστούμε που παρέχετε ένα StringBuilder."); } / ** * Προσθήκη προκαθορισμένου κειμένου συμβολοσειράς στο παρεχόμενο StringBuilder. * * @param builder Το StringBuilder που θα έχει προσαρτημένο κείμενο σε αυτό. θα πρέπει να είναι μηδενικό. * / private void appendPredefinedTextToProvidedBuilderNoCheckForNull (final StringBuilder builder) {builder.append ("Ευχαριστούμε που παρέχετε ένα StringBuilder."); } / ** * Επιδείξτε το αποτέλεσμα του ελέγχου παραμέτρων για null πριν προσπαθήσετε να χρησιμοποιήσετε * παραμέτρους που έχουν πιθανώς null. * / public void مظاہرہCheckingArgumentsForNull () {final String menyebabkanStr = "παροχή null στη μέθοδο ως επιχείρημα."; logHeader ("ΠΑΡΑΜΕΤΡΟΙ ΜΕΘΟΔΟΥ ΕΛΕΓΧΟΥ ΔΗΜΟΣΙΕΥΣΗΣ ΓΙΑ ΝΟΥΛ", System.out); δοκιμάστε το {appendPredefinedTextToProvidedBuilderNoCheckForNull (null); } catch (NullPointerException nullPointer) {log (penyebabStr, nullPointer, System.out); } δοκιμάστε το {appendPredefinedTextToProvidedBuilderCheckForNull (null); } catch (IllegalArgumentException illegalArgument) {log (causStr, illegalArgument, System.out); }} 

Όταν εκτελείται ο παραπάνω κωδικός, η έξοδος εμφανίζεται όπως φαίνεται στη συνέχεια.

ΣΦΑΛΜΑ: Παρουσιάστηκε το NullPointerException κατά την προσπάθεια παροχής της μεθόδου null ως όρισμα. java.lang.NullPointerException ΣΦΑΛΜΑ: Το IllegalArgumentException αντιμετώπισε κατά την προσπάθεια παροχής μηδενικής μεθόδου ως επιχείρημα. java.lang.IllegalArgumentException: Το παρεχόμενο StringBuilder ήταν μηδενικό. Πρέπει να παρέχεται μη μηδενική τιμή. 

Και στις δύο περιπτώσεις, καταγράφηκε ένα μήνυμα σφάλματος. Ωστόσο, η περίπτωση κατά την οποία ελέγχθηκε ένα μηδέν για να ρίξει μια διαφημιζόμενη IllegalArgumentException που περιλάμβανε πρόσθετες πληροφορίες περιβάλλοντος σχετικά με το πότε αντιμετωπίστηκε η μηδενική. Εναλλακτικά, αυτή η μηδενική παράμετρος θα μπορούσε να αντιμετωπιστεί με διάφορους τρόπους. Για την περίπτωση στην οποία δεν αντιμετωπίστηκε μια μηδενική παράμετρος, δεν υπήρχαν επιλογές για τον χειρισμό της. Πολλοί άνθρωποι προτιμούν να ρίξουν ένα NullPolinterException με τις πρόσθετες πληροφορίες περιβάλλοντος όταν ένα μηδέν ανακαλύπτεται ρητά (βλ. στοιχείο # 60 στη δεύτερη έκδοση του Αποτελεσματική Java ή το στοιχείο # 42 στην πρώτη έκδοση), αλλά έχω μια μικρή προτίμηση για IlegalArgumentException όταν είναι ρητώς ένα όρισμα μεθόδου που είναι μηδενικό, διότι πιστεύω ότι η εξαίρεση προσθέτει λεπτομέρειες περιβάλλοντος και είναι εύκολο να συμπεριληφθεί το "null" στο θέμα.

Η τεχνική ελέγχου των επιχειρημάτων της μεθόδου null είναι πραγματικά ένα υποσύνολο της γενικότερης τεχνικής ελέγχου όλων των αντικειμένων για null. Ωστόσο, όπως περιγράφεται παραπάνω, τα επιχειρήματα για μεθόδους που εκτίθενται στο κοινό είναι συχνά το λιγότερο αξιόπιστο σε μια εφαρμογή και επομένως ο έλεγχος τους μπορεί να είναι πιο σημαντικός από τον έλεγχο του μέσου αντικειμένου για μηδέν.

Ο έλεγχος των παραμέτρων της μεθόδου για το null είναι επίσης ένα υποσύνολο της γενικότερης πρακτικής του ελέγχου παραμέτρων μεθόδου για γενική εγκυρότητα όπως συζητείται στο σημείο # 38 της δεύτερης έκδοσης του Αποτελεσματική Java (Θέση 23 στην πρώτη έκδοση).

Εξετάστε τα πρωτόγονα παρά τα αντικείμενα

Δεν νομίζω ότι είναι καλή ιδέα να επιλέξετε έναν πρωτόγονο τύπο δεδομένων (όπως int) πάνω από τον αντίστοιχο τύπο αναφοράς αντικειμένου (όπως Integer) απλά για να αποφευχθεί η πιθανότητα a NullPointerException, αλλά δεν υπάρχει αμφιβολία ότι ένα από τα πλεονεκτήματα των πρωτόγονων τύπων είναι ότι δεν οδηγούν NullPointerExceptionμικρό. Ωστόσο, τα πρωτόγονα πρέπει να ελεγχθούν για εγκυρότητα (ο μήνας δεν μπορεί να είναι αρνητικός ακέραιος αριθμός) και έτσι αυτό το όφελος μπορεί να είναι μικρό. Από την άλλη πλευρά, τα πρωτόγονα δεν μπορούν να χρησιμοποιηθούν στις Συλλογές Java και υπάρχουν φορές που κάποιος θέλει τη δυνατότητα να ορίσει μια τιμή σε μηδέν.

Το πιο σημαντικό πράγμα είναι να είστε πολύ προσεκτικοί σχετικά με το συνδυασμό πρωτόγονων, τύπων αναφοράς και αυτόματης εμφάνισης. Υπάρχει μια προειδοποίηση στο Αποτελεσματική Java (Δεύτερη έκδοση, σημείο # 49) σχετικά με τους κινδύνους, συμπεριλαμβανομένης της ρίψης NullPointerException, που σχετίζονται με την απρόσεκτη ανάμειξη πρωτόγονων και τύπων αναφοράς.

Εξετάστε προσεκτικά τις κλήσεις με μέθοδο αλυσοδεμένων

ΕΝΑ NullPointerException μπορεί να είναι πολύ εύκολο να βρεθεί, επειδή ένας αριθμός γραμμής θα αναφέρει πού συνέβη. Για παράδειγμα, ένα ίχνος στοίβας μπορεί να μοιάζει με αυτό που εμφανίζεται στη συνέχεια:

java.lang.NullPointerException at dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace (MakingingNullPointerExamples.java: 2222) στο dustin.examples.AvoidingNullPointerExamples.main (ΑποφεύγονταςNamPoin) 

Το ίχνος στοίβας καθιστά προφανές ότι το NullPointerException ρίχτηκε ως αποτέλεσμα του κώδικα που εκτελέστηκε στη γραμμή 222 του ΑποφυγήNullPointerExamples.java. Ακόμη και με τον αριθμό γραμμής που παρέχεται, μπορεί να είναι δύσκολο να περιορίσετε ποιο αντικείμενο είναι μηδενικό εάν υπάρχουν πολλά αντικείμενα με μεθόδους ή πεδία στα οποία υπάρχει πρόσβαση στην ίδια γραμμή.

Για παράδειγμα, μια δήλωση όπως someObject.getObjectA (). getObjectB (). getObjectC (). toString (); έχει τέσσερις πιθανές κλήσεις που θα μπορούσαν να έχουν ρίξει το NullPointerException αποδίδεται στην ίδια γραμμή κώδικα. Η χρήση ενός προγράμματος εντοπισμού σφαλμάτων μπορεί να σας βοηθήσει, αλλά μπορεί να υπάρχουν καταστάσεις όπου είναι προτιμότερο να απλώς σπάσετε τον παραπάνω κώδικα, έτσι ώστε κάθε κλήση να εκτελείται σε ξεχωριστή γραμμή. Αυτό επιτρέπει στον αριθμό γραμμής που περιέχεται σε ένα ίχνος στοίβας να υποδεικνύει εύκολα ποια ακριβής κλήση ήταν το πρόβλημα. Επιπλέον, διευκολύνει τον ρητό έλεγχο κάθε αντικειμένου για μηδέν. Ωστόσο, στο μειονέκτημα, η διάσπαση του κώδικα αυξάνει τη γραμμή του αριθμού των κωδικών (σε ορισμένους που είναι θετικό!) Και μπορεί να μην είναι πάντα επιθυμητό, ​​ειδικά αν κάποιος είναι βέβαιος ότι καμία από τις εν λόγω μεθόδους δεν θα είναι ποτέ άκυρη.

Κάντε το NullPointerExceptions πιο ενημερωτικό

Στην παραπάνω σύσταση, η προειδοποίηση ήταν να εξετάσει προσεκτικά τη χρήση της μεθόδου κλήσης αλυσίδας κυρίως επειδή έκανε τον αριθμό γραμμής στο stack trace για NullPointerException λιγότερο χρήσιμη από ό, τι διαφορετικά. Ωστόσο, ο αριθμός γραμμής εμφανίζεται μόνο σε ένα ίχνος στοίβας όταν ο κώδικας συντάχθηκε με ενεργοποιημένη τη σημαία εντοπισμού σφαλμάτων. Εάν συντάχθηκε χωρίς εντοπισμό σφαλμάτων, το ίχνος στοίβας μοιάζει με αυτό που φαίνεται παρακάτω:

java.lang.NullPointerException at dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace (Άγνωστη πηγή) στο dustin.examples.AvoidingNullPointerExamples.main (Άγνωστη πηγή) 

Όπως δείχνει η παραπάνω έξοδος, υπάρχει ένα όνομα μεθόδου, αλλά όχι αριθμός γραμμής για το NullPointerException. Αυτό καθιστά πιο δύσκολο να προσδιορίσετε αμέσως τι στον κώδικα οδήγησε στην εξαίρεση. Ένας τρόπος αντιμετώπισης αυτού είναι να παρέχετε πληροφορίες περιβάλλοντος σε κάθε ρίψη NullPointerException. Αυτή η ιδέα αποδείχθηκε νωρίτερα όταν NullPointerException πιάστηκε και επανεμφανίστηκε με πρόσθετες πληροφορίες περιβάλλοντος ως IlegalArgumentException. Ωστόσο, ακόμη και αν η εξαίρεση απλώς εκτοξευτεί εκ νέου ως άλλη NullPointerException με πληροφορίες περιβάλλοντος, είναι ακόμα χρήσιμο. Οι πληροφορίες περιβάλλοντος βοηθούν το άτομο να εντοπίσει τον κώδικα για να εντοπίσει πιο γρήγορα την πραγματική αιτία του προβλήματος.

Το ακόλουθο παράδειγμα δείχνει αυτήν την αρχή.

τελικό Ημερολόγιο nullCalendar = null; δοκιμάστε το {final Date date = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException με χρήσιμα δεδομένα", nullPointer, System.out); } δοκιμάστε {if (nullCalendar == null) {ρίξτε νέο NullPointerException ("Δεν ήταν δυνατή η εξαγωγή ημερομηνίας από το παρεχόμενο Ημερολόγιο"); } τελικό Ημερομηνία ημερομηνίας = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException με χρήσιμα δεδομένα", nullPointer, System.out); } 

Η έξοδος από την εκτέλεση του παραπάνω κώδικα φαίνεται ως εξής.

ΣΦΑΛΜΑ: Παρουσιάστηκε το NullPointerException κατά την προσπάθεια να το NullPointerException με χρήσιμα δεδομένα 
$config[zx-auto] not found$config[zx-overlay] not found